From 87fe20d1c5fbb61c05c91f992fa46301a40214f1 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 May 2024 19:20:11 +0200 Subject: [PATCH 01/49] explicitly run backup in foreground service --- src/main/AndroidManifest.xml | 4 --- .../receiver/WorkManagerEventReceiver.java | 32 ------------------- .../worker/ExportBackupWorker.java | 23 ++++++------- 3 files changed, 10 insertions(+), 49 deletions(-) delete mode 100644 src/main/java/eu/siacs/conversations/receiver/WorkManagerEventReceiver.java diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 749b593c6..382f0ebd9 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -142,10 +142,6 @@ - - diff --git a/src/main/java/eu/siacs/conversations/receiver/WorkManagerEventReceiver.java b/src/main/java/eu/siacs/conversations/receiver/WorkManagerEventReceiver.java deleted file mode 100644 index 71ec74f53..000000000 --- a/src/main/java/eu/siacs/conversations/receiver/WorkManagerEventReceiver.java +++ /dev/null @@ -1,32 +0,0 @@ -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/worker/ExportBackupWorker.java b/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java index 75ef9036c..02fc16326 100644 --- a/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java +++ b/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java @@ -17,6 +17,7 @@ import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import androidx.work.ForegroundInfo; +import androidx.work.WorkManager; import androidx.work.Worker; import androidx.work.WorkerParameters; @@ -33,7 +34,6 @@ 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; @@ -95,6 +95,7 @@ public ExportBackupWorker(@NonNull Context context, @NonNull WorkerParameters wo @NonNull @Override public Result doWork() { + setForegroundAsync(getForegroundInfo()); final List files; try { files = export(); @@ -223,18 +224,14 @@ private void export( 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 var cancelPendingIntent = + WorkManager.getInstance(context).createCancelPendingIntent(getId()); + 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()) { From f1d5436ef22c3603f5dda63370288b744a8874f5 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 22 May 2024 09:10:24 +0200 Subject: [PATCH 02/49] null check intent extras --- .../siacs/conversations/ui/RtpSessionActivity.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 7a2bb0938..1bb11c973 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -34,6 +34,7 @@ import com.google.common.base.Optional; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -487,7 +488,12 @@ private void initializeWithIntent(final Event event, @NonNull final Intent inten final String action = intent.getAction(); Log.d(Config.LOGTAG, "initializeWithIntent(" + event + "," + action + ")"); final Account account = extractAccount(intent); - final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH)); + final var extraWith = intent.getStringExtra(EXTRA_WITH); + final Jid with = Strings.isNullOrEmpty(extraWith) ? null : Jid.ofEscaped(extraWith); + if (with == null || account == null) { + Log.e(Config.LOGTAG, "intent is missing extras (account or with)"); + return; + } final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID); if (sessionId != null) { if (initializeActivityWithRunningRtpSession(account, with, sessionId)) { @@ -1038,8 +1044,7 @@ private void updateInCallButtonConfigurationSpeaker( final CallIntegration.AudioDevice selectedAudioDevice, final int numberOfChoices) { switch (selectedAudioDevice) { case EARPIECE -> { - this.binding.inCallActionRight.setImageResource( - R.drawable.ic_volume_off_24dp); + this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_off_24dp); if (numberOfChoices >= 2) { this.binding.inCallActionRight.setOnClickListener(this::switchToSpeaker); } else { @@ -1062,8 +1067,7 @@ private void updateInCallButtonConfigurationSpeaker( } } case BLUETOOTH -> { - this.binding.inCallActionRight.setImageResource( - R.drawable.ic_bluetooth_audio_24dp); + this.binding.inCallActionRight.setImageResource(R.drawable.ic_bluetooth_audio_24dp); this.binding.inCallActionRight.setOnClickListener(null); this.binding.inCallActionRight.setClickable(false); } From a42f6874627d97610686f4c6d5d223a58c6f832a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 22 May 2024 14:57:54 +0200 Subject: [PATCH 03/49] remove unnecessary log. fixes #300 --- src/main/java/eu/siacs/conversations/parser/MessageParser.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 6accc1f12..011942c50 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -668,7 +668,6 @@ public void onMessagePacketReceived(Account account, MessagePacket original) { final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam final boolean duplicate = conversation.hasDuplicateMessage(message); if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches) && !duplicate) { - Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'"); synchronized (replacedMessage) { final String uuid = replacedMessage.getUuid(); replacedMessage.setUuid(UUID.randomUUID().toString()); From 89fbd59d9cbae15e8300809ec6af2a0e1032bdff Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 22 May 2024 14:58:37 +0200 Subject: [PATCH 04/49] version bump to 2.16.2 + changelog --- CHANGELOG.md | 4 ++++ build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/4211304.txt | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/4211304.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index ca61483ed..cdcc3b23f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### Version 2.16.2 + +* Run Backup as foreground service to prevent process being stopped after 10 minutes + ### Version 2.16.1 * Fix call getting un-muted when switching output devices diff --git a/build.gradle b/build.gradle index 918c60043..beb73f787 100644 --- a/build.gradle +++ b/build.gradle @@ -100,8 +100,8 @@ android { defaultConfig { minSdkVersion 23 targetSdkVersion 34 - versionCode 42112 - versionName "2.16.1" + versionCode 42113 + versionName "2.16.2" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/4211304.txt b/fastlane/metadata/android/en-US/changelogs/4211304.txt new file mode 100644 index 000000000..4e9637aa1 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4211304.txt @@ -0,0 +1 @@ +* Run Backup as foreground service to prevent process being stopped after 10 minutes From ee641872273ad2bd8697b6e2ba9fd5e4cd1b2974 Mon Sep 17 00:00:00 2001 From: kolAflash Date: Wed, 22 May 2024 17:42:12 +0200 Subject: [PATCH 05/49] Documentation for the meaning of the XEP-0333 check markers. A discussion at xmpp:conversations@conference.siacs.eu yielded, that previous to this there was no documentation or in-app information about the meaning of the check marks. --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b006810bd..b03efffbf 100644 --- a/README.md +++ b/README.md @@ -242,13 +242,17 @@ everybody in your contact list to know that you have been using your computer at In the past status has been used to judge the likelihood of whether or not your messages are being read. This is no longer necessary. With Chat Markers (XEP-0333, supported by Conversations since 0.4) we have the ability to **know** -whether or not your messages are being read. Similar things can be said for -priorities. In the past priorities have been used (by servers, not by clients!) -to route your messages to one specific client. With carbon messages (XEP-0280, -supported by Conversations since 0.1) this is no longer necessary. Using -priorities to route OTR messages isn't practical either because they are not -changeable on the fly. Metrics like last active client (the client which sent -the last message) are much better. +whether or not your messages are being read. +* one check mark ✓: message has been send (arrived at server) +* two check marks ✓✓: message has arrived at receiver +* text _"has read up to this point"_: receiver has read the message (receiver might has read notifications turned off) + +Similar things can be said for priorities. In the past priorities have been used +(by servers, not by clients!) to route your messages to one specific client. +With carbon messages (XEP-0280, supported by Conversations since 0.1) this is no +longer necessary. Using priorities to route OTR messages isn't practical either +because they are not changeable on the fly. Metrics like last active client (the +client which sent the last message) are much better. Unfortunately these modern replacements for legacy XMPP features are not widely adopted. However Conversations should be an instant messenger for the future and From 67326817e6eabe1b04611730a212ecb9d58e810b Mon Sep 17 00:00:00 2001 From: iNPUTmice Date: Thu, 23 May 2024 07:27:21 +0000 Subject: [PATCH 06/49] no reason to mention OTR in readme --- README.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/README.md b/README.md index b03efffbf..d786660bf 100644 --- a/README.md +++ b/README.md @@ -250,19 +250,7 @@ whether or not your messages are being read. Similar things can be said for priorities. In the past priorities have been used (by servers, not by clients!) to route your messages to one specific client. With carbon messages (XEP-0280, supported by Conversations since 0.1) this is no -longer necessary. Using priorities to route OTR messages isn't practical either -because they are not changeable on the fly. Metrics like last active client (the -client which sent the last message) are much better. - -Unfortunately these modern replacements for legacy XMPP features are not widely -adopted. However Conversations should be an instant messenger for the future and -instead of making Conversations compatible with the past we should work on -implementing new, improved technologies and getting them into other XMPP clients -as well. - -Making these status and priority optional isn't a solution either because -Conversations is trying to get rid of old behaviours and set an example for -other clients. +longer necessary. #### Translations Translations are managed on [Weblate](https://translate.codeberg.org/projects/conversations/). From 40cc114d9905b6834cd50aa1340826b5b669b3ff Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Thu, 23 May 2024 07:03:52 +0000 Subject: [PATCH 07/49] Translated using Weblate (Polish) Currently translated at 15.1% (10 of 66 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/pl/ --- fastlane/metadata/android/pl-PL/changelogs/4211204.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/pl-PL/changelogs/4211204.txt diff --git a/fastlane/metadata/android/pl-PL/changelogs/4211204.txt b/fastlane/metadata/android/pl-PL/changelogs/4211204.txt new file mode 100644 index 000000000..da160403c --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* Naprawienie wyłączania wyciszenia rozmowy przy przełączaniu urządzeń wyjściowych +* Wyłączenie wszystkich urządzeń Umidigi z integracji rozmów From 6be393d74143cd33c7c13e449f28725141f4e1d9 Mon Sep 17 00:00:00 2001 From: Besnik_b Date: Thu, 23 May 2024 08:53:27 +0000 Subject: [PATCH 08/49] Translated using Weblate (Albanian) Currently translated at 100.0% (66 of 66 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/sq/ --- fastlane/metadata/android/sq/changelogs/4211104.txt | 3 +++ fastlane/metadata/android/sq/changelogs/4211204.txt | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 fastlane/metadata/android/sq/changelogs/4211104.txt create mode 100644 fastlane/metadata/android/sq/changelogs/4211204.txt diff --git a/fastlane/metadata/android/sq/changelogs/4211104.txt b/fastlane/metadata/android/sq/changelogs/4211104.txt new file mode 100644 index 000000000..ac42b4e67 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/4211104.txt @@ -0,0 +1,3 @@ +* Planifikim kopjeruajtjesh periodike +* Përjashtim i krejt pajisjeve “realme”, deri te Android 11, nga integrim thirrjesh +* Përmirësime të vockla UI (flluskë mesazhi) diff --git a/fastlane/metadata/android/sq/changelogs/4211204.txt b/fastlane/metadata/android/sq/changelogs/4211204.txt new file mode 100644 index 000000000..2b20cd394 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* Ndreqje çheshtimi thirrjeje, kur ndërrohet pajisje dëgjimi +* Përjashtim i krejt pajisjeve Umidigi nga integrim thirrjesh From b77f052a1c1530227b12453bfbbe133ad8dcc925 Mon Sep 17 00:00:00 2001 From: ghose Date: Thu, 23 May 2024 03:47:03 +0000 Subject: [PATCH 09/49] Translated using Weblate (Galician) Currently translated at 57.5% (38 of 66 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/gl/ --- fastlane/metadata/android/gl-ES/changelogs/4211204.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/gl-ES/changelogs/4211204.txt diff --git a/fastlane/metadata/android/gl-ES/changelogs/4211204.txt b/fastlane/metadata/android/gl-ES/changelogs/4211204.txt new file mode 100644 index 000000000..332791c8c --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* Arranxo da chamada que deixa de estar silenciada ao cambiar o dispositivo de saída +* Exclusión da integración de chamadas para todos os dispositivos Umidigi From 4b15ff9528406bec5926a2514c7ba28c6a7d6782 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Thu, 23 May 2024 08:50:28 +0000 Subject: [PATCH 10/49] Translated using Weblate (Ukrainian) Currently translated at 100.0% (66 of 66 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/uk/ --- fastlane/metadata/android/uk/changelogs/4211204.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/4211204.txt diff --git a/fastlane/metadata/android/uk/changelogs/4211204.txt b/fastlane/metadata/android/uk/changelogs/4211204.txt new file mode 100644 index 000000000..76b030cf6 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* Виправлено ввімкнення звуку виклику при перемиканні пристроїв виводу +* Виключення всіх пристроїв Umidigi з інтеграції викликів From a5e5bcef2c2095848cb6c1b0da3148d4770cdadc Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Wed, 22 May 2024 13:09:34 +0000 Subject: [PATCH 11/49] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (66 of 66 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/zh_Hans/ --- fastlane/metadata/android/zh-CN/changelogs/42037.txt | 6 +++--- fastlane/metadata/android/zh-CN/changelogs/4211104.txt | 2 +- fastlane/metadata/android/zh-CN/changelogs/4211204.txt | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 fastlane/metadata/android/zh-CN/changelogs/4211204.txt diff --git a/fastlane/metadata/android/zh-CN/changelogs/42037.txt b/fastlane/metadata/android/zh-CN/changelogs/42037.txt index bde374945..9af497aab 100644 --- a/fastlane/metadata/android/zh-CN/changelogs/42037.txt +++ b/fastlane/metadata/android/zh-CN/changelogs/42037.txt @@ -1,11 +1,11 @@ -版本2.10.9 +版本 2.10.9 * 进行音视频通话时请求蓝牙权限(如果您不使用蓝牙耳机可以拒绝) * 修复呼叫 Movim 时的错误 -* 修复群组聊天的显示错误头像的问题 +* 修复群聊显示错误头像的问题 * 始终要求选择退出电池优化 * 在“x 个已连接账号”通知上设置仅本地标志 * 修复与 Google 地图分享位置插件的交互 * 移除有关服务器费用的脚注 * 将文件存储在适合 Android 11 的位置 * 网络切换后尝试重新连接通话 -* 在来电屏幕中显示来电者JID和帐户JID +* 在来电屏幕中显示来电者 JID 和账号JID diff --git a/fastlane/metadata/android/zh-CN/changelogs/4211104.txt b/fastlane/metadata/android/zh-CN/changelogs/4211104.txt index aa433554d..6ae5e9f8e 100644 --- a/fastlane/metadata/android/zh-CN/changelogs/4211104.txt +++ b/fastlane/metadata/android/zh-CN/changelogs/4211104.txt @@ -1,3 +1,3 @@ * 计划定期备份 -* 将所有 Android 11 以下的 realme 设备排除在呼叫集成之外 +* 从呼叫集成中排除所有 Android 11 以下的 realme 设备 * 用户界面(消息气泡)小幅改进 diff --git a/fastlane/metadata/android/zh-CN/changelogs/4211204.txt b/fastlane/metadata/android/zh-CN/changelogs/4211204.txt new file mode 100644 index 000000000..e3a4d2839 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* 修复切换输出设备时呼叫未静音的问题 +* 从呼叫集成中排除所有 Umidigi 设备 From cb305c336b0da7247929482120c8582c4d164637 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Thu, 23 May 2024 13:15:01 +0000 Subject: [PATCH 12/49] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1020 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/uk/ --- src/main/res/values-uk/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 3ddda15f3..de997fcb0 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -313,7 +313,7 @@ Деталі облікового запису Підтвердити Спробуйте ще - Фонова служба + Процес на передньому плані Не дає операційній системі припиняти Ваш зв\'язок Створити резервну копію Резервні копії зберігатимуться до %s From c1a01a2f04e0027829b21baf4f4ad1df05748f3e Mon Sep 17 00:00:00 2001 From: SomeTr Date: Thu, 23 May 2024 13:18:21 +0000 Subject: [PATCH 13/49] Translated using Weblate (Ukrainian) Currently translated at 100.0% (67 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/uk/ --- fastlane/metadata/android/uk/changelogs/4211304.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/uk/changelogs/4211304.txt diff --git a/fastlane/metadata/android/uk/changelogs/4211304.txt b/fastlane/metadata/android/uk/changelogs/4211304.txt new file mode 100644 index 000000000..e50a9572a --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4211304.txt @@ -0,0 +1 @@ +* Резервне копіювання запускається як процес на передньому плані, щоб запобігти його зупинці через 10 хвилин From 81c220f5c3ca4c3f176bf327340a6e5d998b5b46 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Thu, 23 May 2024 12:05:41 +0000 Subject: [PATCH 14/49] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (67 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/zh_Hans/ --- fastlane/metadata/android/zh-CN/changelogs/4211304.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/zh-CN/changelogs/4211304.txt diff --git a/fastlane/metadata/android/zh-CN/changelogs/4211304.txt b/fastlane/metadata/android/zh-CN/changelogs/4211304.txt new file mode 100644 index 000000000..fe176e4bf --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4211304.txt @@ -0,0 +1 @@ +* 作为前台服务运行备份,防止进程在 10 分钟后停止 From 4406ac930ecbff7102d579925ec8c8ba8365452c Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Fri, 24 May 2024 06:00:21 +0000 Subject: [PATCH 15/49] Translated using Weblate (Polish) Currently translated at 16.4% (11 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/pl/ --- fastlane/metadata/android/pl-PL/changelogs/4211304.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/pl-PL/changelogs/4211304.txt diff --git a/fastlane/metadata/android/pl-PL/changelogs/4211304.txt b/fastlane/metadata/android/pl-PL/changelogs/4211304.txt new file mode 100644 index 000000000..318c2f312 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4211304.txt @@ -0,0 +1 @@ +* Uruchamianie kopii zapasowej jako usługi na pierwszym planie, żeby proces nie był zatrzymywany po 10 minutach From ae516274b367e49e39ac9ac5669899ab165c5565 Mon Sep 17 00:00:00 2001 From: ghose Date: Fri, 24 May 2024 02:47:29 +0000 Subject: [PATCH 16/49] Translated using Weblate (Galician) Currently translated at 58.2% (39 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/gl/ --- fastlane/metadata/android/gl-ES/changelogs/4211304.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/gl-ES/changelogs/4211304.txt diff --git a/fastlane/metadata/android/gl-ES/changelogs/4211304.txt b/fastlane/metadata/android/gl-ES/changelogs/4211304.txt new file mode 100644 index 000000000..7ca805abb --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/4211304.txt @@ -0,0 +1 @@ +* Crear a Copia de Apoio usando o servizo en primeiro plano para evitar que sexa detido após 10 minutos From 8d3a7dda52a6148a3b59480316a28dc5e3c6fc3d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 24 May 2024 08:19:42 +0200 Subject: [PATCH 17/49] exclude Lenovo Smart Tab YT-X705F from call integration --- .../eu/siacs/conversations/services/CallIntegration.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index dbe223599..121c2cae6 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegration.java @@ -42,8 +42,12 @@ public class CallIntegration extends Connection { * *

Samsung Galaxy Tab A claims to have FEATURE_CONNECTION_SERVICE but then throws * SecurityException when invoking placeCall(). Both Stock and LineageOS have this problem. + * + *

Lenovo Yoga Smart Tab YT-X705F claims to have FEATURE_CONNECTION_SERVICE but throws + * SecurityException */ - private static final List BROKEN_DEVICE_MODELS = Arrays.asList("OnePlus6", "gtaxlwifi"); + private static final List BROKEN_DEVICE_MODELS = + Arrays.asList("OnePlus6", "gtaxlwifi", "YT-X705F"); public static final int DEFAULT_TONE_VOLUME = 60; private static final int DEFAULT_MEDIA_PLAYER_VOLUME = 90; From fd55e637699e0e1104ff70867c7b1a9e1bcb8a70 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 23 May 2024 13:59:33 +0200 Subject: [PATCH 18/49] provide alternative api for XMPP extensions --- build.gradle | 4 + libs/annotation-processor/build.gradle | 20 + .../processor/XmlElementProcessor.java | 185 ++++ libs/annotation/build.gradle | 6 + .../android/annotation/XmlElement.java | 15 + .../android/annotation/XmlPackage.java | 12 + proguard-rules.pro | 1 + settings.gradle | 2 + .../services/PushManagementService.java | 11 +- .../crypto/axolotl/AxolotlService.java | 375 ++++---- .../entities/ServiceDiscoveryResult.java | 36 +- .../conversations/generator/IqGenerator.java | 162 ++-- .../generator/MessageGenerator.java | 92 +- .../generator/PresenceGenerator.java | 39 +- .../conversations/http/SlotRequester.java | 14 +- .../conversations/parser/AbstractParser.java | 14 +- .../siacs/conversations/parser/IqParser.java | 82 +- .../conversations/parser/MessageParser.java | 68 +- .../conversations/parser/PresenceParser.java | 17 +- .../services/ChannelDiscoveryService.java | 48 +- .../services/MessageArchiveService.java | 20 +- .../services/UnifiedPushBroker.java | 15 +- .../services/XmppConnectionService.java | 824 ++++++++---------- .../siacs/conversations/ui/XmppActivity.java | 11 +- .../eu/siacs/conversations/xml/Element.java | 17 +- .../conversations/xml/LocalizedContent.java | 2 +- .../eu/siacs/conversations/xml/Namespace.java | 49 +- .../eu/siacs/conversations/xml/TagWriter.java | 15 +- .../eu/siacs/conversations/xml/XmlReader.java | 25 +- .../siacs/conversations/xmpp/InvalidJid.java | 6 +- .../xmpp/OnIqPacketReceived.java | 8 - .../xmpp/OnMessagePacketReceived.java | 7 +- .../xmpp/OnPresencePacketReceived.java | 8 - .../conversations/xmpp/PacketReceived.java | 5 - .../conversations/xmpp/XmppConnection.java | 711 ++++++++------- .../xmpp/jingle/AbstractContentMap.java | 10 +- .../xmpp/jingle/AbstractJingleConnection.java | 49 +- .../xmpp/jingle/FileTransferContentMap.java | 5 +- .../conversations/xmpp/jingle/IceServers.java | 6 +- .../xmpp/jingle/JingleConnectionManager.java | 60 +- .../jingle/JingleFileTransferConnection.java | 166 ++-- .../xmpp/jingle/JingleRtpConnection.java | 191 ++-- .../xmpp/jingle/OnJinglePacketReceived.java | 7 +- .../xmpp/jingle/RtpContentMap.java | 6 +- .../stanzas/FileTransferDescription.java | 11 +- .../InbandBytestreamsTransport.java | 14 +- .../transports/SocksByteStreamsTransport.java | 21 +- .../WebRTCDataChannelTransport.java | 8 +- .../xmpp/pep/PublishOptions.java | 6 +- .../AbstractAcknowledgeableStanza.java | 42 - .../xmpp/stanzas/AbstractStanza.java | 53 -- .../conversations/xmpp/stanzas/IqPacket.java | 75 -- .../xmpp/stanzas/MessagePacket.java | 100 --- .../xmpp/stanzas/PresencePacket.java | 8 - .../xmpp/stanzas/csi/ActivePacket.java | 11 - .../xmpp/stanzas/csi/InactivePacket.java | 11 - .../xmpp/stanzas/streammgmt/AckPacket.java | 14 - .../xmpp/stanzas/streammgmt/EnablePacket.java | 14 - .../stanzas/streammgmt/RequestPacket.java | 13 - .../xmpp/stanzas/streammgmt/ResumePacket.java | 15 - .../im/conversations/android/xmpp/Entity.java | 34 + .../android/xmpp/EntityCapabilities.java | 133 +++ .../android/xmpp/EntityCapabilities2.java | 185 ++++ .../android/xmpp/ExtensionFactory.java | 78 ++ .../android/xmpp/NodeConfiguration.java | 112 +++ .../im/conversations/android/xmpp/Page.java | 31 + .../im/conversations/android/xmpp/Range.java | 40 + .../android/xmpp/Timestamps.java | 44 + .../model/AuthenticationStreamFeature.java | 12 + .../android/xmpp/model/ByteContent.java | 33 + .../android/xmpp/model/DeliveryReceipt.java | 10 + .../xmpp/model/DeliveryReceiptRequest.java | 8 + .../android/xmpp/model/Extension.java | 62 ++ .../android/xmpp/model/Hash.java | 46 + .../android/xmpp/model/StreamElement.java | 8 + .../android/xmpp/model/StreamFeature.java | 8 + .../xmpp/model/addressing/Address.java | 11 + .../xmpp/model/addressing/Addresses.java | 11 + .../xmpp/model/addressing/package-info.java | 6 + .../android/xmpp/model/avatar/Data.java | 14 + .../android/xmpp/model/avatar/Info.java | 37 + .../android/xmpp/model/avatar/Metadata.java | 13 + .../android/xmpp/model/axolotl/Bundle.java | 60 ++ .../android/xmpp/model/axolotl/Device.java | 22 + .../xmpp/model/axolotl/DeviceList.java | 35 + .../model/axolotl/ECPublicKeyContent.java | 23 + .../android/xmpp/model/axolotl/Encrypted.java | 24 + .../android/xmpp/model/axolotl/Header.java | 45 + .../android/xmpp/model/axolotl/IV.java | 13 + .../xmpp/model/axolotl/IdentityKey.java | 12 + .../android/xmpp/model/axolotl/Key.java | 29 + .../android/xmpp/model/axolotl/Payload.java | 13 + .../android/xmpp/model/axolotl/PreKey.java | 21 + .../android/xmpp/model/axolotl/PreKeys.java | 12 + .../xmpp/model/axolotl/SignedPreKey.java | 21 + .../model/axolotl/SignedPreKeySignature.java | 13 + .../xmpp/model/axolotl/package-info.java | 5 + .../android/xmpp/model/bind/Bind.java | 34 + .../android/xmpp/model/bind/Jid.java | 13 + .../android/xmpp/model/bind/Resource.java | 16 + .../android/xmpp/model/bind/package-info.java | 5 + .../android/xmpp/model/bind2/Bind.java | 24 + .../android/xmpp/model/bind2/Bound.java | 11 + .../android/xmpp/model/bind2/Feature.java | 12 + .../android/xmpp/model/bind2/Inline.java | 12 + .../xmpp/model/bind2/package-info.java | 5 + .../android/xmpp/model/blocking/Block.java | 12 + .../xmpp/model/blocking/Blocklist.java | 11 + .../android/xmpp/model/blocking/Item.java | 17 + .../android/xmpp/model/blocking/Unblock.java | 12 + .../xmpp/model/blocking/package-info.java | 5 + .../xmpp/model/bookmark/Conference.java | 32 + .../xmpp/model/bookmark/Extensions.java | 12 + .../android/xmpp/model/bookmark/Nick.java | 12 + .../xmpp/model/bookmark/package-info.java | 5 + .../xmpp/model/capabilties/Capabilities.java | 43 + .../model/capabilties/EntityCapabilities.java | 39 + .../model/capabilties/LegacyCapabilities.java | 45 + .../android/xmpp/model/carbons/Enable.java | 12 + .../android/xmpp/model/carbons/Received.java | 17 + .../android/xmpp/model/carbons/Sent.java | 17 + .../xmpp/model/carbons/package-info.java | 5 + .../xmpp/model/correction/Replace.java | 24 + .../android/xmpp/model/csi/Active.java | 12 + .../xmpp/model/csi/ClientStateIndication.java | 12 + .../android/xmpp/model/csi/Inactive.java | 12 + .../android/xmpp/model/csi/package-info.java | 5 + .../android/xmpp/model/data/Data.java | 110 +++ .../android/xmpp/model/data/Field.java | 29 + .../android/xmpp/model/data/Option.java | 12 + .../android/xmpp/model/data/Value.java | 12 + .../android/xmpp/model/data/package-info.java | 5 + .../android/xmpp/model/delay/Delay.java | 30 + .../xmpp/model/disco/external/Service.java | 12 + .../xmpp/model/disco/external/Services.java | 12 + .../model/disco/external/package-info.java | 5 + .../xmpp/model/disco/info/Feature.java | 19 + .../xmpp/model/disco/info/Identity.java | 39 + .../xmpp/model/disco/info/InfoQuery.java | 38 + .../xmpp/model/disco/info/package-info.java | 5 + .../android/xmpp/model/disco/items/Item.java | 22 + .../xmpp/model/disco/items/ItemsQuery.java | 19 + .../xmpp/model/disco/items/package-info.java | 5 + .../android/xmpp/model/error/Condition.java | 188 ++++ .../android/xmpp/model/error/Error.java | 55 ++ .../android/xmpp/model/error/Text.java | 13 + .../android/xmpp/model/fast/Fast.java | 11 + .../android/xmpp/model/fast/Mechanism.java | 11 + .../android/xmpp/model/fast/RequestToken.java | 17 + .../android/xmpp/model/fast/Token.java | 12 + .../android/xmpp/model/fast/package-info.java | 5 + .../android/xmpp/model/forward/Forwarded.java | 18 + .../android/xmpp/model/hints/Store.java | 12 + .../xmpp/model/hints/package-info.java | 6 + .../android/xmpp/model/jabber/Body.java | 21 + .../android/xmpp/model/jabber/Priority.java | 12 + .../android/xmpp/model/jabber/Show.java | 11 + .../android/xmpp/model/jabber/Status.java | 13 + .../android/xmpp/model/jabber/Subject.java | 12 + .../android/xmpp/model/jabber/Thread.java | 12 + .../xmpp/model/jabber/package-info.java | 5 + .../android/xmpp/model/jingle/Jingle.java} | 110 +-- .../model/jingle/error/JingleCondition.java | 44 + .../xmpp/model/jingle/package-info.java | 5 + .../android/xmpp/model/jmi/Accept.java | 11 + .../android/xmpp/model/jmi/JingleMessage.java | 14 + .../android/xmpp/model/jmi/Proceed.java | 24 + .../android/xmpp/model/jmi/Propose.java | 38 + .../android/xmpp/model/jmi/Reject.java | 11 + .../android/xmpp/model/jmi/Retract.java | 11 + .../android/xmpp/model/jmi/package-info.java | 5 + .../android/xmpp/model/mam/End.java | 15 + .../android/xmpp/model/mam/Fin.java | 16 + .../android/xmpp/model/mam/Metadata.java | 20 + .../android/xmpp/model/mam/Query.java | 16 + .../android/xmpp/model/mam/Result.java | 25 + .../android/xmpp/model/mam/Start.java | 16 + .../android/xmpp/model/mam/package-info.java | 5 + .../android/xmpp/model/markers/Displayed.java | 16 + .../android/xmpp/model/markers/Markable.java | 12 + .../android/xmpp/model/markers/Received.java | 20 + .../xmpp/model/markers/package-info.java | 5 + .../android/xmpp/model/mds/Displayed.java | 12 + .../android/xmpp/model/muc/Affiliation.java | 9 + .../android/xmpp/model/muc/History.java | 20 + .../android/xmpp/model/muc/MultiUserChat.java | 12 + .../android/xmpp/model/muc/Role.java | 8 + .../android/xmpp/model/muc/package-info.java | 5 + .../android/xmpp/model/muc/user/Item.java | 58 ++ .../android/xmpp/model/muc/user/MucUser.java | 27 + .../android/xmpp/model/muc/user/Status.java | 16 + .../xmpp/model/muc/user/package-info.java | 5 + .../android/xmpp/model/nick/Nick.java | 13 + .../xmpp/model/occupant/OccupantId.java | 19 + .../android/xmpp/model/oob/OutOfBandData.java | 18 + .../android/xmpp/model/oob/URL.java | 12 + .../android/xmpp/model/oob/package-info.java | 5 + .../android/xmpp/model/pars/PreAuth.java | 17 + .../android/xmpp/model/pgp/Encrypted.java | 14 + .../android/xmpp/model/pgp/Signed.java | 15 + .../android/xmpp/model/ping/Ping.java | 13 + .../android/xmpp/model/pubsub/Item.java | 10 + .../android/xmpp/model/pubsub/Items.java | 52 ++ .../android/xmpp/model/pubsub/PubSub.java | 64 ++ .../android/xmpp/model/pubsub/Publish.java | 16 + .../xmpp/model/pubsub/PublishOptions.java | 21 + .../android/xmpp/model/pubsub/Retract.java | 20 + .../xmpp/model/pubsub/error/PubSubError.java | 19 + .../xmpp/model/pubsub/error/package-info.java | 5 + .../xmpp/model/pubsub/event/Event.java | 56 ++ .../xmpp/model/pubsub/event/Purge.java | 16 + .../xmpp/model/pubsub/event/Retract.java | 16 + .../xmpp/model/pubsub/event/package-info.java | 5 + .../xmpp/model/pubsub/owner/Configure.java | 21 + .../xmpp/model/pubsub/owner/PubSubOwner.java | 12 + .../xmpp/model/pubsub/owner/package-info.java | 5 + .../xmpp/model/pubsub/package-info.java | 5 + .../xmpp/model/reactions/Reaction.java | 17 + .../xmpp/model/reactions/Reactions.java | 36 + .../xmpp/model/reactions/package-info.java | 5 + .../android/xmpp/model/receipts/Received.java | 20 + .../android/xmpp/model/receipts/Request.java | 12 + .../xmpp/model/receipts/package-info.java | 5 + .../xmpp/model/register/Instructions.java | 10 + .../android/xmpp/model/register/Password.java | 10 + .../android/xmpp/model/register/Register.java | 21 + .../android/xmpp/model/register/Remove.java | 10 + .../android/xmpp/model/register/Username.java | 12 + .../xmpp/model/register/package-info.java | 5 + .../android/xmpp/model/roster/Group.java | 12 + .../android/xmpp/model/roster/Item.java | 61 ++ .../android/xmpp/model/roster/Query.java | 21 + .../xmpp/model/roster/package-info.java | 5 + .../android/xmpp/model/rsm/After.java | 12 + .../android/xmpp/model/rsm/Before.java | 12 + .../android/xmpp/model/rsm/Count.java | 23 + .../android/xmpp/model/rsm/First.java | 12 + .../android/xmpp/model/rsm/Last.java | 12 + .../android/xmpp/model/rsm/Max.java | 16 + .../android/xmpp/model/rsm/Set.java | 55 ++ .../android/xmpp/model/rsm/package-info.java | 5 + .../android/xmpp/model/sasl/Auth.java | 12 + .../android/xmpp/model/sasl/Mechanism.java | 12 + .../android/xmpp/model/sasl/Mechanisms.java | 29 + .../android/xmpp/model/sasl/Response.java | 12 + .../android/xmpp/model/sasl/Success.java | 13 + .../android/xmpp/model/sasl/package-info.java | 5 + .../xmpp/model/sasl2/Authenticate.java | 12 + .../xmpp/model/sasl2/Authentication.java | 30 + .../model/sasl2/AuthorizationIdentifier.java | 28 + .../android/xmpp/model/sasl2/Inline.java | 34 + .../android/xmpp/model/sasl2/Mechanism.java | 12 + .../android/xmpp/model/sasl2/Response.java | 12 + .../android/xmpp/model/sasl2/Success.java | 23 + .../xmpp/model/sasl2/package-info.java | 5 + .../android/xmpp/model/sm/Ack.java | 23 + .../android/xmpp/model/sm/Enable.java | 13 + .../android/xmpp/model/sm/Enabled.java | 35 + .../android/xmpp/model/sm/Failed.java | 17 + .../android/xmpp/model/sm/Request.java | 12 + .../android/xmpp/model/sm/Resume.java | 18 + .../android/xmpp/model/sm/Resumed.java | 18 + .../xmpp/model/sm/StreamManagement.java | 12 + .../android/xmpp/model/sm/package-info.java | 5 + .../android/xmpp/model/stanza/Iq.java | 77 ++ .../android/xmpp/model/stanza/Message.java | 64 ++ .../android/xmpp/model/stanza/Presence.java | 12 + .../android/xmpp/model/stanza/Stanza.java | 74 ++ .../xmpp/model/stanza/package-info.java | 5 + .../android/xmpp/model/state/Active.java | 11 + .../model/state/ChatStateNotification.java | 10 + .../android/xmpp/model/state/Composing.java | 11 + .../android/xmpp/model/state/Gone.java | 11 + .../android/xmpp/model/state/Inactive.java | 11 + .../android/xmpp/model/state/Paused.java | 11 + .../xmpp/model/state/package-info.java | 5 + .../android/xmpp/model/streams/Features.java | 33 + .../xmpp/model/streams/package-info.java | 5 + .../android/xmpp/model/tls/Proceed.java | 13 + .../android/xmpp/model/tls/Required.java | 11 + .../android/xmpp/model/tls/StartTls.java | 15 + .../android/xmpp/model/tls/package-info.java | 5 + .../android/xmpp/model/unique/OriginId.java | 12 + .../android/xmpp/model/unique/StanzaId.java | 21 + .../xmpp/model/unique/package-info.java | 5 + .../android/xmpp/model/upload/Get.java | 22 + .../android/xmpp/model/upload/Header.java | 16 + .../android/xmpp/model/upload/Put.java | 27 + .../android/xmpp/model/upload/Request.java | 24 + .../android/xmpp/model/upload/Slot.java | 12 + .../xmpp/model/upload/package-info.java | 5 + .../android/xmpp/model/vcard/BinaryValue.java | 13 + .../android/xmpp/model/vcard/Photo.java | 11 + .../android/xmpp/model/vcard/VCard.java | 12 + .../xmpp/model/vcard/package-info.java | 5 + .../xmpp/model/vcard/update/Photo.java | 12 + .../xmpp/model/vcard/update/VCardUpdate.java | 21 + .../xmpp/model/vcard/update/package-info.java | 5 + .../android/xmpp/model/version/Version.java | 25 + .../android/xmpp/processor/BindProcessor.java | 90 ++ .../services/PushManagementService.java | 143 +-- .../services/QuickConversationsService.java | 10 +- 302 files changed, 7037 insertions(+), 2146 deletions(-) create mode 100644 libs/annotation-processor/build.gradle create mode 100644 libs/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java create mode 100644 libs/annotation/build.gradle create mode 100644 libs/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java create mode 100644 libs/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/OnIqPacketReceived.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/OnPresencePacketReceived.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/PacketReceived.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java create mode 100644 src/main/java/im/conversations/android/xmpp/Entity.java create mode 100644 src/main/java/im/conversations/android/xmpp/EntityCapabilities.java create mode 100644 src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java create mode 100644 src/main/java/im/conversations/android/xmpp/ExtensionFactory.java create mode 100644 src/main/java/im/conversations/android/xmpp/NodeConfiguration.java create mode 100644 src/main/java/im/conversations/android/xmpp/Page.java create mode 100644 src/main/java/im/conversations/android/xmpp/Range.java create mode 100644 src/main/java/im/conversations/android/xmpp/Timestamps.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/AuthenticationStreamFeature.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/ByteContent.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/Extension.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/Hash.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/StreamElement.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/StreamFeature.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/addressing/Address.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/addressing/Addresses.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/addressing/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/avatar/Data.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/avatar/Info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/Key.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind/Bind.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind/Jid.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind/Resource.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind2/Bound.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind2/Feature.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind2/Inline.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind2/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/blocking/Block.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/blocking/Item.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bookmark/Extensions.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bookmark/Nick.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/carbons/Received.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/correction/Replace.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/csi/Active.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/csi/ClientStateIndication.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/csi/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/data/Data.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/data/Field.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/data/Option.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/data/Value.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/data/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/delay/Delay.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/external/Service.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/external/Services.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/external/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/error/Condition.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/error/Error.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/error/Text.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/fast/Fast.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/fast/Mechanism.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/fast/RequestToken.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/fast/Token.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/fast/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/hints/Store.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/hints/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/Body.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/Priority.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/Show.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/Status.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/Subject.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java rename src/main/java/{eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java => im/conversations/android/xmpp/model/jingle/Jingle.java} (62%) create mode 100644 src/main/java/im/conversations/android/xmpp/model/jingle/error/JingleCondition.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jingle/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/Accept.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/JingleMessage.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/Proceed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/Propose.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/Reject.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/Retract.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/End.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/Fin.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/Metadata.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/Query.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/Result.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/Start.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/markers/Markable.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/markers/Received.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/markers/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mds/Displayed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/History.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/MultiUserChat.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/Role.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/user/Item.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/user/MucUser.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/user/Status.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/nick/Nick.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/oob/URL.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/oob/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pgp/Encrypted.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pgp/Signed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/ping/Ping.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/receipts/Received.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/receipts/Request.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/register/Instructions.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/register/Password.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/register/Register.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/register/Remove.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/register/Username.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/register/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/roster/Group.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/roster/Item.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/roster/Query.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/roster/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/After.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/Before.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/Count.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/First.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/Last.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/Max.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/Set.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/Mechanism.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/Mechanisms.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/Response.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/Success.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Authentication.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Mechanism.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Response.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Success.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Ack.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Enable.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Enabled.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Failed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Request.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Resume.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Resumed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/StreamManagement.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/stanza/Message.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/Active.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/Composing.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/Gone.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/Inactive.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/Paused.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/streams/Features.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/streams/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/tls/Proceed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/tls/Required.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/tls/StartTls.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/tls/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/unique/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/upload/Get.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/upload/Header.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/upload/Put.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/upload/Request.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/upload/Slot.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/upload/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/BinaryValue.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/VCard.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/update/Photo.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/update/VCardUpdate.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/update/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/version/Version.java create mode 100644 src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java diff --git a/build.gradle b/build.gradle index beb73f787..738124702 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,10 @@ configurations { dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' + implementation project(':libs:annotation') + annotationProcessor project(':libs:annotation-processor') + + implementation 'androidx.viewpager:viewpager:1.0.0' playstoreImplementation('com.google.firebase:firebase-messaging:24.0.0') { diff --git a/libs/annotation-processor/build.gradle b/libs/annotation-processor/build.gradle new file mode 100644 index 000000000..6232f33c6 --- /dev/null +++ b/libs/annotation-processor/build.gradle @@ -0,0 +1,20 @@ +apply plugin: "java-library" + +repositories { + google() + mavenCentral() +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} +dependencies { + + implementation project(':libs:annotation') + + annotationProcessor 'com.google.auto.service:auto-service:1.0.1' + api 'com.google.auto.service:auto-service-annotations:1.0.1' + implementation 'com.google.guava:guava:31.1-jre' + +} \ No newline at end of file diff --git a/libs/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java b/libs/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java new file mode 100644 index 000000000..c42cc5340 --- /dev/null +++ b/libs/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java @@ -0,0 +1,185 @@ +package im.conversations.android.annotation.processor; + +import com.google.auto.service.AutoService; +import com.google.common.base.CaseFormat; +import com.google.common.base.Objects; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.annotation.XmlPackage; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementFilter; +import javax.tools.JavaFileObject; + +@AutoService(Processor.class) +@SupportedSourceVersion(SourceVersion.RELEASE_17) +@SupportedAnnotationTypes("im.conversations.android.annotation.XmlElement") +public class XmlElementProcessor extends AbstractProcessor { + + @Override + public boolean process(Set set, RoundEnvironment roundEnvironment) { + final Set elements = + roundEnvironment.getElementsAnnotatedWith(XmlElement.class); + final ImmutableMap.Builder builder = ImmutableMap.builder(); + for (final Element element : elements) { + if (element instanceof final TypeElement typeElement) { + final Id id = of(typeElement); + builder.put(id, typeElement.getQualifiedName().toString()); + } + } + final ImmutableMap maps = builder.build(); + if (maps.isEmpty()) { + return false; + } + final JavaFileObject extensionFile; + try { + extensionFile = + processingEnv + .getFiler() + .createSourceFile("im.conversations.android.xmpp.Extensions"); + } catch (final IOException e) { + throw new RuntimeException(e); + } + try (final PrintWriter out = new PrintWriter(extensionFile.openWriter())) { + out.println("package im.conversations.android.xmpp;"); + out.println("import com.google.common.collect.BiMap;"); + out.println("import com.google.common.collect.ImmutableBiMap;"); + out.println("import im.conversations.android.xmpp.ExtensionFactory;"); + out.println("import im.conversations.android.xmpp.model.Extension;"); + out.print("\n"); + out.println("public final class Extensions {"); + out.println( + "public static final BiMap>" + + " EXTENSION_CLASS_MAP;"); + out.println("static {"); + out.println( + "final var builder = new ImmutableBiMap.Builder>();"); + for (final Map.Entry entry : maps.entrySet()) { + Id id = entry.getKey(); + String clazz = entry.getValue(); + out.format( + "builder.put(new ExtensionFactory.Id(\"%s\",\"%s\"),%s.class);", + id.name, id.namespace, clazz); + out.print("\n"); + } + out.println("EXTENSION_CLASS_MAP = builder.build();"); + out.println("}"); + out.println(" private Extensions() {}"); + out.println("}"); + // writing generated file to out … + } catch (IOException e) { + throw new RuntimeException(e); + } + return true; + } + + private static Id of(final TypeElement typeElement) { + final XmlElement xmlElement = typeElement.getAnnotation(XmlElement.class); + final PackageElement packageElement = getPackageElement(typeElement); + final XmlPackage xmlPackage = + packageElement == null ? null : packageElement.getAnnotation(XmlPackage.class); + if (xmlElement == null) { + throw new IllegalStateException( + String.format( + "%s is not annotated as @XmlElement", + typeElement.getQualifiedName().toString())); + } + final String packageNamespace = xmlPackage == null ? null : xmlPackage.namespace(); + final String elementName = xmlElement.name(); + final String elementNamespace = xmlElement.namespace(); + final String namespace; + if (!Strings.isNullOrEmpty(elementNamespace)) { + namespace = elementNamespace; + } else if (!Strings.isNullOrEmpty(packageNamespace)) { + namespace = packageNamespace; + } else { + throw new IllegalStateException( + String.format( + "%s does not declare a namespace", + typeElement.getQualifiedName().toString())); + } + if (!hasEmptyDefaultConstructor(typeElement)) { + throw new IllegalStateException( + String.format( + "%s does not have an empty default constructor", + typeElement.getQualifiedName().toString())); + } + final String name; + if (Strings.isNullOrEmpty(elementName)) { + name = + CaseFormat.UPPER_CAMEL.to( + CaseFormat.LOWER_HYPHEN, typeElement.getSimpleName().toString()); + } else { + name = elementName; + } + return new Id(name, namespace); + } + + private static PackageElement getPackageElement(final TypeElement typeElement) { + final Element parent = typeElement.getEnclosingElement(); + if (parent instanceof PackageElement) { + return (PackageElement) parent; + } else { + final Element nextParent = parent.getEnclosingElement(); + if (nextParent instanceof PackageElement) { + return (PackageElement) nextParent; + } else { + return null; + } + } + } + + private static boolean hasEmptyDefaultConstructor(final TypeElement typeElement) { + final List constructors = + ElementFilter.constructorsIn(typeElement.getEnclosedElements()); + for (final ExecutableElement constructor : constructors) { + if (constructor.getParameters().isEmpty() + && constructor.getModifiers().contains(Modifier.PUBLIC)) { + return true; + } + } + return false; + } + + public static class Id { + public final String name; + public final String namespace; + + public Id(String name, String namespace) { + this.name = name; + this.namespace = namespace; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Id id = (Id) o; + return Objects.equal(name, id.name) && Objects.equal(namespace, id.namespace); + } + + @Override + public int hashCode() { + return Objects.hashCode(name, namespace); + } + } +} diff --git a/libs/annotation/build.gradle b/libs/annotation/build.gradle new file mode 100644 index 000000000..13a27e90c --- /dev/null +++ b/libs/annotation/build.gradle @@ -0,0 +1,6 @@ +apply plugin: "java-library" + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} \ No newline at end of file diff --git a/libs/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java b/libs/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java new file mode 100644 index 000000000..68ff73635 --- /dev/null +++ b/libs/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java @@ -0,0 +1,15 @@ +package im.conversations.android.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE}) +public @interface XmlElement { + + String name() default ""; + + String namespace() default ""; +} diff --git a/libs/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java b/libs/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java new file mode 100644 index 000000000..462fc6965 --- /dev/null +++ b/libs/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java @@ -0,0 +1,12 @@ +package im.conversations.android.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.PACKAGE) +public @interface XmlPackage { + String namespace(); +} diff --git a/proguard-rules.pro b/proguard-rules.pro index 389e75904..c23fd72ae 100644 --- a/proguard-rules.pro +++ b/proguard-rules.pro @@ -1,6 +1,7 @@ -dontobfuscate -keep class eu.siacs.conversations.** +-keep class im.conversations.** -keep class org.whispersystems.** diff --git a/settings.gradle b/settings.gradle index 4193570fa..3cecbc889 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,3 @@ +include ':libs:annotation', ':libs:annotation-processor:' + rootProject.name = 'Conversations' diff --git a/src/free/java/eu/siacs/conversations/services/PushManagementService.java b/src/free/java/eu/siacs/conversations/services/PushManagementService.java index f436da434..c6c5d2324 100644 --- a/src/free/java/eu/siacs/conversations/services/PushManagementService.java +++ b/src/free/java/eu/siacs/conversations/services/PushManagementService.java @@ -1,7 +1,6 @@ package eu.siacs.conversations.services; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Conversation; public class PushManagementService { @@ -11,11 +10,7 @@ public PushManagementService(XmppConnectionService service) { this.mXmppConnectionService = service; } - void registerPushTokenOnServer(Account account) { - //stub implementation. only affects playstore flavor - } - - void unregisterChannel(Account account, String hash) { + public void registerPushTokenOnServer(Account account) { //stub implementation. only affects playstore flavor } @@ -26,8 +21,4 @@ public boolean available(Account account) { public boolean isStub() { return true; } - - public boolean availableAndUseful(Account account) { - return false; - } } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 3721f4cfe..b1ecda3c2 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -61,7 +61,6 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; -import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jingle.DescriptionTransport; import eu.siacs.conversations.xmpp.jingle.OmemoVerification; import eu.siacs.conversations.xmpp.jingle.OmemoVerifiedRtpContentMap; @@ -70,8 +69,7 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.pep.PublishOptions; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import im.conversations.android.xmpp.model.stanza.Iq; public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { @@ -392,20 +390,18 @@ private void publishOwnDeviceIdIfNeeded() { Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... "); return; } - IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid()); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { - Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids."); - } else { - //TODO consider calling registerDevices only after item-not-found to account for broken PEPs - Element item = mXmppConnectionService.getIqParser().getItem(packet); - Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds); - registerDevices(account.getJid().asBareJid(), deviceIds); - } + Iq packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid()); + mXmppConnectionService.sendIqPacket(account, packet, response -> { + if (packet.getType() == Iq.Type.TIMEOUT) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids."); + } else { + //TODO consider calling registerDevices only after item-not-found to account for broken PEPs + final Element item = IqParser.getItem(packet); + final Set deviceIds = IqParser.deviceIds(item); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds); + registerDevices(account.getJid().asBareJid(), deviceIds); } + }); } @@ -455,40 +451,37 @@ private void publishDeviceIdsAndRefineAccessModel(Set ids) { private void publishDeviceIdsAndRefineAccessModel(final Set ids, final boolean firstAttempt) { final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null; - IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions); - mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - final Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null; - final boolean preConditionNotMet = PublishOptions.preconditionNotMet(packet); - if (firstAttempt && preConditionNotMet) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for device list. pushing node configuration"); - mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() { - @Override - public void onPushSucceeded() { - publishDeviceIdsAndRefineAccessModel(ids, false); - } - - @Override - public void onPushFailed() { - publishDeviceIdsAndRefineAccessModel(ids, false); - } - }); - } else { - if (AxolotlService.this.changeAccessMode.compareAndSet(true, false)) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": done changing access mode"); - account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false); - mXmppConnectionService.databaseBackend.updateAccount(account); + final var publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions); + mXmppConnectionService.sendIqPacket(account, publish, response -> { + final Element error = response.getType() == Iq.Type.ERROR ? response.findChild("error") : null; + final boolean preConditionNotMet = PublishOptions.preconditionNotMet(response); + if (firstAttempt && preConditionNotMet) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for device list. pushing node configuration"); + mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() { + @Override + public void onPushSucceeded() { + publishDeviceIdsAndRefineAccessModel(ids, false); } - if (packet.getType() == IqPacket.TYPE.ERROR) { - if (preConditionNotMet) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": device list pre condition still not met on second attempt"); - } else if (error != null) { - pepBroken = true; - Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error")); - } + @Override + public void onPushFailed() { + publishDeviceIdsAndRefineAccessModel(ids, false); } + }); + } else { + if (AxolotlService.this.changeAccessMode.compareAndSet(true, false)) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": done changing access mode"); + account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false); + mXmppConnectionService.databaseBackend.updateAccount(account); + } + if (response.getType() == Iq.Type.ERROR) { + if (preConditionNotMet) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": device list pre condition still not met on second attempt"); + } else if (error != null) { + pepBroken = true; + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + response.findChild("error")); + } + } } }); @@ -506,26 +499,23 @@ public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPr verifier.initSign(x509PrivateKey, SECURE_RANDOM); verifier.update(axolotlPublicKey.serialize()); byte[] signature = verifier.sign(); - IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId()); + final Iq packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId()); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device " + getOwnDeviceId()); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, IqPacket packet) { - String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId(); - mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() { - @Override - public void onPushSucceeded() { - Log.d(Config.LOGTAG, getLogprefix(account) + "configured verification node to be world readable"); - publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe); - } + mXmppConnectionService.sendIqPacket(account, packet, response -> { + String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId(); + mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() { + @Override + public void onPushSucceeded() { + Log.d(Config.LOGTAG, getLogprefix(account) + "configured verification node to be world readable"); + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe); + } - @Override - public void onPushFailed() { - Log.d(Config.LOGTAG, getLogprefix(account) + "unable to set access model on verification node"); - publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe); - } - }); - } + @Override + public void onPushFailed() { + Log.d(Config.LOGTAG, getLogprefix(account) + "unable to set access model on verification node"); + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe); + } + }); }); } catch (Exception e) { e.printStackTrace(); @@ -549,109 +539,106 @@ public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) { if (this.changeAccessMode.get()) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server gained publish-options capabilities. changing access model"); } - IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId()); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { + final Iq packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, response -> { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { - return; //ignore timeout. do nothing - } + if (response.getType() == Iq.Type.TIMEOUT) { + return; //ignore timeout. do nothing + } - if (packet.getType() == IqPacket.TYPE.ERROR) { - Element error = packet.findChild("error"); - if (error == null || !error.hasChild("item-not-found")) { - pepBroken = true; - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet); - return; - } + if (response.getType() == Iq.Type.ERROR) { + Element error = response.findChild("error"); + if (error == null || !error.hasChild("item-not-found")) { + pepBroken = true; + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + response); + return; } + } - PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet); - Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); - boolean flush = false; - if (bundle == null) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet); - bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null); - flush = true; - } - if (keys == null) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet); + PreKeyBundle bundle = IqParser.bundle(response); + final Map keys = IqParser.preKeyPublics(response); + boolean flush = false; + if (bundle == null) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + response); + bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null); + flush = true; + } + if (keys == null) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + response); + } + try { + boolean changed = false; + // Validate IdentityKey + IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair(); + if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); + changed = true; } - try { - boolean changed = false; - // Validate IdentityKey - IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair(); - if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); - changed = true; - } - // Validate signedPreKeyRecord + ID - SignedPreKeyRecord signedPreKeyRecord; - int numSignedPreKeys = axolotlStore.getSignedPreKeysCount(); - try { - signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId()); - if (flush - || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) - || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); - signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); - axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); - changed = true; - } - } catch (InvalidKeyIdException e) { + // Validate signedPreKeyRecord + ID + SignedPreKeyRecord signedPreKeyRecord; + int numSignedPreKeys = axolotlStore.getSignedPreKeysCount(); + try { + signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId()); + if (flush + || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) + || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) { Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); changed = true; } + } catch (InvalidKeyIdException e) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); + axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); + changed = true; + } - // Validate PreKeys - Set preKeyRecords = new HashSet<>(); - if (keys != null) { - for (Integer id : keys.keySet()) { - try { - PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id); - if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) { - preKeyRecords.add(preKeyRecord); - } - } catch (InvalidKeyIdException ignored) { + // Validate PreKeys + Set preKeyRecords = new HashSet<>(); + if (keys != null) { + for (Integer id : keys.keySet()) { + try { + PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id); + if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) { + preKeyRecords.add(preKeyRecord); } + } catch (InvalidKeyIdException ignored) { } } - int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size(); - if (newKeys > 0) { - List newRecords = KeyHelper.generatePreKeys( - axolotlStore.getCurrentPreKeyId() + 1, newKeys); - preKeyRecords.addAll(newRecords); - for (PreKeyRecord record : newRecords) { - axolotlStore.storePreKey(record.getId(), record); - } - changed = true; - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP."); + } + int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size(); + if (newKeys > 0) { + List newRecords = KeyHelper.generatePreKeys( + axolotlStore.getCurrentPreKeyId() + 1, newKeys); + preKeyRecords.addAll(newRecords); + for (PreKeyRecord record : newRecords) { + axolotlStore.storePreKey(record.getId(), record); } + changed = true; + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP."); + } - if (changed || changeAccessMode.get()) { - if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) { - mXmppConnectionService.publishDisplayName(account); - publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); - } else { - publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); - } + if (changed || changeAccessMode.get()) { + if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) { + mXmppConnectionService.publishDisplayName(account); + publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); } else { - Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current"); - if (wipe) { - wipeOtherPepDevices(); - } else if (announce) { - Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); - publishOwnDeviceIdIfNeeded(); - } + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); + } + } else { + Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current"); + if (wipe) { + wipeOtherPepDevices(); + } else if (announce) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); + publishOwnDeviceIdIfNeeded(); } - } catch (InvalidKeyException e) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); } + } catch (InvalidKeyException e) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); } }); } @@ -669,44 +656,41 @@ private void publishDeviceBundle(final SignedPreKeyRecord signedPreKeyRecord, final boolean wipe, final boolean firstAttempt) { final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null; - final IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( + final Iq publish = mXmppConnectionService.getIqGenerator().publishBundles( signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), preKeyRecords, getOwnDeviceId(), publishOptions); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing..."); - mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, IqPacket packet) { - final boolean preconditionNotMet = PublishOptions.preconditionNotMet(packet); - if (firstAttempt && preconditionNotMet) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration"); - final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId(); - mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() { - @Override - public void onPushSucceeded() { - publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false); - } - - @Override - public void onPushFailed() { - publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false); - } - }); - } else if (packet.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. "); - if (wipe) { - wipeOtherPepDevices(); - } else if (announceAfter) { - Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); - publishOwnDeviceIdIfNeeded(); + mXmppConnectionService.sendIqPacket(account, publish, response -> { + final boolean preconditionNotMet = PublishOptions.preconditionNotMet(response); + if (firstAttempt && preconditionNotMet) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration"); + final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId(); + mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() { + @Override + public void onPushSucceeded() { + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false); } - } else if (packet.getType() == IqPacket.TYPE.ERROR) { - if (preconditionNotMet) { - Log.d(Config.LOGTAG, getLogprefix(account) + "bundle precondition still not met after second attempt"); - } else { - Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.toString()); + + @Override + public void onPushFailed() { + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false); } - pepBroken = true; + }); + } else if (response.getType() == Iq.Type.RESULT) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. "); + if (wipe) { + wipeOtherPepDevices(); + } else if (announceAfter) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); + publishOwnDeviceIdIfNeeded(); + } + } else if (response.getType() == Iq.Type.ERROR) { + if (preconditionNotMet) { + Log.d(Config.LOGTAG, getLogprefix(account) + "bundle precondition still not met after second attempt"); + } else { + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + response.toString()); } + pepBroken = true; } }); } @@ -759,9 +743,9 @@ private ListenableFuture verifySessionWithPEP(final XmppAxol return Futures.immediateFuture(session); } final SettableFuture future = SettableFuture.create(); - final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId()); - mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> { - Pair verification = mXmppConnectionService.getIqParser().verification(response); + final Iq packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, (response) -> { + Pair verification = IqParser.verification(response); if (verification != null) { try { Signature verifier = Signature.getInstance("sha256WithRSA"); @@ -846,7 +830,7 @@ public void fetchDeviceIds(final Jid jid) { } private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) { - IqPacket packet; + final Iq packet; synchronized (this.fetchDeviceIdsMap) { List callbacks = this.fetchDeviceIdsMap.get(jid); if (callbacks != null) { @@ -866,11 +850,11 @@ private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) { } } if (packet != null) { - mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + mXmppConnectionService.sendIqPacket(account, packet, (response) -> { + if (response.getType() == Iq.Type.RESULT) { fetchDeviceListStatus.put(jid, true); - Element item = mXmppConnectionService.getIqParser().getItem(response); - Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + final Element item = IqParser.getItem(response); + final Set deviceIds = IqParser.deviceIds(item); registerDevices(jid, deviceIds); final List callbacks; synchronized (fetchDeviceIdsMap) { @@ -882,7 +866,7 @@ private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) { } } } else { - if (response.getType() == IqPacket.TYPE.TIMEOUT) { + if (response.getType() == Iq.Type.TIMEOUT) { fetchDeviceListStatus.remove(jid); } else { fetchDeviceListStatus.put(jid, false); @@ -929,16 +913,15 @@ private ListenableFuture buildSessionFromPEP(final SignalPro } final Jid jid = Jid.of(address.getName()); final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid()); - IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId()); - mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + final Iq bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId()); + mXmppConnectionService.sendIqPacket(account, bundlesPacket, (packet) -> { + if (packet.getType() == Iq.Type.TIMEOUT) { fetchStatusMap.put(address, FetchStatus.TIMEOUT); sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout")); - } else if (packet.getType() == IqPacket.TYPE.RESULT) { + } else if (packet.getType() == Iq.Type.RESULT) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing..."); - final IqParser parser = mXmppConnectionService.getIqParser(); - final List preKeyBundleList = parser.preKeys(packet); - final PreKeyBundle bundle = parser.bundle(packet); + final List preKeyBundleList = IqParser.preKeys(packet); + final PreKeyBundle bundle = IqParser.bundle(packet); if (preKeyBundleList.isEmpty() || bundle == null) { Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet); fetchStatusMap.put(address, FetchStatus.ERROR); @@ -1544,7 +1527,7 @@ private void completeSession(XmppAxolotlSession session) { axolotlMessage.addDevice(session, true); try { final Jid jid = Jid.of(session.getRemoteAddress().getName()); - MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage); + final var packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage); mXmppConnectionService.sendMessagePacket(account, packet); } catch (IllegalArgumentException e) { throw new Error("Remote addresses are created from jid and should convert back to jid", e); diff --git a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java index 8c63a2670..3f2d2a5ad 100644 --- a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java +++ b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java @@ -24,7 +24,7 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Field; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; public class ServiceDiscoveryResult { public static final String TABLENAME = "discovery_results"; @@ -36,7 +36,7 @@ public class ServiceDiscoveryResult { protected final List features; protected final List forms; private final List identities; - public ServiceDiscoveryResult(final IqPacket packet) { + public ServiceDiscoveryResult(final Iq packet) { this.identities = new ArrayList<>(); this.features = new ArrayList<>(); this.forms = new ArrayList<>(); @@ -275,7 +275,7 @@ public ContentValues getContentValues() { return values; } - public static class Identity implements Comparable { + public static class Identity implements Comparable { protected final String type; protected final String lang; protected final String name; @@ -323,8 +323,21 @@ public String getName() { return this.name; } - public int compareTo(@NonNull Object other) { - Identity o = (Identity) other; + JSONObject toJSON() { + try { + JSONObject o = new JSONObject(); + o.put("category", this.getCategory()); + o.put("type", this.getType()); + o.put("lang", this.getLang()); + o.put("name", this.getName()); + return o; + } catch (JSONException e) { + return null; + } + } + + @Override + public int compareTo(final Identity o) { int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory())); if (r == 0) { r = blankNull(this.getType()).compareTo(blankNull(o.getType())); @@ -338,18 +351,5 @@ public int compareTo(@NonNull Object other) { return r; } - - JSONObject toJSON() { - try { - JSONObject o = new JSONObject(); - o.put("category", this.getCategory()); - o.put("type", this.getType()); - o.put("lang", this.getLang()); - o.put("name", this.getName()); - return o; - } catch (JSONException e) { - return null; - } - } } } diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index df87932e5..85e3a0d7c 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -34,7 +34,7 @@ import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.pep.Avatar; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; public class IqGenerator extends AbstractGenerator { @@ -42,8 +42,8 @@ public IqGenerator(final XmppConnectionService service) { super(service); } - public IqPacket discoResponse(final Account account, final IqPacket request) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT); + public Iq discoResponse(final Account account, final Iq request) { + final var packet = new Iq(Iq.Type.RESULT); packet.setId(request.getId()); packet.setTo(request.getFrom()); final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info"); @@ -58,8 +58,8 @@ public IqPacket discoResponse(final Account account, final IqPacket request) { return packet; } - public IqPacket versionResponse(final IqPacket request) { - final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT); + public Iq versionResponse(final Iq request) { + final var packet = request.generateResponse(Iq.Type.RESULT); Element query = packet.query("jabber:iq:version"); query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name)); query.addChild("version").setContent(getIdentityVersion()); @@ -71,8 +71,8 @@ public IqPacket versionResponse(final IqPacket request) { return packet; } - public IqPacket entityTimeResponse(IqPacket request) { - final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT); + public Iq entityTimeResponse(final Iq request) { + final Iq packet = request.generateResponse(Iq.Type.RESULT); Element time = packet.addChild("time", "urn:xmpp:time"); final long now = System.currentTimeMillis(); time.addChild("utc").setContent(getTimestamp(now)); @@ -91,14 +91,14 @@ public IqPacket entityTimeResponse(IqPacket request) { return packet; } - public IqPacket purgeOfflineMessages() { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public static Iq purgeOfflineMessages() { + final Iq packet = new Iq(Iq.Type.SET); packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge"); return packet; } - protected IqPacket publish(final String node, final Element item, final Bundle options) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + protected Iq publish(final String node, final Element item, final Bundle options) { + final var packet = new Iq(Iq.Type.SET); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); final Element publish = pubsub.addChild("publish"); publish.setAttribute("node", node); @@ -110,12 +110,12 @@ protected IqPacket publish(final String node, final Element item, final Bundle o return packet; } - protected IqPacket publish(final String node, final Element item) { + protected Iq publish(final String node, final Element item) { return publish(node, item, null); } - private IqPacket retrieve(String node, Element item) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + private Iq retrieve(String node, Element item) { + final var packet = new Iq(Iq.Type.GET); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); final Element items = pubsub.addChild("items"); items.setAttribute("node", node); @@ -125,30 +125,30 @@ private IqPacket retrieve(String node, Element item) { return packet; } - public IqPacket retrieveBookmarks() { + public Iq retrieveBookmarks() { return retrieve(Namespace.BOOKMARKS2, null); } - public IqPacket retrieveMds() { + public Iq retrieveMds() { return retrieve(Namespace.MDS_DISPLAYED, null); } - public IqPacket publishNick(String nick) { + public Iq publishNick(String nick) { final Element item = new Element("item"); item.setAttribute("id", "current"); item.addChild("nick", Namespace.NICK).setContent(nick); return publish(Namespace.NICK, item); } - public IqPacket deleteNode(final String node) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq deleteNode(final String node) { + final var packet = new Iq(Iq.Type.SET); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER); pubsub.addChild("delete").setAttribute("node", node); return packet; } - public IqPacket deleteItem(final String node, final String id) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq deleteItem(final String node, final String id) { + final var packet = new Iq(Iq.Type.SET); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); final Element retract = pubsub.addChild("retract"); retract.setAttribute("node", node); @@ -157,7 +157,7 @@ public IqPacket deleteItem(final String node, final String id) { return packet; } - public IqPacket publishAvatar(Avatar avatar, Bundle options) { + public Iq publishAvatar(Avatar avatar, Bundle options) { final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); final Element data = item.addChild("data", Namespace.AVATAR_DATA); @@ -165,14 +165,14 @@ public IqPacket publishAvatar(Avatar avatar, Bundle options) { return publish(Namespace.AVATAR_DATA, item, options); } - public IqPacket publishElement(final String namespace, final Element element, String id, final Bundle options) { + public Iq publishElement(final String namespace, final Element element, String id, final Bundle options) { final Element item = new Element("item"); item.setAttribute("id", id); item.addChild(element); return publish(namespace, item, options); } - public IqPacket publishAvatarMetadata(final Avatar avatar, final Bundle options) { + public Iq publishAvatarMetadata(final Avatar avatar, final Bundle options) { final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); final Element metadata = item @@ -186,57 +186,57 @@ public IqPacket publishAvatarMetadata(final Avatar avatar, final Bundle options) return publish(Namespace.AVATAR_METADATA, item, options); } - public IqPacket retrievePepAvatar(final Avatar avatar) { + public Iq retrievePepAvatar(final Avatar avatar) { final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); - final IqPacket packet = retrieve(Namespace.AVATAR_DATA, item); + final var packet = retrieve(Namespace.AVATAR_DATA, item); packet.setTo(avatar.owner); return packet; } - public IqPacket retrieveVcardAvatar(final Avatar avatar) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq retrieveVcardAvatar(final Avatar avatar) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(avatar.owner); packet.addChild("vCard", "vcard-temp"); return packet; } - public IqPacket retrieveVcardAvatar(final Jid to) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq retrieveVcardAvatar(final Jid to) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(to); packet.addChild("vCard", "vcard-temp"); return packet; } - public IqPacket retrieveAvatarMetaData(final Jid to) { - final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null); + public Iq retrieveAvatarMetaData(final Jid to) { + final Iq packet = retrieve("urn:xmpp:avatar:metadata", null); if (to != null) { packet.setTo(to); } return packet; } - public IqPacket retrieveDeviceIds(final Jid to) { - final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null); + public Iq retrieveDeviceIds(final Jid to) { + final var packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null); if (to != null) { packet.setTo(to); } return packet; } - public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) { - final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null); + public Iq retrieveBundlesForDevice(final Jid to, final int deviceid) { + final var packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null); packet.setTo(to); return packet; } - public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) { - final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null); + public Iq retrieveVerificationForDevice(final Jid to, final int deviceid) { + final var packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null); packet.setTo(to); return packet; } - public IqPacket publishDeviceIds(final Set ids, final Bundle publishOptions) { + public Iq publishDeviceIds(final Set ids, final Bundle publishOptions) { final Element item = new Element("item"); item.setAttribute("id", "current"); final Element list = item.addChild("list", AxolotlService.PEP_PREFIX); @@ -286,7 +286,7 @@ private Element mdsDisplayed(final String stanzaId, final Jid by) { return displayed; } - public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, + public Iq publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, final Set preKeyRecords, final int deviceId, Bundle publishOptions) { final Element item = new Element("item"); item.setAttribute("id", "current"); @@ -310,7 +310,7 @@ public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, fina return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions); } - public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) { + public Iq publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) { final Element item = new Element("item"); item.setAttribute("id", "current"); final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX); @@ -328,8 +328,8 @@ public IqPacket publishVerification(byte[] signature, X509Certificate[] certific return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item); } - public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq queryMessageArchiveManagement(final MessageArchiveService.Query mam) { + final Iq packet = new Iq(Iq.Type.SET); final Element query = packet.query(mam.version.namespace); query.setAttribute("queryid", mam.getQueryId()); final Data data = new Data(); @@ -359,15 +359,15 @@ public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query return packet; } - public IqPacket generateGetBlockList() { - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + public Iq generateGetBlockList() { + final Iq iq = new Iq(Iq.Type.GET); iq.addChild("blocklist", Namespace.BLOCKING); return iq; } - public IqPacket generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) { - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + public Iq generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) { + final Iq iq = new Iq(Iq.Type.SET); final Element block = iq.addChild("block", Namespace.BLOCKING); final Element item = block.addChild("item").setAttribute("jid", jid); if (reportSpam) { @@ -383,15 +383,15 @@ public IqPacket generateSetBlockRequest(final Jid jid, final boolean reportSpam, return iq; } - public IqPacket generateSetUnblockRequest(final Jid jid) { - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + public Iq generateSetUnblockRequest(final Jid jid) { + final Iq iq = new Iq(Iq.Type.SET); final Element block = iq.addChild("unblock", Namespace.BLOCKING); block.addChild("item").setAttribute("jid", jid); return iq; } - public IqPacket generateSetPassword(final Account account, final String newPassword) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq generateSetPassword(final Account account, final String newPassword) { + final Iq packet = new Iq(Iq.Type.SET); packet.setTo(account.getDomain()); final Element query = packet.addChild("query", Namespace.REGISTER); final Jid jid = account.getJid(); @@ -400,14 +400,14 @@ public IqPacket generateSetPassword(final Account account, final String newPassw return packet; } - public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) { + public Iq changeAffiliation(Conversation conference, Jid jid, String affiliation) { List jids = new ArrayList<>(); jids.add(jid); return changeAffiliation(conference, jids, affiliation); } - public IqPacket changeAffiliation(Conversation conference, List jids, String affiliation) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq changeAffiliation(Conversation conference, List jids, String affiliation) { + final Iq packet = new Iq(Iq.Type.SET); packet.setTo(conference.getJid().asBareJid()); packet.setFrom(conference.getAccount().getJid()); Element query = packet.query("http://jabber.org/protocol/muc#admin"); @@ -419,8 +419,8 @@ public IqPacket changeAffiliation(Conversation conference, List jids, Strin return packet; } - public IqPacket changeRole(Conversation conference, String nick, String role) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq changeRole(Conversation conference, String nick, String role) { + final Iq packet = new Iq(Iq.Type.SET); packet.setTo(conference.getJid().asBareJid()); packet.setFrom(conference.getAccount().getJid()); Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item"); @@ -429,8 +429,8 @@ public IqPacket changeRole(Conversation conference, String nick, String role) { return packet; } - public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) { - IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(host); Element request = packet.addChild("request", Namespace.HTTP_UPLOAD); request.setAttribute("filename", convertFilename(file.getName())); @@ -439,8 +439,8 @@ public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mi return packet; } - public IqPacket requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) { - IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(host); Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY); request.addChild("filename").setContent(convertFilename(file.getName())); @@ -466,8 +466,8 @@ private static String convertFilename(String name) { } } - public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) { - final IqPacket register = new IqPacket(IqPacket.TYPE.SET); + public static Iq generateCreateAccountWithCaptcha(final Account account, final String id, final Data data) { + final Iq register = new Iq(Iq.Type.SET); register.setFrom(account.getJid().asBareJid()); register.setTo(account.getDomain()); register.setId(id); @@ -478,12 +478,12 @@ public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Dat return register; } - public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) { + public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId) { return pushTokenToAppServer(appServer, token, deviceId, null); } - public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) { + final Iq packet = new Iq(Iq.Type.SET); packet.setTo(appServer); final Element command = packet.addChild("command", Namespace.COMMANDS); command.setAttribute("node", "register-push-fcm"); @@ -499,8 +499,8 @@ public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceI return packet; } - public IqPacket unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) { + final Iq packet = new Iq(Iq.Type.SET); packet.setTo(appServer); final Element command = packet.addChild("command", Namespace.COMMANDS); command.setAttribute("node", "unregister-push-fcm"); @@ -513,8 +513,8 @@ public IqPacket unregisterChannelOnAppServer(Jid appServer, String deviceId, Str return packet; } - public IqPacket enablePush(final Jid jid, final String node, final String secret) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq enablePush(final Jid jid, final String node, final String secret) { + final Iq packet = new Iq(Iq.Type.SET); Element enable = packet.addChild("enable", Namespace.PUSH); enable.setAttribute("jid", jid); enable.setAttribute("node", node); @@ -528,16 +528,16 @@ public IqPacket enablePush(final Jid jid, final String node, final String secret return packet; } - public IqPacket disablePush(final Jid jid, final String node) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq disablePush(final Jid jid, final String node) { + Iq packet = new Iq(Iq.Type.SET); Element disable = packet.addChild("disable", Namespace.PUSH); disable.setAttribute("jid", jid); disable.setAttribute("node", node); return packet; } - public IqPacket queryAffiliation(Conversation conversation, String affiliation) { - IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq queryAffiliation(Conversation conversation, String affiliation) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(conversation.getJid().asBareJid()); packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation); return packet; @@ -570,16 +570,16 @@ public static Bundle defaultChannelConfiguration() { return options; } - public IqPacket requestPubsubConfiguration(Jid jid, String node) { + public Iq requestPubsubConfiguration(Jid jid, String node) { return pubsubConfiguration(jid, node, null); } - public IqPacket publishPubsubConfiguration(Jid jid, String node, Data data) { + public Iq publishPubsubConfiguration(Jid jid, String node, Data data) { return pubsubConfiguration(jid, node, data); } - private IqPacket pubsubConfiguration(Jid jid, String node, Data data) { - IqPacket packet = new IqPacket(data == null ? IqPacket.TYPE.GET : IqPacket.TYPE.SET); + private Iq pubsubConfiguration(Jid jid, String node, Data data) { + final Iq packet = new Iq(data == null ? Iq.Type.GET : Iq.Type.SET); packet.setTo(jid); Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner"); Element configure = pubsub.addChild("configure").setAttribute("node", node); @@ -589,15 +589,15 @@ private IqPacket pubsubConfiguration(Jid jid, String node, Data data) { return packet; } - public IqPacket queryDiscoItems(Jid jid) { - IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq queryDiscoItems(final Jid jid) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(jid); packet.addChild("query",Namespace.DISCO_ITEMS); return packet; } - public IqPacket queryDiscoInfo(Jid jid) { - IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq queryDiscoInfo(final Jid jid) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(jid); packet.addChild("query",Namespace.DISCO_INFO); return packet; diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index e217f7f1d..f3823dee0 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -22,7 +22,6 @@ import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; public class MessageGenerator extends AbstractGenerator { private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo"; @@ -32,25 +31,25 @@ public MessageGenerator(XmppConnectionService service) { super(service); } - private MessagePacket preparePacket(Message message) { + private im.conversations.android.xmpp.model.stanza.Message preparePacket(Message message) { Conversation conversation = (Conversation) message.getConversation(); Account account = conversation.getAccount(); - MessagePacket packet = new MessagePacket(); + im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); final boolean isWithSelf = conversation.getContact().isSelf(); if (conversation.getMode() == Conversation.MODE_SINGLE) { packet.setTo(message.getCounterpart()); - packet.setType(MessagePacket.TYPE_CHAT); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); if (!isWithSelf) { packet.addChild("request", "urn:xmpp:receipts"); } } else if (message.isPrivateMessage()) { packet.setTo(message.getCounterpart()); - packet.setType(MessagePacket.TYPE_CHAT); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); packet.addChild("x", "http://jabber.org/protocol/muc#user"); packet.addChild("request", "urn:xmpp:receipts"); } else { packet.setTo(message.getCounterpart().asBareJid()); - packet.setType(MessagePacket.TYPE_GROUPCHAT); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT); } if (conversation.isSingleOrPrivateAndNonAnonymous() && !message.isPrivateMessage()) { packet.addChild("markable", "urn:xmpp:chat-markers:0"); @@ -66,7 +65,7 @@ private MessagePacket preparePacket(Message message) { return packet; } - public void addDelay(MessagePacket packet, long timestamp) { + public void addDelay(im.conversations.android.xmpp.model.stanza.Message packet, long timestamp) { final SimpleDateFormat mDateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); @@ -75,8 +74,8 @@ public void addDelay(MessagePacket packet, long timestamp) { delay.setAttribute("stamp", mDateFormat.format(date)); } - public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) { - MessagePacket packet = preparePacket(message); + public im.conversations.android.xmpp.model.stanza.Message generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) { + im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message); if (axolotlMessage == null) { return null; } @@ -89,17 +88,17 @@ public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axo return packet; } - public MessagePacket generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) { - MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); + public im.conversations.android.xmpp.model.stanza.Message generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) { + im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); packet.setTo(to); packet.setAxolotlMessage(axolotlMessage.toElement()); packet.addChild("store", "urn:xmpp:hints"); return packet; } - public MessagePacket generateChat(Message message) { - MessagePacket packet = preparePacket(message); + public im.conversations.android.xmpp.model.stanza.Message generateChat(Message message) { + im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message); String content; if (message.hasFileOnRemoteHost()) { final Message.FileParams fileParams = message.getFileParams(); @@ -112,8 +111,8 @@ public MessagePacket generateChat(Message message) { return packet; } - public MessagePacket generatePgpChat(Message message) { - MessagePacket packet = preparePacket(message); + public im.conversations.android.xmpp.model.stanza.Message generatePgpChat(Message message) { + final im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message); if (message.hasFileOnRemoteHost()) { Message.FileParams fileParams = message.getFileParams(); final String url = fileParams.url; @@ -134,10 +133,10 @@ public MessagePacket generatePgpChat(Message message) { return packet; } - public MessagePacket generateChatState(Conversation conversation) { + public im.conversations.android.xmpp.model.stanza.Message generateChatState(Conversation conversation) { final Account account = conversation.getAccount(); - MessagePacket packet = new MessagePacket(); - packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT); + final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); packet.setTo(conversation.getJid().asBareJid()); packet.setFrom(account.getJid()); packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); @@ -146,11 +145,11 @@ public MessagePacket generateChatState(Conversation conversation) { return packet; } - public MessagePacket confirm(final Message message) { + public im.conversations.android.xmpp.model.stanza.Message confirm(final Message message) { final boolean groupChat = message.getConversation().getMode() == Conversational.MODE_MULTI; final Jid to = message.getCounterpart(); - final MessagePacket packet = new MessagePacket(); - packet.setType(groupChat ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT); + final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(groupChat ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); packet.setTo(groupChat ? to.asBareJid() : to); final Element displayed = packet.addChild("displayed", "urn:xmpp:chat-markers:0"); if (groupChat) { @@ -168,18 +167,18 @@ public MessagePacket confirm(final Message message) { return packet; } - public MessagePacket conferenceSubject(Conversation conversation, String subject) { - MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_GROUPCHAT); + public im.conversations.android.xmpp.model.stanza.Message conferenceSubject(Conversation conversation, String subject) { + im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT); packet.setTo(conversation.getJid().asBareJid()); packet.addChild("subject").setContent(subject); packet.setFrom(conversation.getAccount().getJid().asBareJid()); return packet; } - public MessagePacket directInvite(final Conversation conversation, final Jid contact) { - MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_NORMAL); + public im.conversations.android.xmpp.model.stanza.Message directInvite(final Conversation conversation, final Jid contact) { + im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.NORMAL); packet.setTo(contact); packet.setFrom(conversation.getAccount().getJid()); Element x = packet.addChild("x", "jabber:x:conference"); @@ -195,8 +194,8 @@ public MessagePacket directInvite(final Conversation conversation, final Jid con return packet; } - public MessagePacket invite(final Conversation conversation, final Jid contact) { - final MessagePacket packet = new MessagePacket(); + public im.conversations.android.xmpp.model.stanza.Message invite(final Conversation conversation, final Jid contact) { + final var packet = new im.conversations.android.xmpp.model.stanza.Message(); packet.setTo(conversation.getJid().asBareJid()); packet.setFrom(conversation.getAccount().getJid()); Element x = new Element("x"); @@ -208,8 +207,9 @@ public MessagePacket invite(final Conversation conversation, final Jid contact) return packet; } - public MessagePacket received(Account account, final Jid from, final String id, ArrayList namespaces, int type) { - final MessagePacket receivedPacket = new MessagePacket(); + public im.conversations.android.xmpp.model.stanza.Message received(Account account, final Jid from, final String id, ArrayList namespaces, im.conversations.android.xmpp.model.stanza.Message.Type type) { + final var receivedPacket = + new im.conversations.android.xmpp.model.stanza.Message(); receivedPacket.setType(type); receivedPacket.setTo(from); receivedPacket.setFrom(account.getJid()); @@ -220,8 +220,8 @@ public MessagePacket received(Account account, final Jid from, final String id, return receivedPacket; } - public MessagePacket received(Account account, Jid to, String id) { - MessagePacket packet = new MessagePacket(); + public im.conversations.android.xmpp.model.stanza.Message received(Account account, Jid to, String id) { + im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); packet.setFrom(account.getJid()); packet.setTo(to); packet.addChild("received", "urn:xmpp:receipts").setAttribute("id", id); @@ -229,10 +229,10 @@ public MessagePacket received(Account account, Jid to, String id) { return packet; } - public MessagePacket sessionFinish( + public im.conversations.android.xmpp.model.stanza.Message sessionFinish( final Jid with, final String sessionId, final Reason reason) { - final MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); + final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); packet.setTo(with); final Element finish = packet.addChild("finish", Namespace.JINGLE_MESSAGE); finish.setAttribute("id", sessionId); @@ -242,9 +242,9 @@ public MessagePacket sessionFinish( return packet; } - public MessagePacket sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) { - final MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those + public im.conversations.android.xmpp.model.stanza.Message sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) { + final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those packet.setTo(proposal.with); packet.setId(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX + proposal.sessionId); final Element propose = packet.addChild("propose", Namespace.JINGLE_MESSAGE); @@ -257,9 +257,9 @@ public MessagePacket sessionProposal(final JingleConnectionManager.RtpSessionPro return packet; } - public MessagePacket sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) { - final MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those + public im.conversations.android.xmpp.model.stanza.Message sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) { + final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those packet.setTo(proposal.with); final Element propose = packet.addChild("retract", Namespace.JINGLE_MESSAGE); propose.setAttribute("id", proposal.sessionId); @@ -268,9 +268,9 @@ public MessagePacket sessionRetract(final JingleConnectionManager.RtpSessionProp return packet; } - public MessagePacket sessionReject(final Jid with, final String sessionId) { - final MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those + public im.conversations.android.xmpp.model.stanza.Message sessionReject(final Jid with, final String sessionId) { + final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those packet.setTo(with); final Element propose = packet.addChild("reject", Namespace.JINGLE_MESSAGE); propose.setAttribute("id", sessionId); diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java index 1485385bc..7bb734184 100644 --- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java @@ -9,7 +9,6 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.PresencePacket; public class PresenceGenerator extends AbstractGenerator { @@ -17,20 +16,20 @@ public PresenceGenerator(XmppConnectionService service) { super(service); } - private PresencePacket subscription(String type, Contact contact) { - PresencePacket packet = new PresencePacket(); + private im.conversations.android.xmpp.model.stanza.Presence subscription(String type, Contact contact) { + im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence(); packet.setAttribute("type", type); packet.setTo(contact.getJid()); packet.setFrom(contact.getAccount().getJid().asBareJid()); return packet; } - public PresencePacket requestPresenceUpdatesFrom(final Contact contact) { + public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom(final Contact contact) { return requestPresenceUpdatesFrom(contact, null); } - public PresencePacket requestPresenceUpdatesFrom(final Contact contact, final String preAuth) { - PresencePacket packet = subscription("subscribe", contact); + public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom(final Contact contact, final String preAuth) { + im.conversations.android.xmpp.model.stanza.Presence packet = subscription("subscribe", contact); String displayName = contact.getAccount().getDisplayName(); if (!TextUtils.isEmpty(displayName)) { packet.addChild("nick", Namespace.NICK).setContent(displayName); @@ -41,24 +40,24 @@ public PresencePacket requestPresenceUpdatesFrom(final Contact contact, final St return packet; } - public PresencePacket stopPresenceUpdatesFrom(Contact contact) { + public im.conversations.android.xmpp.model.stanza.Presence stopPresenceUpdatesFrom(Contact contact) { return subscription("unsubscribe", contact); } - public PresencePacket stopPresenceUpdatesTo(Contact contact) { + public im.conversations.android.xmpp.model.stanza.Presence stopPresenceUpdatesTo(Contact contact) { return subscription("unsubscribed", contact); } - public PresencePacket sendPresenceUpdatesTo(Contact contact) { + public im.conversations.android.xmpp.model.stanza.Presence sendPresenceUpdatesTo(Contact contact) { return subscription("subscribed", contact); } - public PresencePacket selfPresence(Account account, Presence.Status status) { + public im.conversations.android.xmpp.model.stanza.Presence selfPresence(Account account, Presence.Status status) { return selfPresence(account, status, true); } - public PresencePacket selfPresence(final Account account, final Presence.Status status, final boolean personal) { - final PresencePacket packet = new PresencePacket(); + public im.conversations.android.xmpp.model.stanza.Presence selfPresence(final Account account, final Presence.Status status, final boolean personal) { + final im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence(); if (personal) { final String sig = account.getPgpSignature(); final String message = account.getPresenceStatusMessage(); @@ -83,16 +82,16 @@ public PresencePacket selfPresence(final Account account, final Presence.Status return packet; } - public PresencePacket leave(final MucOptions mucOptions) { - PresencePacket presencePacket = new PresencePacket(); - presencePacket.setTo(mucOptions.getSelf().getFullJid()); - presencePacket.setFrom(mucOptions.getAccount().getJid()); - presencePacket.setAttribute("type", "unavailable"); - return presencePacket; + public im.conversations.android.xmpp.model.stanza.Presence leave(final MucOptions mucOptions) { + im.conversations.android.xmpp.model.stanza.Presence presence = new im.conversations.android.xmpp.model.stanza.Presence(); + presence.setTo(mucOptions.getSelf().getFullJid()); + presence.setFrom(mucOptions.getAccount().getJid()); + presence.setAttribute("type", "unavailable"); + return presence; } - public PresencePacket sendOfflinePresence(Account account) { - PresencePacket packet = new PresencePacket(); + public im.conversations.android.xmpp.model.stanza.Presence sendOfflinePresence(Account account) { + im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence(); packet.setFrom(account.getJid()); packet.setAttribute("type", "unavailable"); return packet; diff --git a/src/main/java/eu/siacs/conversations/http/SlotRequester.java b/src/main/java/eu/siacs/conversations/http/SlotRequester.java index 5a3558855..d76a99fda 100644 --- a/src/main/java/eu/siacs/conversations/http/SlotRequester.java +++ b/src/main/java/eu/siacs/conversations/http/SlotRequester.java @@ -43,7 +43,7 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.IqResponseException; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import okhttp3.Headers; import okhttp3.HttpUrl; @@ -67,9 +67,9 @@ public ListenableFuture request(Method method, Account account, Downloadab private ListenableFuture requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) { final SettableFuture future = SettableFuture.create(); - final IqPacket request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime); - service.sendIqPacket(account, request, (a, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + final Iq request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime); + service.sendIqPacket(account, request, (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD_LEGACY); if (slotElement != null) { try { @@ -97,9 +97,9 @@ private ListenableFuture requestHttpUploadLegacy(Account account, Jid host private ListenableFuture requestHttpUpload(Account account, Jid host, DownloadableFile file, String mime) { final SettableFuture future = SettableFuture.create(); - final IqPacket request = service.getIqGenerator().requestHttpUploadSlot(host, file, mime); - service.sendIqPacket(account, request, (a, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + final Iq request = service.getIqGenerator().requestHttpUploadSlot(host, file, mime); + service.sendIqPacket(account, request, (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD); if (slotElement != null) { try { diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index 5de637399..2e2cb2684 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -16,14 +16,16 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; +import im.conversations.android.xmpp.model.stanza.Stanza; public abstract class AbstractParser { - protected XmppConnectionService mXmppConnectionService; + protected final XmppConnectionService mXmppConnectionService; + protected final Account account; - protected AbstractParser(XmppConnectionService service) { + protected AbstractParser(final XmppConnectionService service, final Account account) { this.mXmppConnectionService = service; + this.account = account; } public static Long parseTimestamp(Element element, Long d) { @@ -34,8 +36,8 @@ public static Long parseTimestamp(Element element, Long d, boolean ignoreCsiAndS long min = Long.MAX_VALUE; boolean returnDefault = true; final Jid to; - if (ignoreCsiAndSm && element instanceof AbstractStanza) { - to = ((AbstractStanza) element).getTo(); + if (ignoreCsiAndSm && element instanceof Stanza stanza) { + to = stanza.getTo(); } else { to = null; } @@ -123,7 +125,7 @@ protected void updateLastseen(final Account account, final Jid from) { contact.setLastResource(from.isBareJid() ? "" : from.getResource()); } - protected String avatarData(Element items) { + protected static String avatarData(Element items) { Element item = items.findChild("item"); if (item == null) { return null; diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 0c08c557e..339c025c9 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; @@ -37,18 +38,17 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.forms.Data; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; -public class IqParser extends AbstractParser implements OnIqPacketReceived { +public class IqParser extends AbstractParser implements Consumer { - public IqParser(final XmppConnectionService service) { - super(service); + public IqParser(final XmppConnectionService service, final Account account) { + super(service, account); } - public static List items(IqPacket packet) { + public static List items(final Iq packet) { ArrayList items = new ArrayList<>(); final Element query = packet.findChild("query", Namespace.DISCO_ITEMS); if (query == null) { @@ -65,7 +65,7 @@ public static List items(IqPacket packet) { return items; } - public static Room parseRoom(IqPacket packet) { + public static Room parseRoom(Iq packet) { final Element query = packet.findChild("query", Namespace.DISCO_INFO); if (query == null) { return null; @@ -143,7 +143,7 @@ private void rosterItems(final Account account, final Element query) { mXmppConnectionService.syncRoster(account); } - public String avatarData(final IqPacket packet) { + public static String avatarData(final Iq packet) { final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB); if (pubsub == null) { return null; @@ -152,10 +152,10 @@ public String avatarData(final IqPacket packet) { if (items == null) { return null; } - return super.avatarData(items); + return AbstractParser.avatarData(items); } - public Element getItem(final IqPacket packet) { + public static Element getItem(final Iq packet) { final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB); if (pubsub == null) { return null; @@ -168,7 +168,7 @@ public Element getItem(final IqPacket packet) { } @NonNull - public Set deviceIds(final Element item) { + public static Set deviceIds(final Element item) { Set deviceIds = new HashSet<>(); if (item != null) { final Element list = item.findChild("list"); @@ -189,7 +189,7 @@ public Set deviceIds(final Element item) { return deviceIds; } - private Integer signedPreKeyId(final Element bundle) { + private static Integer signedPreKeyId(final Element bundle) { final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); if (signedPreKeyPublic == null) { return null; @@ -201,7 +201,7 @@ private Integer signedPreKeyId(final Element bundle) { } } - private ECPublicKey signedPreKeyPublic(final Element bundle) { + private static ECPublicKey signedPreKeyPublic(final Element bundle) { ECPublicKey publicKey = null; final String signedPreKeyPublic = bundle.findChildContent("signedPreKeyPublic"); if (signedPreKeyPublic == null) { @@ -215,7 +215,7 @@ private ECPublicKey signedPreKeyPublic(final Element bundle) { return publicKey; } - private byte[] signedPreKeySignature(final Element bundle) { + private static byte[] signedPreKeySignature(final Element bundle) { final String signedPreKeySignature = bundle.findChildContent("signedPreKeySignature"); if (signedPreKeySignature == null) { return null; @@ -228,7 +228,7 @@ private byte[] signedPreKeySignature(final Element bundle) { } } - private IdentityKey identityKey(final Element bundle) { + private static IdentityKey identityKey(final Element bundle) { final String identityKey = bundle.findChildContent("identityKey"); if (identityKey == null) { return null; @@ -241,7 +241,7 @@ private IdentityKey identityKey(final Element bundle) { } } - public Map preKeyPublics(final IqPacket packet) { + public static Map preKeyPublics(final Iq packet) { Map preKeyRecords = new HashMap<>(); Element item = getItem(packet); if (item == null) { @@ -284,7 +284,7 @@ private static byte[] base64decode(String input) { return BaseEncoding.base64().decode(CharMatcher.whitespace().removeFrom(input)); } - public Pair verification(final IqPacket packet) { + public static Pair verification(final Iq packet) { Element item = getItem(packet); Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null; Element chain = verification != null ? verification.findChild("chain") : null; @@ -312,7 +312,7 @@ public Pair verification(final IqPacket packet) { } } - public PreKeyBundle bundle(final IqPacket bundle) { + public static PreKeyBundle bundle(final Iq bundle) { final Element bundleItem = getItem(bundle); if (bundleItem == null) { return null; @@ -336,7 +336,7 @@ public PreKeyBundle bundle(final IqPacket bundle) { signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); } - public List preKeys(final IqPacket preKeys) { + public static List preKeys(final Iq preKeys) { List bundles = new ArrayList<>(); Map preKeyPublics = preKeyPublics(preKeys); if (preKeyPublics != null) { @@ -351,15 +351,15 @@ public List preKeys(final IqPacket preKeys) { } @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - final boolean isGet = packet.getType() == IqPacket.TYPE.GET; - if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) { + public void accept(final Iq packet) { + final boolean isGet = packet.getType() == Iq.Type.GET; + if (packet.getType() == Iq.Type.ERROR || packet.getType() == Iq.Type.TIMEOUT) { return; } if (packet.hasChild("query", Namespace.ROSTER) && packet.fromServer(account)) { final Element query = packet.findChild("query"); // If this is in response to a query for the whole roster: - if (packet.getType() == IqPacket.TYPE.RESULT) { + if (packet.getType() == Iq.Type.RESULT) { account.getRoster().markAllAsNotInRoster(); } this.rosterItems(account, query); @@ -373,7 +373,7 @@ public void onIqPacketReceived(final Account account, final IqPacket packet) { (block != null ? block.getChildren() : null); // If this is a response to a blocklist query, clear the block list and replace with the new one. // Otherwise, just update the existing blocklist. - if (packet.getType() == IqPacket.TYPE.RESULT) { + if (packet.getType() == Iq.Type.RESULT) { account.clearBlocklist(); account.getXmppConnection().getFeatures().setBlockListRequested(true); } @@ -389,7 +389,7 @@ public void onIqPacketReceived(final Account account, final IqPacket packet) { } } account.getBlocklist().addAll(jids); - if (packet.getType() == IqPacket.TYPE.SET) { + if (packet.getType() == Iq.Type.SET) { boolean removed = false; for (Jid jid : jids) { removed |= mXmppConnectionService.removeBlockedConversations(account, jid); @@ -401,15 +401,15 @@ public void onIqPacketReceived(final Account account, final IqPacket packet) { } // Update the UI mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); - if (packet.getType() == IqPacket.TYPE.SET) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); + if (packet.getType() == Iq.Type.SET) { + final Iq response = packet.generateResponse(Iq.Type.RESULT); mXmppConnectionService.sendIqPacket(account, response, null); } } else if (packet.hasChild("unblock", Namespace.BLOCKING) && - packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) { + packet.fromServer(account) && packet.getType() == Iq.Type.SET) { Log.d(Config.LOGTAG, "Received unblock update from server"); final Collection items = packet.findChild("unblock", Namespace.BLOCKING).getChildren(); - if (items.size() == 0) { + if (items.isEmpty()) { // No children to unblock == unblock all account.getBlocklist().clear(); } else { @@ -425,7 +425,7 @@ public void onIqPacketReceived(final Account account, final IqPacket packet) { account.getBlocklist().removeAll(jids); } mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); - final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); + final Iq response = packet.generateResponse(Iq.Type.RESULT); mXmppConnectionService.sendIqPacket(account, response, null); } else if (packet.hasChild("open", "http://jabber.org/protocol/ibb") || packet.hasChild("data", "http://jabber.org/protocol/ibb") @@ -433,18 +433,18 @@ public void onIqPacketReceived(final Account account, final IqPacket packet) { mXmppConnectionService.getJingleConnectionManager() .deliverIbbPacket(account, packet); } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { - final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet); + final Iq response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet); mXmppConnectionService.sendIqPacket(account, response, null); } else if (packet.hasChild("query", "jabber:iq:version") && isGet) { - final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet); + final Iq response = mXmppConnectionService.getIqGenerator().versionResponse(packet); mXmppConnectionService.sendIqPacket(account, response, null); } else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); + final Iq response = packet.generateResponse(Iq.Type.RESULT); mXmppConnectionService.sendIqPacket(account, response, null); } else if (packet.hasChild("time", "urn:xmpp:time") && isGet) { - final IqPacket response; + final Iq response; if (mXmppConnectionService.useTorToConnect() || account.isOnion()) { - response = packet.generateResponse(IqPacket.TYPE.ERROR); + response = packet.generateResponse(Iq.Type.ERROR); final Element error = response.addChild("error"); error.setAttribute("type", "cancel"); error.addChild("not-allowed", "urn:ietf:params:xml:ns:xmpp-stanzas"); @@ -452,18 +452,18 @@ public void onIqPacketReceived(final Account account, final IqPacket packet) { response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet); } mXmppConnectionService.sendIqPacket(account, response, null); - } else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) && packet.getType() == IqPacket.TYPE.SET) { + } else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) && packet.getType() == Iq.Type.SET) { final Jid transport = packet.getFrom(); final Element push = packet.findChild("push", Namespace.UNIFIED_PUSH); final boolean success = push != null && mXmppConnectionService.processUnifiedPushMessage( account, transport, push); - final IqPacket response; + final Iq response; if (success) { - response = packet.generateResponse(IqPacket.TYPE.RESULT); + response = packet.generateResponse(Iq.Type.RESULT); } else { - response = packet.generateResponse(IqPacket.TYPE.ERROR); + response = packet.generateResponse(Iq.Type.ERROR); final Element error = response.addChild("error"); error.setAttribute("type", "cancel"); error.setAttribute("code", "404"); @@ -471,8 +471,8 @@ public void onIqPacketReceived(final Account account, final IqPacket packet) { } mXmppConnectionService.sendIqPacket(account, response, null); } else { - if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); + if (packet.getType() == Iq.Type.GET || packet.getType() == Iq.Type.SET) { + final Iq response = packet.generateResponse(Iq.Type.ERROR); final Element error = response.addChild("error"); error.setAttribute("type", "cancel"); error.addChild("feature-not-implemented", "urn:ietf:params:xml:ns:xmpp-stanzas"); diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 011942c50..c979ae1b6 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -15,6 +15,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.function.Consumer; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; @@ -49,17 +50,20 @@ import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.pep.Avatar; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.carbons.Received; +import im.conversations.android.xmpp.model.carbons.Sent; +import im.conversations.android.xmpp.model.forward.Forwarded; -public class MessageParser extends AbstractParser implements OnMessagePacketReceived { +public class MessageParser extends AbstractParser implements Consumer { private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH); private static final List JINGLE_MESSAGE_ELEMENT_NAMES = Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish"); - public MessageParser(XmppConnectionService service) { - super(service); + public MessageParser(final XmppConnectionService service, final Account account) { + super(service, account); } private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) { @@ -98,7 +102,7 @@ private static Jid getTrueCounterpart(Element mucUserElement, Jid fallback) { return result != null ? result : fallback; } - private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) { + private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final im.conversations.android.xmpp.model.stanza.Message packet) { ChatState state = ChatState.parse(packet); if (state != null && c != null) { final Account account = c.getAccount(); @@ -240,7 +244,7 @@ private void parseEvent(final Element event, final Jid from, final Account accou } } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) { Element item = items.findChild("item"); - Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + final Set deviceIds = IqParser.deviceIds(item); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... "); final AxolotlService axolotlService = account.getAxolotlService(); axolotlService.registerDevices(from, deviceIds); @@ -347,10 +351,10 @@ private void setNick(Account account, Jid user, String nick) { mXmppConnectionService.updateAccountUi(); } - private boolean handleErrorMessage(final Account account, final MessagePacket packet) { - if (packet.getType() == MessagePacket.TYPE_ERROR) { + private boolean handleErrorMessage(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet) { + if (packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.ERROR) { if (packet.fromServer(account)) { - final Pair forwarded = packet.getForwardedMessagePacket("received", Namespace.CARBONS); + final var forwarded = getForwardedMessagePacket(packet,"received", Namespace.CARBONS); if (forwarded != null) { return handleErrorMessage(account, forwarded.first); } @@ -393,11 +397,11 @@ private boolean handleErrorMessage(final Account account, final MessagePacket pa } @Override - public void onMessagePacketReceived(Account account, MessagePacket original) { + public void accept(final im.conversations.android.xmpp.model.stanza.Message original) { if (handleErrorMessage(account, original)) { return; } - final MessagePacket packet; + final im.conversations.android.xmpp.model.stanza.Message packet; Long timestamp = null; boolean isCarbon = false; String serverMsgId = null; @@ -411,7 +415,7 @@ public void onMessagePacketReceived(Account account, MessagePacket original) { final MessageArchiveService.Query query = queryId == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(queryId); final boolean offlineMessagesRetrieved = account.getXmppConnection().isOfflineMessagesRetrieved(); if (query != null && query.validFrom(original.getFrom())) { - final Pair f = original.getForwardedMessagePacket("result", query.version.namespace); + final var f = getForwardedMessagePacket(original,"result", query.version.namespace); if (f == null) { return; } @@ -426,9 +430,9 @@ public void onMessagePacketReceived(Account account, MessagePacket original) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result with invalid from (" + original.getFrom() + ") or queryId (" + queryId + ")"); return; } else if (original.fromServer(account)) { - Pair f; - f = original.getForwardedMessagePacket("received", Namespace.CARBONS); - f = f == null ? original.getForwardedMessagePacket("sent", Namespace.CARBONS) : f; + Pair f; + f = getForwardedMessagePacket(original, Received.class); + f = f == null ? getForwardedMessagePacket(original, Sent.class) : f; packet = f != null ? f.first : original; if (handleErrorMessage(account, packet)) { return; @@ -468,7 +472,7 @@ public void onMessagePacketReceived(Account account, MessagePacket original) { return; } - boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT; + boolean isTypeGroupChat = packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT; if (query != null && !query.muc() && isTypeGroupChat) { Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping"); return; @@ -1106,6 +1110,34 @@ public void onMessagePacketReceived(Account account, MessagePacket original) { } } + private static Pair getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, Class clazz) { + final var extension = original.getExtension(clazz); + final var forwarded = extension == null ? null : extension.getExtension(Forwarded.class); + if (forwarded == null) { + return null; + } + final Long timestamp = AbstractParser.parseTimestamp(forwarded, null); + final var forwardedMessage = forwarded.getMessage(); + if (forwardedMessage == null) { + return null; + } + return new Pair<>(forwardedMessage,timestamp); + } + + private static Pair getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, final String name, final String namespace) { + final Element wrapper = original.findChild(name, namespace); + final var forwardedElement = wrapper == null ? null : wrapper.findChild("forwarded",Namespace.FORWARD); + if (forwardedElement instanceof Forwarded forwarded) { + final Long timestamp = AbstractParser.parseTimestamp(forwarded, null); + final var forwardedMessage = forwarded.getMessage(); + if (forwardedMessage == null) { + return null; + } + return new Pair<>(forwardedMessage,timestamp); + } + return null; + } + private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query, final String id) { final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid()); if (conversation != null && (query == null || query.isCatchup())) { @@ -1118,7 +1150,7 @@ private void dismissNotification(Account account, Jid counterpart, MessageArchiv } } - private void processMessageReceipts(final Account account, final MessagePacket packet, final String remoteMsgId, MessageArchiveService.Query query) { + private void processMessageReceipts(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet, final String remoteMsgId, MessageArchiveService.Query query) { final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0"); final boolean request = packet.hasChild("request", "urn:xmpp:receipts"); if (query == null) { @@ -1130,7 +1162,7 @@ private void processMessageReceipts(final Account account, final MessagePacket p receiptsNamespaces.add("urn:xmpp:receipts"); } if (receiptsNamespaces.size() > 0) { - final MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, + final var receipt = mXmppConnectionService.getMessageGenerator().received(account, packet.getFrom(), remoteMsgId, receiptsNamespaces, diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 584b8e704..fc439ae08 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -19,22 +19,21 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.OnPresencePacketReceived; import eu.siacs.conversations.xmpp.pep.Avatar; -import eu.siacs.conversations.xmpp.stanzas.PresencePacket; import org.openintents.openpgp.util.OpenPgpUtils; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; -public class PresenceParser extends AbstractParser implements OnPresencePacketReceived { +public class PresenceParser extends AbstractParser implements Consumer { - public PresenceParser(XmppConnectionService service) { - super(service); + public PresenceParser(final XmppConnectionService service, final Account account) { + super(service, account); } - public void parseConferencePresence(PresencePacket packet, Account account) { + public void parseConferencePresence(final im.conversations.android.xmpp.model.stanza.Presence packet, Account account) { final Conversation conversation = packet.getFrom() == null ? null @@ -58,7 +57,7 @@ public void parseConferencePresence(PresencePacket packet, Account account) { } } - private void processConferencePresence(PresencePacket packet, Conversation conversation) { + private void processConferencePresence(final im.conversations.android.xmpp.model.stanza.Presence packet, Conversation conversation) { final Account account = conversation.getAccount(); final MucOptions mucOptions = conversation.getMucOptions(); final Jid jid = conversation.getAccount().getJid(); @@ -297,7 +296,7 @@ private static List getStatusCodes(Element x) { return codes; } - private void parseContactPresence(final PresencePacket packet, final Account account) { + private void parseContactPresence(final im.conversations.android.xmpp.model.stanza.Presence packet, final Account account) { final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator(); final Jid from = packet.getFrom(); if (from == null || from.equals(account.getJid())) { @@ -431,7 +430,7 @@ private void parseContactPresence(final PresencePacket packet, final Account acc } @Override - public void onPresencePacketReceived(Account account, PresencePacket packet) { + public void accept(final im.conversations.android.xmpp.model.stanza.Presence packet) { if (packet.hasChild("x", Namespace.MUC_USER)) { this.parseConferencePresence(packet, account); } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) { diff --git a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java index 3ca6bfde4..b9d43fa51 100644 --- a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java +++ b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java @@ -20,10 +20,9 @@ import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.utils.TLSSocketFactory; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.XmppConnection; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import okhttp3.OkHttpClient; import okhttp3.ResponseBody; @@ -202,7 +201,7 @@ private void discoverChannelsLocalServers( final String query, final OnChannelSearchResultsFound listener) { final Map localMucService = getLocalMucServices(); Log.d(Config.LOGTAG, "checking with " + localMucService.size() + " muc services"); - if (localMucService.size() == 0) { + if (localMucService.isEmpty()) { listener.onChannelSearchResultsFound(Collections.emptyList()); return; } @@ -216,39 +215,36 @@ private void discoverChannelsLocalServers( } final AtomicInteger queriesInFlight = new AtomicInteger(); final List rooms = new ArrayList<>(); - for (Map.Entry entry : localMucService.entrySet()) { - IqPacket itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey()); + for (final Map.Entry entry : localMucService.entrySet()) { + Iq itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey()); queriesInFlight.incrementAndGet(); + final var account = entry.getValue(); service.sendIqPacket( - entry.getValue(), + account, itemsRequest, - (account, itemsResponse) -> { - if (itemsResponse.getType() == IqPacket.TYPE.RESULT) { + (itemsResponse) -> { + if (itemsResponse.getType() == Iq.Type.RESULT) { final List items = IqParser.items(itemsResponse); - for (Jid item : items) { - IqPacket infoRequest = + for (final Jid item : items) { + final Iq infoRequest = service.getIqGenerator().queryDiscoInfo(item); queriesInFlight.incrementAndGet(); service.sendIqPacket( account, infoRequest, - new OnIqPacketReceived() { - @Override - public void onIqPacketReceived( - Account account, IqPacket infoResponse) { - if (infoResponse.getType() - == IqPacket.TYPE.RESULT) { - final Room room = - IqParser.parseRoom(infoResponse); - if (room != null) { - rooms.add(room); - } - if (queriesInFlight.decrementAndGet() <= 0) { - finishDiscoSearch(rooms, query, listener); - } - } else { - queriesInFlight.decrementAndGet(); + infoResponse -> { + if (infoResponse.getType() + == Iq.Type.RESULT) { + final Room room = + IqParser.parseRoom(infoResponse); + if (room != null) { + rooms.add(room); } + if (queriesInFlight.decrementAndGet() <= 0) { + finishDiscoSearch(rooms, query, listener); + } + } else { + queriesInFlight.decrementAndGet(); } }); } diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index 126993501..2c342e672 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -23,8 +23,8 @@ import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.mam.MamReference; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.stanza.Message; public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { @@ -81,7 +81,7 @@ public static boolean has(List features) { return false; } - public static Element findResult(MessagePacket packet) { + public static Element findResult(Message packet) { for (Version version : values()) { Element result = packet.findChild("result", version.namespace); if (result != null) { @@ -234,17 +234,17 @@ private void execute(final Query query) { throw new IllegalStateException("Attempted to run MAM query for archived conversation"); } Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": running mam query " + query.toString()); - final IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); - this.mXmppConnectionService.sendIqPacket(account, packet, (a, p) -> { + final Iq packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); + this.mXmppConnectionService.sendIqPacket(account, packet, (p) -> { final Element fin = p.findChild("fin", query.version.namespace); - if (p.getType() == IqPacket.TYPE.TIMEOUT) { + if (p.getType() == Iq.Type.TIMEOUT) { synchronized (this.queries) { this.queries.remove(query); if (query.hasCallback()) { query.callback(false); } } - } else if (p.getType() == IqPacket.TYPE.RESULT && fin != null) { + } else if (p.getType() == Iq.Type.RESULT && fin != null) { final boolean running; synchronized (this.queries) { running = this.queries.contains(query); @@ -254,10 +254,10 @@ private void execute(final Query query) { } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring MAM iq result because query had been killed"); } - } else if (p.getType() == IqPacket.TYPE.RESULT && query.isLegacy()) { + } else if (p.getType() == Iq.Type.RESULT && query.isLegacy()) { //do nothing } else { - Log.d(Config.LOGTAG, a.getJid().asBareJid().toString() + ": error executing mam: " + p.toString()); + Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": error executing mam: " + p.toString()); try { finalizeQuery(query, true); } catch (final IllegalStateException e) { @@ -304,7 +304,7 @@ private void finalizeQuery(final Query query, boolean done) { } } - boolean inCatchup(Account account) { + public boolean inCatchup(Account account) { synchronized (this.queries) { for (Query query : queries) { if (query.account == account && query.isCatchup() && query.getWith() == null) { diff --git a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java index 4aab05cee..92ae9d9ec 100644 --- a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java +++ b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java @@ -32,8 +32,9 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.PresencePacket; +import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.stanza.Presence; + import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.List; @@ -82,7 +83,7 @@ public void renewUnifiedPushEndpointsOnBind(final Account account) { } private void sendDirectedPresence(final Account account, Jid to) { - final PresencePacket presence = new PresencePacket(); + final var presence = new Presence(); presence.setTo(to); service.sendPresencePacket(account, presence); } @@ -146,7 +147,7 @@ private void renewUnifiedEndpoint(final Transport transport, final PushTargetMes UnifiedPushDistributor.hash(account.getUuid(), renewal.application); final String hashedInstance = UnifiedPushDistributor.hash(account.getUuid(), renewal.instance); - final IqPacket registration = new IqPacket(IqPacket.TYPE.SET); + final Iq registration = new Iq(Iq.Type.SET); registration.setTo(transport.transport); final Element register = registration.addChild("register", Namespace.UNIFIED_PUSH); register.setAttribute("application", hashedApplication); @@ -160,7 +161,7 @@ private void renewUnifiedEndpoint(final Transport transport, final PushTargetMes this.service.sendIqPacket( account, registration, - (a, response) -> processRegistration(transport, renewal, messenger, response)); + (response) -> processRegistration(transport, renewal, messenger, response)); } } @@ -168,8 +169,8 @@ private void processRegistration( final Transport transport, final UnifiedPushDatabase.PushTarget renewal, final Messenger messenger, - final IqPacket response) { - if (response.getType() == IqPacket.TYPE.RESULT) { + final Iq response) { + if (response.getType() == Iq.Type.RESULT) { final Element registered = response.findChild("registered", Namespace.UNIFIED_PUSH); if (registered == null) { return; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 80b4d6ddd..0cf6fbe24 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -52,7 +52,6 @@ import androidx.annotation.Nullable; import androidx.core.app.RemoteInput; import androidx.core.content.ContextCompat; -import androidx.core.util.Consumer; import com.google.common.base.Objects; import com.google.common.base.Optional; @@ -92,6 +91,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; @@ -123,8 +123,6 @@ import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.parser.AbstractParser; import eu.siacs.conversations.parser.IqParser; -import eu.siacs.conversations.parser.MessageParser; -import eu.siacs.conversations.parser.PresenceParser; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.UnifiedPushDatabase; @@ -141,7 +139,6 @@ import eu.siacs.conversations.utils.ConversationsFileObserver; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.EasyOnboardingInvite; -import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.utils.QuickLoader; @@ -160,11 +157,8 @@ import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnBindListener; import eu.siacs.conversations.xmpp.OnContactStatusChanged; -import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnMessageAcknowledged; -import eu.siacs.conversations.xmpp.OnMessagePacketReceived; -import eu.siacs.conversations.xmpp.OnPresencePacketReceived; import eu.siacs.conversations.xmpp.OnStatusChanged; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.XmppConnection; @@ -178,9 +172,7 @@ import eu.siacs.conversations.xmpp.mam.MamReference; import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.pep.PublishOptions; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; -import eu.siacs.conversations.xmpp.stanzas.PresencePacket; +import im.conversations.android.xmpp.model.stanza.Iq; import me.leolin.shortcutbadger.ShortcutBadger; public class XmppConnectionService extends Service { @@ -225,12 +217,12 @@ public class XmppConnectionService extends Service { private final Set mInProgressAvatarFetches = new HashSet<>(); private final Set mOmittedPepAvatarFetches = new HashSet<>(); private final HashSet mLowPingTimeoutMode = new HashSet<>(); - private final OnIqPacketReceived mDefaultIqHandler = (account, packet) -> { - if (packet.getType() != IqPacket.TYPE.RESULT) { - Element error = packet.findChild("error"); + private final Consumer mDefaultIqHandler = (packet) -> { + if (packet.getType() != Iq.Type.RESULT) { + final var error = packet.getError(); String text = error != null ? error.findChildContent("text") : null; if (text != null) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received iq error - " + text); + Log.d(Config.LOGTAG, "received iq error: " + text); } } }; @@ -247,9 +239,6 @@ public class XmppConnectionService extends Service { private final AtomicBoolean mOngoingVideoTranscoding = new AtomicBoolean(false); private final AtomicBoolean mForceDuringOnCreate = new AtomicBoolean(false); private final AtomicReference ongoingCall = new AtomicReference<>(); - private final OnMessagePacketReceived mMessageParser = new MessageParser(this); - private final OnPresencePacketReceived mPresenceParser = new PresenceParser(this); - private final IqParser mIqParser = new IqParser(this); private final MessageGenerator mMessageGenerator = new MessageGenerator(this); public OnContactStatusChanged onContactStatusChanged = (contact, online) -> { Conversation conversation = find(getConversations(), contact); @@ -330,79 +319,6 @@ public boolean onMessageAcknowledged(final Account account, final Jid to, final public final Set FILENAMES_TO_IGNORE_DELETION = new HashSet<>(); - private final OnBindListener mOnBindListener = new OnBindListener() { - - @Override - public void onBind(final Account account) { - synchronized (mInProgressAvatarFetches) { - for (Iterator iterator = mInProgressAvatarFetches.iterator(); iterator.hasNext(); ) { - final String KEY = iterator.next(); - if (KEY.startsWith(account.getJid().asBareJid() + "_")) { - iterator.remove(); - } - } - } - boolean loggedInSuccessfully = account.setOption(Account.OPTION_LOGGED_IN_SUCCESSFULLY, true); - boolean gainedFeature = account.setOption(Account.OPTION_HTTP_UPLOAD_AVAILABLE, account.getXmppConnection().getFeatures().httpUpload(0)); - if (loggedInSuccessfully || gainedFeature) { - databaseBackend.updateAccount(account); - } - - if (loggedInSuccessfully) { - if (!TextUtils.isEmpty(account.getDisplayName())) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": display name wasn't empty on first log in. publishing"); - publishDisplayName(account); - } - } - - account.getRoster().clearPresences(); - synchronized (account.inProgressConferenceJoins) { - account.inProgressConferenceJoins.clear(); - } - synchronized (account.inProgressConferencePings) { - account.inProgressConferencePings.clear(); - } - mJingleConnectionManager.notifyRebound(account); - mQuickConversationsService.considerSyncBackground(false); - fetchRosterFromServer(account); - - final XmppConnection connection = account.getXmppConnection(); - - if (connection.getFeatures().bookmarks2()) { - fetchBookmarks2(account); - } else if (!account.getXmppConnection().getFeatures().bookmarksConversion()) { - fetchBookmarks(account); - } - - if (connection.getFeatures().mds()) { - fetchMessageDisplayedSynchronization(account); - } else { - Log.d(Config.LOGTAG,account.getJid()+": server has no support for mds"); - } - final boolean flexible = account.getXmppConnection().getFeatures().flexibleOfflineMessageRetrieval(); - final boolean catchup = getMessageArchiveService().inCatchup(account); - final boolean trackOfflineMessageRetrieval; - if (flexible && catchup && account.getXmppConnection().isMamPreferenceAlways()) { - trackOfflineMessageRetrieval = false; - sendIqPacket(account, mIqGenerator.purgeOfflineMessages(), (acc, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG, acc.getJid().asBareJid() + ": successfully purged offline messages"); - } - }); - } else { - trackOfflineMessageRetrieval = true; - } - sendPresence(account); - account.getXmppConnection().trackOfflineMessageRetrieval(trackOfflineMessageRetrieval); - if (mPushManagementService.available(account)) { - mPushManagementService.registerPushTokenOnServer(account); - } - connectMultiModeConversations(account); - syncDirtyContacts(account); - - unifiedPushBroker.renewUnifiedPushEndpointsOnBind(account); - } - }; private final AtomicLong mLastExpiryRun = new AtomicLong(0); private final LruCache, ServiceDiscoveryResult> discoCache = new LruCache<>(20); @@ -1636,12 +1552,8 @@ private void scheduleNextIdlePing() { public XmppConnection createConnection(final Account account) { final XmppConnection connection = new XmppConnection(account, this); - connection.setOnMessagePacketReceivedListener(this.mMessageParser); connection.setOnStatusChangedListener(this.statusListener); - connection.setOnPresencePacketReceivedListener(this.mPresenceParser); - connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser); connection.setOnJinglePacketReceivedListener((mJingleConnectionManager::deliverPacket)); - connection.setOnBindListener(this.mOnBindListener); connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService); connection.addOnAdvancedStreamFeaturesAvailableListener(this.mAvatarService); @@ -1654,7 +1566,7 @@ public XmppConnection createConnection(final Account account) { public void sendChatState(Conversation conversation) { if (sendChatStates()) { - MessagePacket packet = mMessageGenerator.generateChatState(conversation); + final var packet = mMessageGenerator.generateChatState(conversation); sendMessagePacket(conversation.getAccount(), packet); } } @@ -1692,7 +1604,7 @@ private void sendMessage(final Message message, final boolean resend, final bool } } - MessagePacket packet = null; + im.conversations.android.xmpp.model.stanza.Message packet = null; final boolean addToConversation = !message.edited(); boolean saveInDb = addToConversation; message.setStatus(Message.STATUS_WAITING); @@ -1866,13 +1778,13 @@ public void requestEasyOnboardingInvite(final Account account, final EasyOnboard callback.inviteRequestFailed(getString(R.string.server_does_not_support_easy_onboarding_invites)); return; } - final IqPacket request = new IqPacket(IqPacket.TYPE.SET); + final Iq request = new Iq(Iq.Type.SET); request.setTo(jid); final Element command = request.addChild("command", Namespace.COMMANDS); command.setAttribute("node", Namespace.EASY_ONBOARDING_INVITE); command.setAttribute("action", "execute"); - sendIqPacket(account, request, (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + sendIqPacket(account, request, (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element resultCommand = response.findChild("command", Namespace.COMMANDS); final Element x = resultCommand == null ? null : resultCommand.findChild("x", Namespace.DATA); if (x != null) { @@ -1887,7 +1799,7 @@ public void requestEasyOnboardingInvite(final Account account, final EasyOnboard } callback.inviteRequestFailed(getString(R.string.unable_to_parse_invite)); Log.d(Config.LOGTAG, response.toString()); - } else if (response.getType() == IqPacket.TYPE.ERROR) { + } else if (response.getType() == Iq.Type.ERROR) { callback.inviteRequestFailed(IqParser.errorMessage(response)); } else { callback.inviteRequestFailed(getString(R.string.remote_server_timeout)); @@ -1896,54 +1808,42 @@ public void requestEasyOnboardingInvite(final Account account, final EasyOnboard } - public void fetchRosterFromServer(final Account account) { - final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET); - if (!"".equals(account.getRosterVersion())) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() - + ": fetching roster version " + account.getRosterVersion()); - } else { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching roster"); - } - iqPacket.query(Namespace.ROSTER).setAttribute("ver", account.getRosterVersion()); - sendIqPacket(account, iqPacket, mIqParser); - } - public void fetchBookmarks(final Account account) { - final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET); + final Iq iqPacket = new Iq(Iq.Type.GET); final Element query = iqPacket.query("jabber:iq:private"); query.addChild("storage", Namespace.BOOKMARKS); - final OnIqPacketReceived callback = (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + final Consumer callback = (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element query1 = response.query(); final Element storage = query1.findChild("storage", "storage:bookmarks"); Map bookmarks = Bookmark.parseFromStorage(storage, account); - processBookmarksInitial(a, bookmarks, false); + processBookmarksInitial(account, bookmarks, false); } else { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": could not fetch bookmarks"); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not fetch bookmarks"); } }; sendIqPacket(account, iqPacket, callback); } public void fetchBookmarks2(final Account account) { - final IqPacket retrieve = mIqGenerator.retrieveBookmarks(); - sendIqPacket(account, retrieve, (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + final Iq retrieve = mIqGenerator.retrieveBookmarks(); + sendIqPacket(account, retrieve, (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element pubsub = response.findChild("pubsub", Namespace.PUBSUB); - final Map bookmarks = Bookmark.parseFromPubsub(pubsub, a); - processBookmarksInitial(a, bookmarks, true); + final Map bookmarks = Bookmark.parseFromPubsub(pubsub, account); + processBookmarksInitial(account, bookmarks, true); } }); } - private void fetchMessageDisplayedSynchronization(final Account account) { + public void fetchMessageDisplayedSynchronization(final Account account) { Log.d(Config.LOGTAG, account.getJid() + ": retrieve mds"); final var retrieve = mIqGenerator.retrieveMds(); sendIqPacket( account, retrieve, - (a, response) -> { - if (response.getType() != IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() != Iq.Type.RESULT) { return; } final var pubSub = response.findChild("pubsub", Namespace.PUBSUB); @@ -2096,11 +1996,11 @@ public void deleteBookmark(final Account account, final Bookmark bookmark) { account.removeBookmark(bookmark); final XmppConnection connection = account.getXmppConnection(); if (connection.getFeatures().bookmarks2()) { - final IqPacket request = mIqGenerator.deleteItem(Namespace.BOOKMARKS2, bookmark.getJid().asBareJid().toEscapedString()); + final Iq request = mIqGenerator.deleteItem(Namespace.BOOKMARKS2, bookmark.getJid().asBareJid().toEscapedString()); Log.d(Config.LOGTAG,account.getJid().asBareJid() + ": removing bookmark via Bookmarks 2"); - sendIqPacket(account, request, (a, response) -> { - if (response.getType() == IqPacket.TYPE.ERROR) { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": unable to delete bookmark " + response.getErrorCondition()); + sendIqPacket(account, request, (response) -> { + if (response.getType() == Iq.Type.ERROR) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to delete bookmark " + response.getErrorCondition()); } }); } else if (connection.getFeatures().bookmarksConversion()) { @@ -2112,7 +2012,7 @@ public void deleteBookmark(final Account account, final Bookmark bookmark) { private void pushBookmarksPrivateXml(Account account) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": pushing bookmarks via private xml"); - IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET); + final Iq iqPacket = new Iq(Iq.Type.SET); Element query = iqPacket.query("jabber:iq:private"); Element storage = query.addChild("storage", "storage:bookmarks"); for (final Bookmark bookmark : account.getBookmarks()) { @@ -2137,9 +2037,9 @@ private void pushNodeAndEnforcePublishOptions(final Account account, final Strin } private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final String id, final Bundle options, final boolean retry) { - final IqPacket packet = mIqGenerator.publishElement(node, element, id, options); - sendIqPacket(account, packet, (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + final Iq packet = mIqGenerator.publishElement(node, element, id, options); + sendIqPacket(account, packet, (response) -> { + if (response.getType() == Iq.Type.RESULT) { return; } if (retry && PublishOptions.preconditionNotMet(response)) { @@ -2610,6 +2510,10 @@ public Optional renewUnifiedPushEndpoints() { return this.unifiedPushBroker.renewUnifiedPushEndpoints(null); } + public UnifiedPushBroker getUnifiedPushBroker() { + return this.unifiedPushBroker; + } + private void provisionAccount(final String address, final String password) { final Jid jid = Jid.ofEscaped(address); final Account account = new Account(jid, password); @@ -2708,12 +2612,12 @@ public boolean updateAccount(final Account account) { } public void updateAccountPasswordOnServer(final Account account, final String newPassword, final OnAccountPasswordChanged callback) { - final IqPacket iq = getIqGenerator().generateSetPassword(account, newPassword); - sendIqPacket(account, iq, (a, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { - a.setPassword(newPassword); - a.setOption(Account.OPTION_MAGIC_CREATE, false); - databaseBackend.updateAccount(a); + final Iq iq = getIqGenerator().generateSetPassword(account, newPassword); + sendIqPacket(account, iq, (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { + account.setPassword(newPassword); + account.setOption(Account.OPTION_MAGIC_CREATE, false); + databaseBackend.updateAccount(account); callback.onPasswordChangeSucceeded(); } else { callback.onPasswordChangeFailed(); @@ -2722,12 +2626,12 @@ public void updateAccountPasswordOnServer(final Account account, final String ne } public void unregisterAccount(final Account account, final Consumer callback) { - final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET); + final Iq iqPacket = new Iq(Iq.Type.SET); final Element query = iqPacket.addChild("query",Namespace.REGISTER); query.addChild("remove"); - sendIqPacket(account, iqPacket, (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { - deleteAccount(a); + sendIqPacket(account, iqPacket, (response) -> { + if (response.getType() == Iq.Type.RESULT) { + deleteAccount(account); callback.accept(true); } else { callback.accept(false); @@ -3055,7 +2959,7 @@ private void switchToBackground() { Log.d(Config.LOGTAG, "app switched into background"); } - private void connectMultiModeConversations(Account account) { + public void connectMultiModeConversations(Account account) { List conversations = getConversations(); for (Conversation conversation : conversations) { if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getAccount() == account) { @@ -3079,20 +2983,20 @@ public void mucSelfPingAndRejoin(final Conversation conversation) { } } final Jid self = conversation.getMucOptions().getSelf().getFullJid(); - final IqPacket ping = new IqPacket(IqPacket.TYPE.GET); + final Iq ping = new Iq(Iq.Type.GET); ping.setTo(self); ping.addChild("ping", Namespace.PING); - sendIqPacket(conversation.getAccount(), ping, (a, response) -> { - if (response.getType() == IqPacket.TYPE.ERROR) { - Element error = response.findChild("error"); + sendIqPacket(conversation.getAccount(), ping, (response) -> { + if (response.getType() == Iq.Type.ERROR) { + final var error = response.getError(); if (error == null || error.hasChild("service-unavailable") || error.hasChild("feature-not-implemented") || error.hasChild("item-not-found")) { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": ping to " + self + " came back as ignorable error"); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ping to " + self + " came back as ignorable error"); } else { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": ping to " + self + " failed. attempting rejoin"); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ping to " + self + " failed. attempting rejoin"); joinMuc(conversation); } - } else if (response.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": ping to " + self + " came back fine"); + } else if (response.getType() == Iq.Type.RESULT) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ping to " + self + " came back fine"); } synchronized (account.inProgressConferencePings) { account.inProgressConferencePings.remove(conversation); @@ -3151,7 +3055,7 @@ private void join(Conversation conversation) { final Jid joinJid = mucOptions.getSelf().getFullJid(); Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": joining conversation " + joinJid.toString()); - PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous() || onConferenceJoined != null); + final var packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous() || onConferenceJoined != null); packet.setTo(joinJid); Element x = packet.addChild("x", "http://jabber.org/protocol/muc"); if (conversation.getMucOptions().getPassword() != null) { @@ -3240,16 +3144,16 @@ private void fetchConferenceMembers(final Conversation conversation) { final Account account = conversation.getAccount(); final AxolotlService axolotlService = account.getAxolotlService(); final String[] affiliations = {"member", "admin", "owner"}; - OnIqPacketReceived callback = new OnIqPacketReceived() { + final Consumer callback = new Consumer() { private int i = 0; private boolean success = true; @Override - public void onIqPacketReceived(Account account, IqPacket packet) { + public void accept(Iq response) { final boolean omemoEnabled = conversation.getNextEncryption() == Message.ENCRYPTION_AXOLOTL; - Element query = packet.query("http://jabber.org/protocol/muc#admin"); - if (packet.getType() == IqPacket.TYPE.RESULT && query != null) { + Element query = response.query("http://jabber.org/protocol/muc#admin"); + if (response.getType() == Iq.Type.RESULT && query != null) { for (Element child : query.getChildren()) { if ("item".equals(child.getName())) { MucOptions.User user = AbstractParser.parseItem(conversation, child); @@ -3335,29 +3239,29 @@ public void deletePepNode(final Account account, final String node) { } private void deletePepNode(final Account account, final String node, final Runnable runnable) { - final IqPacket request = mIqGenerator.deleteNode(node); - sendIqPacket(account, request, (a, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG,a.getJid().asBareJid()+": successfully deleted pep node "+node); + final Iq request = mIqGenerator.deleteNode(node); + sendIqPacket(account, request, (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": successfully deleted pep node "+node); if (runnable != null) { runnable.run(); } } else { - Log.d(Config.LOGTAG,a.getJid().asBareJid()+": failed to delete "+ packet); + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": failed to delete "+ packet); } }); } private void deleteVcardAvatar(final Account account, @NonNull final Runnable runnable) { - final IqPacket retrieveVcard = mIqGenerator.retrieveVcardAvatar(account.getJid().asBareJid()); - sendIqPacket(account, retrieveVcard, (a, response) -> { - if (response.getType() != IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG,a.getJid().asBareJid()+": no vCard set. nothing to do"); + final Iq retrieveVcard = mIqGenerator.retrieveVcardAvatar(account.getJid().asBareJid()); + sendIqPacket(account, retrieveVcard, (response) -> { + if (response.getType() != Iq.Type.RESULT) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": no vCard set. nothing to do"); return; } final Element vcard = response.findChild("vCard", "vcard-temp"); if (vcard == null) { - Log.d(Config.LOGTAG,a.getJid().asBareJid()+": no vCard set. nothing to do"); + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": no vCard set. nothing to do"); return; } Element photo = vcard.findChild("PHOTO"); @@ -3365,12 +3269,12 @@ private void deleteVcardAvatar(final Account account, @NonNull final Runnable ru photo = vcard.addChild("PHOTO"); } photo.clearChildren(); - IqPacket publication = new IqPacket(IqPacket.TYPE.SET); - publication.setTo(a.getJid().asBareJid()); + final Iq publication = new Iq(Iq.Type.SET); + publication.setTo(account.getJid().asBareJid()); publication.addChild(vcard); - sendIqPacket(account, publication, (a1, publicationResponse) -> { - if (publicationResponse.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG,a1.getJid().asBareJid()+": successfully deleted vcard avatar"); + sendIqPacket(account, publication, (publicationResponse) -> { + if (publicationResponse.getType() == Iq.Type.RESULT) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": successfully deleted vcard avatar"); runnable.run(); } else { Log.d(Config.LOGTAG, "failed to publish vcard " + publicationResponse.getErrorCondition()); @@ -3450,7 +3354,7 @@ public void onFailure() { } }); - final PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, options.nonanonymous()); + final var packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, options.nonanonymous()); packet.setTo(joinJid); sendPresencePacket(account, packet); } else { @@ -3610,39 +3514,37 @@ public void fetchConferenceConfiguration(final Conversation conversation) { } public void fetchConferenceConfiguration(final Conversation conversation, final OnConferenceConfigurationFetched callback) { - IqPacket request = mIqGenerator.queryDiscoInfo(conversation.getJid().asBareJid()); - sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - final MucOptions mucOptions = conversation.getMucOptions(); - final Bookmark bookmark = conversation.getBookmark(); - final boolean sameBefore = StringUtils.equals(bookmark == null ? null : bookmark.getBookmarkName(), mucOptions.getName()); + final Iq request = mIqGenerator.queryDiscoInfo(conversation.getJid().asBareJid()); + final var account = conversation.getAccount(); + sendIqPacket(account, request, response -> { + if (response.getType() == Iq.Type.RESULT) { + final MucOptions mucOptions = conversation.getMucOptions(); + final Bookmark bookmark = conversation.getBookmark(); + final boolean sameBefore = StringUtils.equals(bookmark == null ? null : bookmark.getBookmarkName(), mucOptions.getName()); - if (mucOptions.updateConfiguration(new ServiceDiscoveryResult(packet))) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": muc configuration changed for " + conversation.getJid().asBareJid()); - updateConversation(conversation); - } + if (mucOptions.updateConfiguration(new ServiceDiscoveryResult(response))) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": muc configuration changed for " + conversation.getJid().asBareJid()); + updateConversation(conversation); + } - if (bookmark != null && (sameBefore || bookmark.getBookmarkName() == null)) { - if (bookmark.setBookmarkName(StringUtils.nullOnEmpty(mucOptions.getName()))) { - createBookmark(account, bookmark); - } + if (bookmark != null && (sameBefore || bookmark.getBookmarkName() == null)) { + if (bookmark.setBookmarkName(StringUtils.nullOnEmpty(mucOptions.getName()))) { + createBookmark(account, bookmark); } + } - if (callback != null) { - callback.onConferenceConfigurationFetched(conversation); - } + if (callback != null) { + callback.onConferenceConfigurationFetched(conversation); + } - updateConversationUi(); - } else if (packet.getType() == IqPacket.TYPE.TIMEOUT) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received timeout waiting for conference configuration fetch"); - } else { - if (callback != null) { - callback.onFetchFailed(conversation, packet.getErrorCondition()); - } + updateConversationUi(); + } else if (response.getType() == Iq.Type.TIMEOUT) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received timeout waiting for conference configuration fetch"); + } else { + if (callback != null) { + callback.onFetchFailed(conversation, response.getErrorCondition()); } } }); @@ -3654,33 +3556,27 @@ public void pushNodeConfiguration(Account account, final String node, final Bund public void pushNodeConfiguration(Account account, final Jid jid, final String node, final Bundle options, final OnConfigurationPushed callback) { Log.d(Config.LOGTAG, "pushing node configuration"); - sendIqPacket(account, mIqGenerator.requestPubsubConfiguration(jid, node), new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub#owner"); - Element configuration = pubsub == null ? null : pubsub.findChild("configure"); - Element x = configuration == null ? null : configuration.findChild("x", Namespace.DATA); - if (x != null) { - final Data data = Data.parse(x); - data.submit(options); - sendIqPacket(account, mIqGenerator.publishPubsubConfiguration(jid, node, data), new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT && callback != null) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully changed node configuration for node " + node); - callback.onPushSucceeded(); - } else if (packet.getType() == IqPacket.TYPE.ERROR && callback != null) { - callback.onPushFailed(); - } - } - }); - } else if (callback != null) { - callback.onPushFailed(); - } - } else if (packet.getType() == IqPacket.TYPE.ERROR && callback != null) { + sendIqPacket(account, mIqGenerator.requestPubsubConfiguration(jid, node), responseToRequest -> { + if (responseToRequest.getType() == Iq.Type.RESULT) { + Element pubsub = responseToRequest.findChild("pubsub", "http://jabber.org/protocol/pubsub#owner"); + Element configuration = pubsub == null ? null : pubsub.findChild("configure"); + Element x = configuration == null ? null : configuration.findChild("x", Namespace.DATA); + if (x != null) { + final Data data = Data.parse(x); + data.submit(options); + sendIqPacket(account, mIqGenerator.publishPubsubConfiguration(jid, node, data), responseToPublish -> { + if (responseToPublish.getType() == Iq.Type.RESULT && callback != null) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully changed node configuration for node " + node); + callback.onPushSucceeded(); + } else if (responseToPublish.getType() == Iq.Type.ERROR && callback != null) { + callback.onPushFailed(); + } + }); + } else if (callback != null) { callback.onPushFailed(); } + } else if (responseToRequest.getType() == Iq.Type.ERROR && callback != null) { + callback.onPushFailed(); } }); } @@ -3700,50 +3596,45 @@ public void pushConferenceConfiguration(final Conversation conversation, final B options.putString("allow_private_messages", allow ? "1" : "0"); options.putString("allow_private_messages_from_visitors", allow ? "anyone" : "nobody"); } - final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final var account = conversation.getAccount(); + final Iq request = new Iq(Iq.Type.GET); request.setTo(conversation.getJid().asBareJid()); request.query("http://jabber.org/protocol/muc#owner"); - sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - final Data data = Data.parse(packet.query().findChild("x", Namespace.DATA)); - data.submit(options); - final IqPacket set = new IqPacket(IqPacket.TYPE.SET); - set.setTo(conversation.getJid().asBareJid()); - set.query("http://jabber.org/protocol/muc#owner").addChild(data); - sendIqPacket(account, set, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (callback != null) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - callback.onPushSucceeded(); - } else { - Log.d(Config.LOGTAG,"failed: "+packet.toString()); - callback.onPushFailed(); - } - } - } - }); - } else { + sendIqPacket(account, request, response -> { + if (response.getType() == Iq.Type.RESULT) { + final Data data = Data.parse(response.query().findChild("x", Namespace.DATA)); + data.submit(options); + final Iq set = new Iq(Iq.Type.SET); + set.setTo(conversation.getJid().asBareJid()); + set.query("http://jabber.org/protocol/muc#owner").addChild(data); + sendIqPacket(account, set, packet -> { if (callback != null) { - callback.onPushFailed(); + if (packet.getType() == Iq.Type.RESULT) { + callback.onPushSucceeded(); + } else { + Log.d(Config.LOGTAG,"failed: "+packet.toString()); + callback.onPushFailed(); + } } + }); + } else { + if (callback != null) { + callback.onPushFailed(); } } }); } public void pushSubjectToConference(final Conversation conference, final String subject) { - MessagePacket packet = this.getMessageGenerator().conferenceSubject(conference, StringUtils.nullOnEmpty(subject)); + final var packet = this.getMessageGenerator().conferenceSubject(conference, StringUtils.nullOnEmpty(subject)); this.sendMessagePacket(conference.getAccount(), packet); } public void changeAffiliationInConference(final Conversation conference, Jid user, final MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) { final Jid jid = user.asBareJid(); - final IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString()); - sendIqPacket(conference.getAccount(), request, (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + final Iq request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString()); + sendIqPacket(conference.getAccount(), request, (response) -> { + if (response.getType() == Iq.Type.RESULT) { conference.getMucOptions().changeAffiliation(jid, affiliation); getAvatarService().clear(conference); if (callback != null) { @@ -3760,29 +3651,27 @@ public void changeAffiliationInConference(final Conversation conference, Jid use } public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role) { - IqPacket request = this.mIqGenerator.changeRole(conference, nick, role.toString()); - sendIqPacket(conference.getAccount(), request, (account, packet) -> { - if (packet.getType() != IqPacket.TYPE.RESULT) { + final var account =conference.getAccount(); + final Iq request = this.mIqGenerator.changeRole(conference, nick, role.toString()); + sendIqPacket(account, request, (packet) -> { + if (packet.getType() != Iq.Type.RESULT) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + " unable to change role of " + nick); } }); } public void destroyRoom(final Conversation conversation, final OnRoomDestroy callback) { - IqPacket request = new IqPacket(IqPacket.TYPE.SET); + final Iq request = new Iq(Iq.Type.SET); request.setTo(conversation.getJid().asBareJid()); request.query("http://jabber.org/protocol/muc#owner").addChild("destroy"); - sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - if (callback != null) { - callback.onRoomDestroySucceeded(); - } - } else if (packet.getType() == IqPacket.TYPE.ERROR) { - if (callback != null) { - callback.onRoomDestroyFailed(); - } + sendIqPacket(conversation.getAccount(), request, response -> { + if (response.getType() == Iq.Type.RESULT) { + if (callback != null) { + callback.onRoomDestroySucceeded(); + } + } else if (response.getType() == Iq.Type.ERROR) { + if (callback != null) { + callback.onRoomDestroyFailed(); } } }); @@ -3832,7 +3721,7 @@ public void updateMessage(Message message, String uuid) { updateConversationUi(); } - protected void syncDirtyContacts(Account account) { + public void syncDirtyContacts(Account account) { for (Contact contact : account.getRoster().getContacts()) { if (contact.getOption(Contact.Options.DIRTY_PUSH)) { pushContactToServer(contact); @@ -3868,7 +3757,7 @@ private void pushContactToServer(final Contact contact, final String preAuth) { final boolean sendUpdates = contact .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST) && contact.getOption(Contact.Options.PREEMPTIVE_GRANT); - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + final Iq iq = new Iq(Iq.Type.SET); iq.query(Namespace.ROSTER).addChild(contact.asElement()); account.getXmppConnection().sendIqPacket(iq, mDefaultIqHandler); if (sendUpdates) { @@ -3920,10 +3809,11 @@ public void publishAvatar(final Account account, final Uri image, final OnAvatar } private void publishMucAvatar(Conversation conversation, Avatar avatar, OnAvatarPublication callback) { - final IqPacket retrieve = mIqGenerator.retrieveVcardAvatar(avatar); - sendIqPacket(conversation.getAccount(), retrieve, (account, response) -> { - boolean itemNotFound = response.getType() == IqPacket.TYPE.ERROR && response.hasChild("error") && response.findChild("error").hasChild("item-not-found"); - if (response.getType() == IqPacket.TYPE.RESULT || itemNotFound) { + final var account = conversation.getAccount(); + final Iq retrieve = mIqGenerator.retrieveVcardAvatar(avatar); + sendIqPacket(account, retrieve, (response) -> { + boolean itemNotFound = response.getType() == Iq.Type.ERROR && response.hasChild("error") && response.findChild("error").hasChild("item-not-found"); + if (response.getType() == Iq.Type.RESULT || itemNotFound) { Element vcard = response.findChild("vCard", "vcard-temp"); if (vcard == null) { vcard = new Element("vCard", "vcard-temp"); @@ -3935,11 +3825,11 @@ private void publishMucAvatar(Conversation conversation, Avatar avatar, OnAvatar photo.clearChildren(); photo.addChild("TYPE").setContent(avatar.type); photo.addChild("BINVAL").setContent(avatar.image); - IqPacket publication = new IqPacket(IqPacket.TYPE.SET); + final Iq publication = new Iq(Iq.Type.SET); publication.setTo(conversation.getJid().asBareJid()); publication.addChild(vcard); - sendIqPacket(account, publication, (a1, publicationResponse) -> { - if (publicationResponse.getType() == IqPacket.TYPE.RESULT) { + sendIqPacket(account, publication, (publicationResponse) -> { + if (publicationResponse.getType() == Iq.Type.RESULT) { callback.onAvatarPublicationSucceeded(); } else { Log.d(Config.LOGTAG, "failed to publish vcard " + publicationResponse.getErrorCondition()); @@ -3965,71 +3855,64 @@ public void publishAvatar(Account account, final Avatar avatar, final OnAvatarPu public void publishAvatar(Account account, final Avatar avatar, final Bundle options, final boolean retry, final OnAvatarPublication callback) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": publishing avatar. options=" + options); - IqPacket packet = this.mIqGenerator.publishAvatar(avatar, options); - this.sendIqPacket(account, packet, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, IqPacket result) { - if (result.getType() == IqPacket.TYPE.RESULT) { - publishAvatarMetadata(account, avatar, options, true, callback); - } else if (retry && PublishOptions.preconditionNotMet(result)) { - pushNodeConfiguration(account, Namespace.AVATAR_DATA, options, new OnConfigurationPushed() { - @Override - public void onPushSucceeded() { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": changed node configuration for avatar node"); - publishAvatar(account, avatar, options, false, callback); - } + final Iq packet = this.mIqGenerator.publishAvatar(avatar, options); + this.sendIqPacket(account, packet, result -> { + if (result.getType() == Iq.Type.RESULT) { + publishAvatarMetadata(account, avatar, options, true, callback); + } else if (retry && PublishOptions.preconditionNotMet(result)) { + pushNodeConfiguration(account, Namespace.AVATAR_DATA, options, new OnConfigurationPushed() { + @Override + public void onPushSucceeded() { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": changed node configuration for avatar node"); + publishAvatar(account, avatar, options, false, callback); + } - @Override - public void onPushFailed() { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to change node configuration for avatar node"); - publishAvatar(account, avatar, null, false, callback); - } - }); - } else { - Element error = result.findChild("error"); - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server rejected avatar " + (avatar.size / 1024) + "KiB " + (error != null ? error.toString() : "")); - if (callback != null) { - callback.onAvatarPublicationFailed(R.string.error_publish_avatar_server_reject); + @Override + public void onPushFailed() { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to change node configuration for avatar node"); + publishAvatar(account, avatar, null, false, callback); } + }); + } else { + Element error = result.findChild("error"); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server rejected avatar " + (avatar.size / 1024) + "KiB " + (error != null ? error.toString() : "")); + if (callback != null) { + callback.onAvatarPublicationFailed(R.string.error_publish_avatar_server_reject); } } }); } public void publishAvatarMetadata(Account account, final Avatar avatar, final Bundle options, final boolean retry, final OnAvatarPublication callback) { - final IqPacket packet = XmppConnectionService.this.mIqGenerator.publishAvatarMetadata(avatar, options); - sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket result) { - if (result.getType() == IqPacket.TYPE.RESULT) { - if (account.setAvatar(avatar.getFilename())) { - getAvatarService().clear(account); - databaseBackend.updateAccount(account); - notifyAccountAvatarHasChanged(account); - } - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": published avatar " + (avatar.size / 1024) + "KiB"); - if (callback != null) { - callback.onAvatarPublicationSucceeded(); + final Iq packet = XmppConnectionService.this.mIqGenerator.publishAvatarMetadata(avatar, options); + sendIqPacket(account, packet, result -> { + if (result.getType() == Iq.Type.RESULT) { + if (account.setAvatar(avatar.getFilename())) { + getAvatarService().clear(account); + databaseBackend.updateAccount(account); + notifyAccountAvatarHasChanged(account); + } + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": published avatar " + (avatar.size / 1024) + "KiB"); + if (callback != null) { + callback.onAvatarPublicationSucceeded(); + } + } else if (retry && PublishOptions.preconditionNotMet(result)) { + pushNodeConfiguration(account, Namespace.AVATAR_METADATA, options, new OnConfigurationPushed() { + @Override + public void onPushSucceeded() { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": changed node configuration for avatar meta data node"); + publishAvatarMetadata(account, avatar, options, false, callback); } - } else if (retry && PublishOptions.preconditionNotMet(result)) { - pushNodeConfiguration(account, Namespace.AVATAR_METADATA, options, new OnConfigurationPushed() { - @Override - public void onPushSucceeded() { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": changed node configuration for avatar meta data node"); - publishAvatarMetadata(account, avatar, options, false, callback); - } - @Override - public void onPushFailed() { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to change node configuration for avatar meta data node"); - publishAvatarMetadata(account, avatar, null, false, callback); - } - }); - } else { - if (callback != null) { - callback.onAvatarPublicationFailed(R.string.error_publish_avatar_server_reject); + @Override + public void onPushFailed() { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to change node configuration for avatar meta data node"); + publishAvatarMetadata(account, avatar, null, false, callback); } + }); + } else { + if (callback != null) { + callback.onAvatarPublicationFailed(R.string.error_publish_avatar_server_reject); } } }); @@ -4040,10 +3923,10 @@ public void republishAvatarIfNeeded(Account account) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping republication of avatar because pep is broken"); return; } - IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); - this.sendIqPacket(account, packet, new OnIqPacketReceived() { + final Iq packet = this.mIqGenerator.retrieveAvatarMetaData(null); + this.sendIqPacket(account, packet, new Consumer() { - private Avatar parseAvatar(IqPacket packet) { + private Avatar parseAvatar(Iq packet) { Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub"); if (pubsub != null) { Element items = pubsub.findChild("items"); @@ -4054,16 +3937,16 @@ private Avatar parseAvatar(IqPacket packet) { return null; } - private boolean errorIsItemNotFound(IqPacket packet) { + private boolean errorIsItemNotFound(Iq packet) { Element error = packet.findChild("error"); - return packet.getType() == IqPacket.TYPE.ERROR + return packet.getType() == Iq.Type.ERROR && error != null && error.hasChild("item-not-found"); } @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT || errorIsItemNotFound(packet)) { + public void accept(final Iq packet) { + if (packet.getType() == Iq.Type.RESULT || errorIsItemNotFound(packet)) { Avatar serverAvatar = parseAvatar(packet); if (serverAvatar == null && account.getAvatar() != null) { Avatar avatar = fileBackend.getStoredPepAvatar(account.getAvatar()); @@ -4079,6 +3962,17 @@ public void onIqPacketReceived(Account account, IqPacket packet) { }); } + public void cancelAvatarFetches(final Account account) { + synchronized (mInProgressAvatarFetches) { + for (final Iterator iterator = mInProgressAvatarFetches.iterator(); iterator.hasNext(); ) { + final String KEY = iterator.next(); + if (KEY.startsWith(account.getJid().asBareJid() + "_")) { + iterator.remove(); + } + } + } + } + public void fetchAvatar(Account account, Avatar avatar) { fetchAvatar(account, avatar, null); } @@ -4105,26 +3999,26 @@ public void fetchAvatar(Account account, final Avatar avatar, final UiCallback callback) { - IqPacket packet = this.mIqGenerator.retrievePepAvatar(avatar); - sendIqPacket(account, packet, (a, result) -> { + private void fetchAvatarPep(final Account account, final Avatar avatar, final UiCallback callback) { + final Iq packet = this.mIqGenerator.retrievePepAvatar(avatar); + sendIqPacket(account, packet, (result) -> { synchronized (mInProgressAvatarFetches) { - mInProgressAvatarFetches.remove(generateFetchKey(a, avatar)); + mInProgressAvatarFetches.remove(generateFetchKey(account, avatar)); } - final String ERROR = a.getJid().asBareJid() + ": fetching avatar for " + avatar.owner + " failed "; - if (result.getType() == IqPacket.TYPE.RESULT) { - avatar.image = mIqParser.avatarData(result); + final String ERROR = account.getJid().asBareJid() + ": fetching avatar for " + avatar.owner + " failed "; + if (result.getType() == Iq.Type.RESULT) { + avatar.image = IqParser.avatarData(result); if (avatar.image != null) { if (getFileBackend().save(avatar)) { - if (a.getJid().asBareJid().equals(avatar.owner)) { - if (a.setAvatar(avatar.getFilename())) { - databaseBackend.updateAccount(a); + if (account.getJid().asBareJid().equals(avatar.owner)) { + if (account.setAvatar(avatar.getFilename())) { + databaseBackend.updateAccount(account); } - getAvatarService().clear(a); + getAvatarService().clear(account); updateConversationUi(); updateAccountUi(); } else { - final Contact contact = a.getRoster().getContact(avatar.owner); + final Contact contact = account.getRoster().getContact(avatar.owner); contact.setAvatar(avatar); syncRoster(account); getAvatarService().clear(contact); @@ -4134,7 +4028,7 @@ private void fetchAvatarPep(Account account, final Avatar avatar, final UiCallba if (callback != null) { callback.success(avatar); } - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully fetched pep avatar for " + avatar.owner); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully fetched pep avatar for " + avatar.owner); return; } } else { @@ -4157,57 +4051,54 @@ private void fetchAvatarPep(Account account, final Avatar avatar, final UiCallba } private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback callback) { - IqPacket packet = this.mIqGenerator.retrieveVcardAvatar(avatar); - this.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - final boolean previouslyOmittedPepFetch; - synchronized (mInProgressAvatarFetches) { - final String KEY = generateFetchKey(account, avatar); - mInProgressAvatarFetches.remove(KEY); - previouslyOmittedPepFetch = mOmittedPepAvatarFetches.remove(KEY); - } - if (packet.getType() == IqPacket.TYPE.RESULT) { - Element vCard = packet.findChild("vCard", "vcard-temp"); - Element photo = vCard != null ? vCard.findChild("PHOTO") : null; - String image = photo != null ? photo.findChildContent("BINVAL") : null; - if (image != null) { - avatar.image = image; - if (getFileBackend().save(avatar)) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() - + ": successfully fetched vCard avatar for " + avatar.owner + " omittedPep=" + previouslyOmittedPepFetch); - if (avatar.owner.isBareJid()) { - if (account.getJid().asBareJid().equals(avatar.owner) && account.getAvatar() == null) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": had no avatar. replacing with vcard"); - account.setAvatar(avatar.getFilename()); - databaseBackend.updateAccount(account); - getAvatarService().clear(account); - updateAccountUi(); - } else { - final Contact contact = account.getRoster().getContact(avatar.owner); - contact.setAvatar(avatar, previouslyOmittedPepFetch); - syncRoster(account); - getAvatarService().clear(contact); - updateRosterUi(); - } - updateConversationUi(); + final Iq packet = this.mIqGenerator.retrieveVcardAvatar(avatar); + this.sendIqPacket(account, packet, response -> { + final boolean previouslyOmittedPepFetch; + synchronized (mInProgressAvatarFetches) { + final String KEY = generateFetchKey(account, avatar); + mInProgressAvatarFetches.remove(KEY); + previouslyOmittedPepFetch = mOmittedPepAvatarFetches.remove(KEY); + } + if (response.getType() == Iq.Type.RESULT) { + Element vCard = response.findChild("vCard", "vcard-temp"); + Element photo = vCard != null ? vCard.findChild("PHOTO") : null; + String image = photo != null ? photo.findChildContent("BINVAL") : null; + if (image != null) { + avatar.image = image; + if (getFileBackend().save(avatar)) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + + ": successfully fetched vCard avatar for " + avatar.owner + " omittedPep=" + previouslyOmittedPepFetch); + if (avatar.owner.isBareJid()) { + if (account.getJid().asBareJid().equals(avatar.owner) && account.getAvatar() == null) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": had no avatar. replacing with vcard"); + account.setAvatar(avatar.getFilename()); + databaseBackend.updateAccount(account); + getAvatarService().clear(account); + updateAccountUi(); } else { - Conversation conversation = find(account, avatar.owner.asBareJid()); - if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { - MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner); - if (user != null) { - if (user.setAvatar(avatar)) { - getAvatarService().clear(user); - updateConversationUi(); - updateMucRosterUi(); - } - if (user.getRealJid() != null) { - Contact contact = account.getRoster().getContact(user.getRealJid()); - contact.setAvatar(avatar); - syncRoster(account); - getAvatarService().clear(contact); - updateRosterUi(); - } + final Contact contact = account.getRoster().getContact(avatar.owner); + contact.setAvatar(avatar, previouslyOmittedPepFetch); + syncRoster(account); + getAvatarService().clear(contact); + updateRosterUi(); + } + updateConversationUi(); + } else { + Conversation conversation = find(account, avatar.owner.asBareJid()); + if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { + MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner); + if (user != null) { + if (user.setAvatar(avatar)) { + getAvatarService().clear(user); + updateConversationUi(); + updateMucRosterUi(); + } + if (user.getRealJid() != null) { + Contact contact = account.getRoster().getContact(user.getRealJid()); + contact.setAvatar(avatar); + syncRoster(account); + getAvatarService().clear(contact); + updateRosterUi(); } } } @@ -4218,36 +4109,32 @@ public void onIqPacketReceived(Account account, IqPacket packet) { }); } - public void checkForAvatar(Account account, final UiCallback callback) { - IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); - this.sendIqPacket(account, packet, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub"); - if (pubsub != null) { - Element items = pubsub.findChild("items"); - if (items != null) { - Avatar avatar = Avatar.parseMetadata(items); - if (avatar != null) { - avatar.owner = account.getJid().asBareJid(); - if (fileBackend.isAvatarCached(avatar)) { - if (account.setAvatar(avatar.getFilename())) { - databaseBackend.updateAccount(account); - } - getAvatarService().clear(account); - callback.success(avatar); - } else { - fetchAvatarPep(account, avatar, callback); + public void checkForAvatar(final Account account, final UiCallback callback) { + final Iq packet = this.mIqGenerator.retrieveAvatarMetaData(null); + this.sendIqPacket(account, packet, response -> { + if (response.getType() == Iq.Type.RESULT) { + Element pubsub = response.findChild("pubsub", "http://jabber.org/protocol/pubsub"); + if (pubsub != null) { + Element items = pubsub.findChild("items"); + if (items != null) { + Avatar avatar = Avatar.parseMetadata(items); + if (avatar != null) { + avatar.owner = account.getJid().asBareJid(); + if (fileBackend.isAvatarCached(avatar)) { + if (account.setAvatar(avatar.getFilename())) { + databaseBackend.updateAccount(account); } - return; + getAvatarService().clear(account); + callback.success(avatar); + } else { + fetchAvatarPep(account, avatar, callback); } + return; } } } - callback.error(0, null); } + callback.error(0, null); }); } @@ -4259,7 +4146,7 @@ public void notifyAccountAvatarHasChanged(final Account account) { if (conversation.getAccount() == account && conversation.getMode() == Conversational.MODE_MULTI) { final MucOptions mucOptions = conversation.getMucOptions(); if (mucOptions.online()) { - PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous()); + final var packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous()); packet.setTo(mucOptions.getSelf().getFullJid()); connection.sendPresencePacket(packet); } @@ -4274,7 +4161,7 @@ public void deleteContactOnServer(Contact contact) { contact.setOption(Contact.Options.DIRTY_DELETE); Account account = contact.getAccount(); if (account.getStatus() == Account.State.ONLINE) { - IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + final Iq iq = new Iq(Iq.Type.SET); Element item = iq.query(Namespace.ROSTER).addChild("item"); item.setAttribute("jid", contact.getJid()); item.setAttribute("subscription", "remove"); @@ -4334,12 +4221,12 @@ public void invite(final Conversation conversation, final Jid contact) { if (user == null || user.getAffiliation() == MucOptions.Affiliation.OUTCAST) { changeAffiliationInConference(conversation, contact, MucOptions.Affiliation.NONE, null); } - final MessagePacket packet = mMessageGenerator.invite(conversation, contact); + final var packet = mMessageGenerator.invite(conversation, contact); sendMessagePacket(conversation.getAccount(), packet); } public void directInvite(Conversation conversation, Jid jid) { - MessagePacket packet = mMessageGenerator.directInvite(conversation, jid); + final var packet = mMessageGenerator.directInvite(conversation, jid); sendMessagePacket(conversation.getAccount(), packet); } @@ -4676,7 +4563,7 @@ public void sendReadMarker(final Conversation conversation, final String upToUui if (sendDisplayedMarker && serverAssist) { final var mdsDisplayed = mIqGenerator.mdsDisplayed(stanzaId, conversation); - final MessagePacket packet = mMessageGenerator.confirm(last); + final var packet = mMessageGenerator.confirm(last); packet.addChild(mdsDisplayed); if (!last.isPrivateMessage()) { packet.setTo(packet.getTo().asBareJid()); @@ -4692,7 +4579,7 @@ public void sendReadMarker(final Conversation conversation, final String upToUui conversation.getAccount().getJid().asBareJid() + ": sending displayed marker to " + last.getCounterpart().toString()); - final MessagePacket packet = mMessageGenerator.confirm(last); + final var packet = mMessageGenerator.confirm(last); this.sendMessagePacket(account, packet); } } @@ -4798,15 +4685,15 @@ public Collection getKnownConferenceHosts() { return mucServers; } - public void sendMessagePacket(Account account, MessagePacket packet) { + public void sendMessagePacket(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet) { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.sendMessagePacket(packet); } } - public void sendPresencePacket(Account account, PresencePacket packet) { - XmppConnection connection = account.getXmppConnection(); + public void sendPresencePacket(final Account account, final im.conversations.android.xmpp.model.stanza.Presence packet) { + final XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.sendPresencePacket(packet); } @@ -4814,18 +4701,18 @@ public void sendPresencePacket(Account account, PresencePacket packet) { public void sendCreateAccountWithCaptchaPacket(Account account, String id, Data data) { final XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - IqPacket request = mIqGenerator.generateCreateAccountWithCaptcha(account, id, data); - connection.sendUnmodifiedIqPacket(request, connection.registrationResponseListener, true); + if (connection == null) { + return; } + connection.sendCreateAccountWithCaptchaPacket(id, data); } - public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) { + public void sendIqPacket(final Account account, final Iq packet, final Consumer callback) { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.sendIqPacket(packet, callback); } else if (callback != null) { - callback.onIqPacketReceived(account, new IqPacket(IqPacket.TYPE.TIMEOUT)); + callback.accept(Iq.TIMEOUT); } } @@ -4840,7 +4727,7 @@ private void sendPresence(final Account account, final boolean includeIdleTimest } else { status = getTargetPresence(); } - final PresencePacket packet = mPresenceGenerator.selfPresence(account, status); + final var packet = mPresenceGenerator.selfPresence(account, status); if (mLastActivity > 0 && includeIdleTimestamp) { long since = Math.min(mLastActivity, System.currentTimeMillis()); //don't send future dates packet.addChild("idle", Namespace.IDLE).setAttribute("since", AbstractGenerator.getTimestamp(since)); @@ -4890,10 +4777,6 @@ public IqGenerator getIqGenerator() { return this.mIqGenerator; } - public IqParser getIqParser() { - return this.mIqParser; - } - public JingleConnectionManager getJingleConnectionManager() { return this.mJingleConnectionManager; } @@ -4986,10 +4869,11 @@ public void clearConversationHistory(final Conversation conversation) { public boolean sendBlockRequest(final Blockable blockable, final boolean reportSpam, final String serverMsgId) { if (blockable != null && blockable.getBlockedJid() != null) { + final var account = blockable.getAccount(); final Jid jid = blockable.getBlockedJid(); - this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid, reportSpam, serverMsgId), (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { - a.getBlocklist().add(jid); + this.sendIqPacket(account, getIqGenerator().generateSetBlockRequest(jid, reportSpam, serverMsgId), (response) -> { + if (response.getType() == Iq.Type.RESULT) { + account.getBlocklist().add(jid); updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); } }); @@ -5030,31 +4914,29 @@ public boolean removeBlockedConversations(final Account account, final Jid block public void sendUnblockRequest(final Blockable blockable) { if (blockable != null && blockable.getJid() != null) { + final var account = blockable.getAccount(); final Jid jid = blockable.getBlockedJid(); - this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetUnblockRequest(jid), new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.getBlocklist().remove(jid); - updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); - } + this.sendIqPacket(account, getIqGenerator().generateSetUnblockRequest(jid), response -> { + if (response.getType() == Iq.Type.RESULT) { + account.getBlocklist().remove(jid); + updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); } }); } } - public void publishDisplayName(Account account) { + public void publishDisplayName(final Account account) { String displayName = account.getDisplayName(); - final IqPacket request; + final Iq request; if (TextUtils.isEmpty(displayName)) { request = mIqGenerator.deleteNode(Namespace.NICK); } else { request = mIqGenerator.publishNick(displayName); } mAvatarService.clear(account); - sendIqPacket(account, request, (account1, packet) -> { - if (packet.getType() == IqPacket.TYPE.ERROR) { - Log.d(Config.LOGTAG, account1.getJid().asBareJid() + ": unable to modify nick name " + packet); + sendIqPacket(account, request, (packet) -> { + if (packet.getType() == Iq.Type.ERROR) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to modify nick name " + packet); } }); } @@ -5072,7 +4954,7 @@ public ServiceDiscoveryResult getCachedServiceDiscoveryResult(Pair key = new Pair<>(presence.getHash(), presence.getVer()); final ServiceDiscoveryResult disco = getCachedServiceDiscoveryResult(key); if (disco != null) { @@ -5082,7 +4964,7 @@ public void fetchCaps(Account account, final Jid jid, final Presence presence) { syncRoster(account); } } else { - final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.setTo(jid); final String node = presence.getNode(); final String ver = presence.getVer(); @@ -5091,14 +4973,14 @@ public void fetchCaps(Account account, final Jid jid, final Presence presence) { query.setAttribute("node", node + "#" + ver); } Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": making disco request for " + key.second + " to " + jid); - sendIqPacket(account, request, (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + sendIqPacket(account, request, (response) -> { + if (response.getType() == Iq.Type.RESULT) { final ServiceDiscoveryResult discoveryResult = new ServiceDiscoveryResult(response); if (presence.getVer().equals(discoveryResult.getVer())) { databaseBackend.insertDiscoveryResult(discoveryResult); - injectServiceDiscoveryResult(a.getRoster(), presence.getHash(), presence.getVer(), discoveryResult); + injectServiceDiscoveryResult(account.getRoster(), presence.getHash(), presence.getVer(), discoveryResult); } else { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + discoveryResult.getVer()); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + discoveryResult.getVer()); } } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to fetch caps from " + jid); @@ -5126,13 +5008,13 @@ private void injectServiceDiscoveryResult(Roster roster, String hash, String ver } } - public void fetchMamPreferences(Account account, final OnMamPreferencesFetched callback) { + public void fetchMamPreferences(final Account account, final OnMamPreferencesFetched callback) { final MessageArchiveService.Version version = MessageArchiveService.Version.get(account); - IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.addChild("prefs", version.namespace); - sendIqPacket(account, request, (account1, packet) -> { - Element prefs = packet.findChild("prefs", version.namespace); - if (packet.getType() == IqPacket.TYPE.RESULT && prefs != null) { + sendIqPacket(account, request, (packet) -> { + final Element prefs = packet.findChild("prefs", version.namespace); + if (packet.getType() == Iq.Type.RESULT && prefs != null) { callback.onPreferencesFetched(prefs); } else { callback.onPreferencesFetchFailed(); @@ -5231,7 +5113,7 @@ public ShortcutService getShortcutService() { } public void pushMamPreferences(Account account, Element prefs) { - IqPacket set = new IqPacket(IqPacket.TYPE.SET); + final Iq set = new Iq(Iq.Type.SET); set.addChild(prefs); sendIqPacket(account, set, null); } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 2ce3eb52a..45c2259f4 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -494,14 +494,9 @@ protected boolean isCameraFeatureAvailable() { } protected boolean isOptimizingBattery() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); - return pm != null - && !pm.isIgnoringBatteryOptimizations(getPackageName()); - } else { - return false; - } - } + final PowerManager pm = getSystemService(PowerManager.class); + return !pm.isIgnoringBatteryOptimizations(getPackageName()); +} protected boolean isAffectedByDataSaver() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index 98a2bfb46..20db7f878 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -3,7 +3,9 @@ import androidx.annotation.NonNull; import com.google.common.base.Optional; +import com.google.common.base.Strings; import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; import java.util.ArrayList; import java.util.Hashtable; @@ -12,7 +14,7 @@ import eu.siacs.conversations.utils.XmlHelper; import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import im.conversations.android.xmpp.model.stanza.Message; public class Element { private final String name; @@ -136,6 +138,10 @@ public Element setAttribute(String name, Jid value) { return this; } + public void setAttribute(final String name, final boolean value) { + this.setAttribute(name, value ? "1" : "0"); + } + public void removeAttribute(final String name) { this.attributes.remove(name); } @@ -153,6 +159,11 @@ public String getAttribute(String name) { } } + public long getLongAttribute(final String name) { + final var value = Longs.tryParse(Strings.nullToEmpty(this.attributes.get(name))); + return value == null ? 0 : value; + } + public Optional getOptionalIntAttribute(final String name) { final String value = getAttribute(name); if (value == null) { @@ -167,7 +178,7 @@ public Jid getAttributeAsJid(String name) { try { return Jid.ofEscaped(jid); } catch (final IllegalArgumentException e) { - return InvalidJid.of(jid, this instanceof MessagePacket); + return InvalidJid.of(jid, this instanceof Message); } } return null; @@ -180,7 +191,7 @@ public Hashtable getAttributes() { @NonNull public String toString() { final StringBuilder elementOutput = new StringBuilder(); - if ((content == null) && (children.size() == 0)) { + if (content == null && children.isEmpty()) { final Tag emptyTag = Tag.empty(name); emptyTag.setAttributes(this.attributes); elementOutput.append(emptyTag); diff --git a/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java b/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java index 635afd145..d730bd34f 100644 --- a/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java +++ b/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java @@ -37,7 +37,7 @@ public static LocalizedContent get(final Element element, String name) { } } } - if (contents.size() == 0) { + if (contents.isEmpty()) { return null; } final String userLanguage = Locale.getDefault().getLanguage(); diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 2e5eb1c5a..cc08154f9 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -1,8 +1,29 @@ package eu.siacs.conversations.xml; public final class Namespace { + public static final String ADDRESSING = "http://jabber.org/protocol/address"; + public static final String AXOLOTL = "eu.siacs.conversations.axolotl"; + public static final String PGP_SIGNED = "jabber:x:signed"; + public static final String PGP_ENCRYPTED = "jabber:x:encrypted"; + public static final String AXOLOTL_BUNDLES = AXOLOTL + ".bundles"; + public static final String AXOLOTL_DEVICE_LIST = AXOLOTL + ".devicelist"; + public static final String HINTS = "urn:xmpp:hints"; + public static final String MESSAGE_ARCHIVE_MANAGEMENT = "urn:xmpp:mam:2"; + public static final String VERSION = "jabber:iq:version"; + public static final String LAST_MESSAGE_CORRECTION = "urn:xmpp:message-correct:0"; + public static final String RESULT_SET_MANAGEMENT = "http://jabber.org/protocol/rsm"; + public static final String CHAT_MARKERS = "urn:xmpp:chat-markers:0"; + public static final String CHAT_STATES = "http://jabber.org/protocol/chatstates"; + public static final String DELIVERY_RECEIPTS = "urn:xmpp:receipts"; + public static final String REACTIONS = "urn:xmpp:reactions:0"; + public static final String VCARD_TEMP = "vcard-temp"; + public static final String VCARD_TEMP_UPDATE = "vcard-temp:x:update"; + public static final String DELAY = "urn:xmpp:delay"; + public static final String OCCUPANT_ID = "urn:xmpp:occupant-id:0"; public static final String STREAMS = "http://etherx.jabber.org/streams"; + public static final String STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas"; public static final String JABBER_CLIENT = "jabber:client"; + public static final String FORWARD = "urn:xmpp:forward:0"; public static final String DISCO_ITEMS = "http://jabber.org/protocol/disco#items"; public static final String DISCO_INFO = "http://jabber.org/protocol/disco#info"; public static final String EXTERNAL_SERVICE_DISCOVERY = "urn:xmpp:extdisco:2"; @@ -23,12 +44,15 @@ public final class Namespace { public static final String FAST = "urn:xmpp:fast:0"; public static final String TLS = "urn:ietf:params:xml:ns:xmpp-tls"; public static final String PUBSUB = "http://jabber.org/protocol/pubsub"; + public static final String PUBSUB_EVENT = PUBSUB + "#event"; + public static final String MUC = "http://jabber.org/protocol/muc"; public static final String PUBSUB_PUBLISH_OPTIONS = PUBSUB + "#publish-options"; public static final String PUBSUB_CONFIG_NODE_MAX = PUBSUB + "#config-node-max"; public static final String PUBSUB_ERROR = PUBSUB + "#errors"; public static final String PUBSUB_OWNER = PUBSUB + "#owner"; public static final String NICK = "http://jabber.org/protocol/nick"; - public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = "http://jabber.org/protocol/offline"; + public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = + "http://jabber.org/protocol/offline"; public static final String BIND = "urn:ietf:params:xml:ns:xmpp-bind"; public static final String BIND2 = "urn:xmpp:bind:0"; public static final String STREAM_MANAGEMENT = "urn:xmpp:sm:3"; @@ -38,7 +62,7 @@ public final class Namespace { public static final String BOOKMARKS = "storage:bookmarks"; public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0"; public static final String AVATAR_DATA = "urn:xmpp:avatar:data"; - public static final String AVATAR_METADATA = "urn:xmpp:avatar:metadata"; + public static final String AVATAR_METADATA = "urn:xmpp:avatar:metadata"; public static final String AVATAR_CONVERSION = "urn:xmpp:pep-vcard-conversion:0"; public static final String JINGLE = "urn:xmpp:jingle:1"; public static final String JINGLE_ERRORS = "urn:xmpp:jingle:errors:1"; @@ -48,7 +72,8 @@ public final class Namespace { public static final String JINGLE_TRANSPORTS_S5B = "urn:xmpp:jingle:transports:s5b:1"; public static final String JINGLE_TRANSPORTS_IBB = "urn:xmpp:jingle:transports:ibb:1"; public static final String JINGLE_TRANSPORT_ICE_UDP = "urn:xmpp:jingle:transports:ice-udp:1"; - public static final String JINGLE_TRANSPORT_WEBRTC_DATA_CHANNEL = "urn:xmpp:jingle:transports:webrtc-datachannel:1"; + public static final String JINGLE_TRANSPORT_WEBRTC_DATA_CHANNEL = + "urn:xmpp:jingle:transports:webrtc-datachannel:1"; public static final String JINGLE_TRANSPORT = "urn:xmpp:jingle:transports:dtls-sctp:1"; public static final String JINGLE_APPS_RTP = "urn:xmpp:jingle:apps:rtp:1"; @@ -57,9 +82,12 @@ public final class Namespace { public static final String JINGLE_APPS_GROUPING = "urn:xmpp:jingle:apps:grouping:0"; public static final String JINGLE_FEATURE_AUDIO = "urn:xmpp:jingle:apps:rtp:audio"; public static final String JINGLE_FEATURE_VIDEO = "urn:xmpp:jingle:apps:rtp:video"; - public static final String JINGLE_RTP_HEADER_EXTENSIONS = "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"; - public static final String JINGLE_RTP_FEEDBACK_NEGOTIATION = "urn:xmpp:jingle:apps:rtp:rtcp-fb:0"; - public static final String JINGLE_RTP_SOURCE_SPECIFIC_MEDIA_ATTRIBUTES = "urn:xmpp:jingle:apps:rtp:ssma:0"; + public static final String JINGLE_RTP_HEADER_EXTENSIONS = + "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"; + public static final String JINGLE_RTP_FEEDBACK_NEGOTIATION = + "urn:xmpp:jingle:apps:rtp:rtcp-fb:0"; + public static final String JINGLE_RTP_SOURCE_SPECIFIC_MEDIA_ATTRIBUTES = + "urn:xmpp:jingle:apps:rtp:ssma:0"; public static final String IBB = "http://jabber.org/protocol/ibb"; public static final String PING = "urn:xmpp:ping"; public static final String PUSH = "urn:xmpp:push:0"; @@ -70,8 +98,10 @@ public final class Namespace { public static final String INVITE = "urn:xmpp:invite"; public static final String PARS = "urn:xmpp:pars:0"; public static final String EASY_ONBOARDING_INVITE = "urn:xmpp:invite#invite"; - public static final String OMEMO_DTLS_SRTP_VERIFICATION = "http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification"; - public static final String JINGLE_TRANSPORT_ICE_OPTION = "http://gultsch.de/xmpp/drafts/jingle/transports/ice-udp/option"; + public static final String OMEMO_DTLS_SRTP_VERIFICATION = + "http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification"; + public static final String JINGLE_TRANSPORT_ICE_OPTION = + "http://gultsch.de/xmpp/drafts/jingle/transports/ice-udp/option"; public static final String UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push"; public static final String REPORTING = "urn:xmpp:reporting:1"; public static final String REPORTING_REASON_SPAM = "urn:xmpp:reporting:spam"; @@ -79,4 +109,7 @@ public final class Namespace { public static final String HASHES = "urn:xmpp:hashes:2"; public static final String MDS_DISPLAYED = "urn:xmpp:mds:displayed:0"; public static final String MDS_SERVER_ASSIST = "urn:xmpp:mds:server-assist:0"; + + public static final String ENTITY_CAPABILITIES = "http://jabber.org/protocol/caps"; + public static final String ENTITY_CAPABILITIES_2 = "urn:xmpp:caps"; } diff --git a/src/main/java/eu/siacs/conversations/xml/TagWriter.java b/src/main/java/eu/siacs/conversations/xml/TagWriter.java index 5a9f3317c..2401c612c 100644 --- a/src/main/java/eu/siacs/conversations/xml/TagWriter.java +++ b/src/main/java/eu/siacs/conversations/xml/TagWriter.java @@ -10,13 +10,14 @@ import java.util.concurrent.TimeUnit; import eu.siacs.conversations.Config; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; +import im.conversations.android.xmpp.model.StreamElement; public class TagWriter { private OutputStreamWriter outputStream; private boolean finished = false; - private final LinkedBlockingQueue writeQueue = new LinkedBlockingQueue(); + + private final LinkedBlockingQueue writeQueue = new LinkedBlockingQueue<>(); private CountDownLatch stanzaWriterCountDownLatch = null; private final Thread asyncStanzaWriter = new Thread() { @@ -25,13 +26,13 @@ public class TagWriter { public void run() { stanzaWriterCountDownLatch = new CountDownLatch(1); while (!isInterrupted()) { - if (finished && writeQueue.size() == 0) { + if (finished && writeQueue.isEmpty()) { break; } try { - AbstractStanza output = writeQueue.take(); + final var output = writeQueue.take(); outputStream.write(output.toString()); - if (writeQueue.size() == 0) { + if (writeQueue.isEmpty()) { outputStream.flush(); } } catch (Exception e) { @@ -74,7 +75,7 @@ public synchronized void writeTag(final Tag tag, final boolean flush) throws IOE } } - public synchronized void writeElement(Element element) throws IOException { + public synchronized void writeElement(final StreamElement element) throws IOException { if (outputStream == null) { throw new IOException("output stream was null"); } @@ -82,7 +83,7 @@ public synchronized void writeElement(Element element) throws IOException { outputStream.flush(); } - public void writeStanzaAsync(AbstractStanza stanza) { + public void writeStanzaAsync(StreamElement stanza) { if (finished) { Log.d(Config.LOGTAG, "attempting to write stanza to finished TagWriter"); } else { diff --git a/src/main/java/eu/siacs/conversations/xml/XmlReader.java b/src/main/java/eu/siacs/conversations/xml/XmlReader.java index 240b92b7a..090a6e47d 100644 --- a/src/main/java/eu/siacs/conversations/xml/XmlReader.java +++ b/src/main/java/eu/siacs/conversations/xml/XmlReader.java @@ -3,6 +3,12 @@ import android.util.Log; import android.util.Xml; +import eu.siacs.conversations.Config; + +import im.conversations.android.xmpp.ExtensionFactory; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.StreamElement; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -11,8 +17,6 @@ import java.io.InputStream; import java.io.InputStreamReader; -import eu.siacs.conversations.Config; - public class XmlReader implements Closeable { private final XmlPullParser parser; private InputStream is; @@ -87,8 +91,21 @@ public Tag readTag() throws IOException { return null; } - public Element readElement(Tag currentTag) throws IOException { - Element element = new Element(currentTag.getName()); + public T readElement(final Tag current, final Class clazz) + throws IOException { + final Element element = readElement(current); + if (clazz.isInstance(element)) { + return clazz.cast(element); + } + throw new IOException( + String.format("Read unexpected {%s}%s", element.getNamespace(), element.getName())); + } + + public Element readElement(final Tag currentTag) throws IOException { + final var attributes = currentTag.getAttributes(); + final var namespace = attributes.get("xmlns"); + final var name = currentTag.getName(); + final Element element = ExtensionFactory.create(name, namespace); element.setAttributes(currentTag.getAttributes()); Tag nextTag = this.readTag(); if (nextTag == null) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java b/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java index f3a21c36f..4e3092821 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java @@ -31,7 +31,7 @@ import androidx.annotation.NonNull; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; +import im.conversations.android.xmpp.model.stanza.Stanza; public class InvalidJid implements Jid { @@ -137,10 +137,10 @@ public static Jid getNullForInvalid(Jid jid) { } public static boolean isValid(Jid jid) { - return !(jid != null && jid instanceof InvalidJid); + return !(jid instanceof InvalidJid); } - public static boolean hasValidFrom(AbstractStanza stanza) { + public static boolean hasValidFrom(Stanza stanza) { final String from = stanza.getAttribute("from"); if (from == null) { return false; diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnIqPacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/OnIqPacketReceived.java deleted file mode 100644 index 6db24ef94..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/OnIqPacketReceived.java +++ /dev/null @@ -1,8 +0,0 @@ -package eu.siacs.conversations.xmpp; - -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; - -public interface OnIqPacketReceived extends PacketReceived { - void onIqPacketReceived(Account account, IqPacket packet); -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnMessagePacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/OnMessagePacketReceived.java index 24acf16e2..6ff26884b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/OnMessagePacketReceived.java +++ b/src/main/java/eu/siacs/conversations/xmpp/OnMessagePacketReceived.java @@ -1,8 +1,7 @@ package eu.siacs.conversations.xmpp; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import im.conversations.android.xmpp.model.stanza.Message; -public interface OnMessagePacketReceived extends PacketReceived { - void onMessagePacketReceived(Account account, MessagePacket packet); +public interface OnMessagePacketReceived { + void onMessagePacketReceived(Message packet); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnPresencePacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/OnPresencePacketReceived.java deleted file mode 100644 index e1bf839f4..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/OnPresencePacketReceived.java +++ /dev/null @@ -1,8 +0,0 @@ -package eu.siacs.conversations.xmpp; - -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.stanzas.PresencePacket; - -public interface OnPresencePacketReceived extends PacketReceived { - void onPresencePacketReceived(Account account, PresencePacket packet); -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/PacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/PacketReceived.java deleted file mode 100644 index 05ddc392f..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/PacketReceived.java +++ /dev/null @@ -1,5 +0,0 @@ -package eu.siacs.conversations.xmpp; - -public interface PacketReceived { - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 31e150ac7..a38266266 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -36,6 +36,9 @@ import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.http.HttpConnectionManager; +import eu.siacs.conversations.parser.IqParser; +import eu.siacs.conversations.parser.MessageParser; +import eu.siacs.conversations.parser.PresenceParser; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.MemorizingTrustManager; import eu.siacs.conversations.services.MessageArchiveService; @@ -58,18 +61,37 @@ import eu.siacs.conversations.xmpp.bind.Bind2; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; -import eu.siacs.conversations.xmpp.stanzas.AbstractAcknowledgeableStanza; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; -import eu.siacs.conversations.xmpp.stanzas.PresencePacket; -import eu.siacs.conversations.xmpp.stanzas.csi.ActivePacket; -import eu.siacs.conversations.xmpp.stanzas.csi.InactivePacket; -import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket; -import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket; -import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket; -import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket; + +import im.conversations.android.xmpp.model.AuthenticationStreamFeature; +import im.conversations.android.xmpp.model.StreamElement; +import im.conversations.android.xmpp.model.bind2.Bind; +import im.conversations.android.xmpp.model.bind2.Bound; +import im.conversations.android.xmpp.model.csi.Active; +import im.conversations.android.xmpp.model.csi.Inactive; +import im.conversations.android.xmpp.model.error.Condition; +import im.conversations.android.xmpp.model.fast.Fast; +import im.conversations.android.xmpp.model.fast.RequestToken; +import im.conversations.android.xmpp.model.jingle.Jingle; +import im.conversations.android.xmpp.model.sasl.Auth; +import im.conversations.android.xmpp.model.sasl.Mechanisms; +import im.conversations.android.xmpp.model.sasl.Response; +import im.conversations.android.xmpp.model.sasl.Success; +import im.conversations.android.xmpp.model.sasl2.Authenticate; +import im.conversations.android.xmpp.model.sasl2.Authentication; +import im.conversations.android.xmpp.model.sm.Ack; +import im.conversations.android.xmpp.model.sm.Enable; +import im.conversations.android.xmpp.model.sm.Enabled; +import im.conversations.android.xmpp.model.sm.Failed; +import im.conversations.android.xmpp.model.sm.Request; +import im.conversations.android.xmpp.model.sm.Resume; +import im.conversations.android.xmpp.model.sm.Resumed; +import im.conversations.android.xmpp.model.sm.StreamManagement; +import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.stanza.Presence; +import im.conversations.android.xmpp.model.stanza.Stanza; +import im.conversations.android.xmpp.model.tls.Proceed; +import im.conversations.android.xmpp.model.tls.StartTls; +import im.conversations.android.xmpp.processor.BindProcessor; import okhttp3.HttpUrl; @@ -104,6 +126,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.regex.Matcher; import javax.net.ssl.KeyManager; @@ -116,46 +139,12 @@ public class XmppConnection implements Runnable { - private static final int PACKET_IQ = 0; - private static final int PACKET_MESSAGE = 1; - private static final int PACKET_PRESENCE = 2; - public final OnIqPacketReceived registrationResponseListener = - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.setOption(Account.OPTION_REGISTER, false); - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": successfully registered new account on server"); - throw new StateChangingError(Account.State.REGISTRATION_SUCCESSFUL); - } else { - final List PASSWORD_TOO_WEAK_MSGS = - Arrays.asList( - "The password is too weak", "Please use a longer password."); - Element error = packet.findChild("error"); - Account.State state = Account.State.REGISTRATION_FAILED; - if (error != null) { - if (error.hasChild("conflict")) { - state = Account.State.REGISTRATION_CONFLICT; - } else if (error.hasChild("resource-constraint") - && "wait".equals(error.getAttribute("type"))) { - state = Account.State.REGISTRATION_PLEASE_WAIT; - } else if (error.hasChild("not-acceptable") - && PASSWORD_TOO_WEAK_MSGS.contains( - error.findChildContent("text"))) { - state = Account.State.REGISTRATION_PASSWORD_TOO_WEAK; - } - } - throw new StateChangingError(state); - } - }; protected final Account account; private final Features features = new Features(this); private final HashMap disco = new HashMap<>(); private final HashMap commands = new HashMap<>(); - private final SparseArray mStanzaQueue = new SparseArray<>(); - private final Hashtable> packetCallbacks = - new Hashtable<>(); + private final SparseArray mStanzaQueue = new SparseArray<>(); + private final Hashtable>> packetCallbacks = new Hashtable<>(); private final Set advancedStreamFeaturesLoadedListeners = new HashSet<>(); private final AppSettings appSettings; @@ -168,8 +157,8 @@ public class XmppConnection implements Runnable { private boolean quickStartInProgress = false; private boolean isBound = false; private boolean offlineMessagesRetrieved = false; - private Element streamFeatures; - private Element boundStreamFeatures; + private im.conversations.android.xmpp.model.streams.Features streamFeatures; + private im.conversations.android.xmpp.model.streams.Features boundStreamFeatures; private StreamId streamId = null; private int stanzasReceived = 0; private int stanzasSent = 0; @@ -186,12 +175,13 @@ public class XmppConnection implements Runnable { private final AtomicInteger mSmCatchupMessageCounter = new AtomicInteger(0); private boolean mInteractive = false; private int attempt = 0; - private OnPresencePacketReceived presenceListener = null; private OnJinglePacketReceived jingleListener = null; - private OnIqPacketReceived unregisteredIqListener = null; - private OnMessagePacketReceived messageListener = null; + + private final Consumer presenceListener; + private final Consumer unregisteredIqListener; + private final Consumer messageListener; private OnStatusChanged statusListener = null; - private OnBindListener bindListener = null; + private final Runnable bindListener; private OnMessageAcknowledged acknowledgedListener = null; private LoginInfo loginInfo; private HashedToken.Mechanism hashTokenRequest; @@ -206,6 +196,10 @@ public XmppConnection(final Account account, final XmppConnectionService service this.account = account; this.mXmppConnectionService = service; this.appSettings = new AppSettings(mXmppConnectionService.getApplicationContext()); + this.presenceListener = new PresenceParser(service, account); + this.unregisteredIqListener = new IqParser(service, account); + this.messageListener = new MessageParser(service, account); + this.bindListener = new BindProcessor(service, account); } private static void fixResource(final Context context, final Account account) { @@ -606,7 +600,7 @@ private void processStream() throws XmlPullParserException, IOException { } else if (nextTag.isStart("features", Namespace.STREAMS)) { processStreamFeatures(nextTag); } else if (nextTag.isStart("proceed", Namespace.TLS)) { - switchOverToTls(); + switchOverToTls(nextTag); } else if (nextTag.isStart("failure", Namespace.TLS)) { throw new StateChangingException(Account.State.TLS_ERROR); } else if (account.isOptionSet(Account.OPTION_REGISTER) @@ -632,10 +626,10 @@ private void processStream() throws XmlPullParserException, IOException { throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } else if (this.streamId != null && nextTag.isStart("resumed", Namespace.STREAM_MANAGEMENT)) { - final Element resumed = tagReader.readElement(nextTag); + final Resumed resumed = tagReader.readElement(nextTag, Resumed.class); processResumed(resumed); } else if (nextTag.isStart("failed", Namespace.STREAM_MANAGEMENT)) { - final Element failed = tagReader.readElement(nextTag); + final Failed failed = tagReader.readElement(nextTag, Failed.class); processFailed(failed, true); } else if (nextTag.isStart("iq", Namespace.JABBER_CLIENT)) { processIq(nextTag); @@ -651,7 +645,7 @@ private void processStream() throws XmlPullParserException, IOException { } else if (nextTag.isStart("presence", Namespace.JABBER_CLIENT)) { processPresence(nextTag); } else if (nextTag.isStart("enabled", Namespace.STREAM_MANAGEMENT)) { - final Element enabled = tagReader.readElement(nextTag); + final var enabled = tagReader.readElement(nextTag, Enabled.class); processEnabled(enabled); } else if (nextTag.isStart("r", Namespace.STREAM_MANAGEMENT)) { tagReader.readElement(nextTag); @@ -662,7 +656,7 @@ private void processStream() throws XmlPullParserException, IOException { + ": acknowledging stanza #" + this.stanzasReceived); } - final AckPacket ack = new AckPacket(this.stanzasReceived); + final Ack ack = new Ack(this.stanzasReceived); tagWriter.writeStanzaAsync(ack); } else if (nextTag.isStart("a", Namespace.STREAM_MANAGEMENT)) { boolean accountUiNeedsRefresh = false; @@ -689,11 +683,11 @@ private void processStream() throws XmlPullParserException, IOException { if (accountUiNeedsRefresh) { mXmppConnectionService.updateAccountUi(); } - final Element ack = tagReader.readElement(nextTag); + final var ack = tagReader.readElement(nextTag, Ack.class); lastPacketReceived = SystemClock.elapsedRealtime(); final boolean acknowledgedMessages; synchronized (this.mStanzaQueue) { - final Optional serverSequence = ack.getOptionalIntAttribute("h"); + final Optional serverSequence = ack.getHandled(); if (serverSequence.isPresent()) { acknowledgedMessages = acknowledgeStanzaUpTo(serverSequence.get()); } else { @@ -729,11 +723,11 @@ private void processChallenge(final Element challenge) throws IOException { } catch (final IllegalArgumentException e) { throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } - final Element response; + final StreamElement response; if (version == SaslMechanism.Version.SASL) { - response = new Element("response", Namespace.SASL); + response = new Response(); } else if (version == SaslMechanism.Version.SASL_2) { - response = new Element("response", Namespace.SASL_2); + response = new im.conversations.android.xmpp.model.sasl2.Response(); } else { throw new AssertionError("Missing implementation for " + version); } @@ -753,26 +747,23 @@ private void processChallenge(final Element challenge) throws IOException { tagWriter.writeElement(response); } - private boolean processSuccess(final Element success) + private boolean processSuccess(final Element element) throws IOException, XmlPullParserException { - final SaslMechanism.Version version; - try { - version = SaslMechanism.Version.of(success); - } catch (final IllegalArgumentException e) { - throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); - } final LoginInfo currentLoginInfo = this.loginInfo; final SaslMechanism currentSaslMechanism = LoginInfo.mechanism(currentLoginInfo); if (currentLoginInfo == null || currentSaslMechanism == null) { throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } + final SaslMechanism.Version version; final String challenge; - if (version == SaslMechanism.Version.SASL) { + if (element instanceof Success success) { challenge = success.getContent(); - } else if (version == SaslMechanism.Version.SASL_2) { + version = SaslMechanism.Version.SASL; + } else if (element instanceof im.conversations.android.xmpp.model.sasl2.Success success) { challenge = success.findChildContent("additional-data"); + version = SaslMechanism.Version.SASL_2; } else { - throw new AssertionError("Missing implementation for " + version); + throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } try { currentLoginInfo.success(challenge, sslSocketOrNull(socket)); @@ -786,47 +777,24 @@ private boolean processSuccess(final Element success) if (SaslMechanism.pin(currentSaslMechanism)) { account.setPinnedMechanism(currentSaslMechanism); } - if (version == SaslMechanism.Version.SASL_2) { - final String authorizationIdentifier = - success.findChildContent("authorization-identifier"); - final Jid authorizationJid; - try { - authorizationJid = - Strings.isNullOrEmpty(authorizationIdentifier) - ? null - : Jid.ofEscaped(authorizationIdentifier); - } catch (final IllegalArgumentException e) { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": SASL 2.0 authorization identifier was not a valid jid"); - throw new StateChangingException(Account.State.BIND_FAILURE); - } - if (authorizationJid == null) { - throw new StateChangingException(Account.State.BIND_FAILURE); - } + if (element instanceof im.conversations.android.xmpp.model.sasl2.Success success) { + final var authorizationJid = success.getAuthorizationIdentifier(); + checkAssignedDomainOrThrow(authorizationJid); Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": SASL 2.0 authorization identifier was " + authorizationJid); - if (!account.getJid().getDomain().equals(authorizationJid.getDomain())) { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": server tried to re-assign domain to " - + authorizationJid.getDomain()); - throw new StateChangingError(Account.State.BIND_FAILURE); - } + // TODO this should only happen when we used Bind 2 if (authorizationJid.isFullJid() && account.setJid(authorizationJid)) { Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": jid changed during SASL 2.0. updating database"); } - final Element bound = success.findChild("bound", Namespace.BIND2); - final Element resumed = success.findChild("resumed", Namespace.STREAM_MANAGEMENT); - final Element failed = success.findChild("failed", Namespace.STREAM_MANAGEMENT); + final Bound bound = success.getExtension(Bound.class); + final Resumed resumed = success.getExtension(Resumed.class); + final Failed failed = success.getExtension(Failed.class); final Element tokenWrapper = success.findChild("token", Namespace.FAST); final String token = tokenWrapper == null ? null : tokenWrapper.getAttribute("token"); if (bound != null && resumed != null) { @@ -853,8 +821,7 @@ private boolean processSuccess(final Element success) this.isBound = true; processNopStreamFeatures(); this.boundStreamFeatures = this.streamFeatures; - final Element streamManagementEnabled = - bound.findChild("enabled", Namespace.STREAM_MANAGEMENT); + final Enabled streamManagementEnabled = bound.getExtension(Enabled.class); final Element carbonsEnabled = bound.findChild("enabled", Namespace.CARBONS); final boolean waitForDisco; if (streamManagementEnabled != null) { @@ -939,7 +906,7 @@ private boolean processSuccess(final Element success) private void resetOutboundStanzaQueue() { synchronized (this.mStanzaQueue) { - final ImmutableList.Builder intermediateStanzasBuilder = + final ImmutableList.Builder intermediateStanzasBuilder = new ImmutableList.Builder<>(); if (Config.EXTENDED_SM_LOGGING) { Log.d( @@ -949,7 +916,7 @@ private void resetOutboundStanzaQueue() { + this.stanzasSentBeforeAuthentication); } for (int i = this.stanzasSentBeforeAuthentication + 1; i <= this.stanzasSent; ++i) { - final AbstractAcknowledgeableStanza stanza = this.mStanzaQueue.get(i); + final Stanza stanza = this.mStanzaQueue.get(i); if (stanza != null) { intermediateStanzasBuilder.add(stanza); } @@ -973,7 +940,9 @@ private void resetOutboundStanzaQueue() { private void processNopStreamFeatures() throws IOException { final Tag tag = tagReader.readTag(); if (tag != null && tag.isStart("features", Namespace.STREAMS)) { - this.streamFeatures = tagReader.readElement(tag); + this.streamFeatures = + tagReader.readElement( + tag, im.conversations.android.xmpp.model.streams.Features.class); Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -1043,22 +1012,8 @@ private static SSLSocket sslSocketOrNull(final Socket socket) { } } - private void processEnabled(final Element enabled) { - final String id; - if (enabled.getAttributeAsBoolean("resume")) { - id = enabled.getAttribute("id"); - } else { - id = null; - } - final String locationAttribute = enabled.getAttribute("location"); - final Resolver.Result currentResolverResult = this.currentResolverResult; - final Resolver.Result location; - if (Strings.isNullOrEmpty(locationAttribute) || currentResolverResult == null) { - location = null; - } else { - location = currentResolverResult.seeOtherHost(locationAttribute); - } - final StreamId streamId = id == null ? null : new StreamId(id, location); + private void processEnabled(final Enabled enabled) { + final StreamId streamId = getStreamId(enabled); if (streamId == null) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": stream management enabled"); } else { @@ -1071,16 +1026,30 @@ private void processEnabled(final Element enabled) { this.streamId = streamId; this.stanzasReceived = 0; this.inSmacksSession = true; - final RequestPacket r = new RequestPacket(); + final var r = new Request(); tagWriter.writeStanzaAsync(r); } - private void processResumed(final Element resumed) throws StateChangingException { + @Nullable + private StreamId getStreamId(final Enabled enabled) { + final Optional id = enabled.getResumeId(); + final String locationAttribute = enabled.getLocation(); + final Resolver.Result currentResolverResult = this.currentResolverResult; + final Resolver.Result location; + if (Strings.isNullOrEmpty(locationAttribute) || currentResolverResult == null) { + location = null; + } else { + location = currentResolverResult.seeOtherHost(locationAttribute); + } + return id.isPresent() ? new StreamId(id.get(), location) : null; + } + + private void processResumed(final Resumed resumed) throws StateChangingException { this.inSmacksSession = true; this.isBound = true; - this.tagWriter.writeStanzaAsync(new RequestPacket()); + this.tagWriter.writeStanzaAsync(new Request()); lastPacketReceived = SystemClock.elapsedRealtime(); - final Optional h = resumed.getOptionalIntAttribute("h"); + final Optional h = resumed.getHandled(); final int serverCount; if (h.isPresent()) { serverCount = h.get(); @@ -1088,7 +1057,7 @@ private void processResumed(final Element resumed) throws StateChangingException resetStreamId(); throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } - final ArrayList failedStanzas = new ArrayList<>(); + final ArrayList failedStanzas = new ArrayList<>(); final boolean acknowledgedMessages; synchronized (this.mStanzaQueue) { if (serverCount < stanzasSent) { @@ -1111,8 +1080,8 @@ private void processResumed(final Element resumed) throws StateChangingException Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": resending " + failedStanzas.size() + " stanzas"); - for (final AbstractAcknowledgeableStanza packet : failedStanzas) { - if (packet instanceof MessagePacket message) { + for (final Stanza packet : failedStanzas) { + if (packet instanceof im.conversations.android.xmpp.model.stanza.Message message) { mXmppConnectionService.markMessage( account, message.getTo().asBareJid(), @@ -1131,8 +1100,8 @@ private void changeStatusToOnline() { changeStatus(Account.State.ONLINE); } - private void processFailed(final Element failed, final boolean sendBindRequest) { - final Optional serverCount = failed.getOptionalIntAttribute("h"); + private void processFailed(final Failed failed, final boolean sendBindRequest) { + final Optional serverCount = failed.getHandled(); if (serverCount.isPresent()) { Log.d( Config.LOGTAG, @@ -1179,8 +1148,9 @@ private boolean acknowledgeStanzaUpTo(final int serverCount) { + ": server acknowledged stanza #" + mStanzaQueue.keyAt(i)); } - final AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i); - if (stanza instanceof MessagePacket packet && acknowledgedListener != null) { + final Stanza stanza = mStanzaQueue.valueAt(i); + if (stanza instanceof im.conversations.android.xmpp.model.stanza.Message packet + && acknowledgedListener != null) { final String id = packet.getId(); final Jid to = packet.getTo(); if (id != null && to != null) { @@ -1195,29 +1165,9 @@ private boolean acknowledgeStanzaUpTo(final int serverCount) { return acknowledgedMessages; } - private @NonNull Element processPacket(final Tag currentTag, final int packetType) + private @NonNull S processPacket(final Tag currentTag, final Class clazz) throws IOException { - final Element element = - switch (packetType) { - case PACKET_IQ -> new IqPacket(); - case PACKET_MESSAGE -> new MessagePacket(); - case PACKET_PRESENCE -> new PresencePacket(); - default -> throw new AssertionError("Should never encounter invalid type"); - }; - element.setAttributes(currentTag.getAttributes()); - Tag nextTag = tagReader.readTag(); - if (nextTag == null) { - throw new IOException("interrupted mid tag"); - } - while (!nextTag.isEnd(element.getName())) { - if (!nextTag.isNo()) { - element.addChild(tagReader.readElement(nextTag)); - } - nextTag = tagReader.readTag(); - if (nextTag == null) { - throw new IOException("interrupted mid tag"); - } - } + final S stanza = tagReader.readElement(currentTag, clazz); if (stanzasReceived == Integer.MAX_VALUE) { resetStreamId(); throw new IOException("time to restart the session. cant handle >2 billion pcks"); @@ -1229,25 +1179,19 @@ private boolean acknowledgeStanzaUpTo(final int serverCount) { Config.LOGTAG, account.getJid().asBareJid() + ": not counting stanza(" - + element.getClass().getSimpleName() + + stanza.getClass().getSimpleName() + "). Not in smacks session."); } lastPacketReceived = SystemClock.elapsedRealtime(); if (Config.BACKGROUND_STANZA_LOGGING && mXmppConnectionService.checkListeners()) { - Log.d(Config.LOGTAG, "[background stanza] " + element); - } - if (element instanceof IqPacket - && (((IqPacket) element).getType() == IqPacket.TYPE.SET) - && element.hasChild("jingle", Namespace.JINGLE)) { - return JinglePacket.upgrade((IqPacket) element); - } else { - return element; + Log.d(Config.LOGTAG, "[background stanza] " + stanza); } + return stanza; } private void processIq(final Tag currentTag) throws IOException { - final IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ); - if (!packet.valid()) { + final Iq packet = processPacket(currentTag, Iq.class); + if (packet.isInvalid()) { Log.e( Config.LOGTAG, "encountered invalid iq from='" @@ -1263,12 +1207,12 @@ private void processIq(final Tag currentTag) throws IOException { account.getJid().asBareJid() + "Not processing iq. Thread was interrupted"); return; } - if (packet instanceof JinglePacket jinglePacket && isBound) { + if (packet.hasExtension(Jingle.class) && packet.getType() == Iq.Type.SET && isBound) { if (this.jingleListener != null) { - this.jingleListener.onJinglePacketReceived(account, jinglePacket); + this.jingleListener.onJinglePacketReceived(account, packet); } } else { - final OnIqPacketReceived callback = getIqPacketReceivedCallback(packet); + final var callback = getIqPacketReceivedCallback(packet); if (callback == null) { Log.d( Config.LOGTAG, @@ -1278,17 +1222,17 @@ private void processIq(final Tag currentTag) throws IOException { return; } try { - callback.onIqPacketReceived(account, packet); + callback.accept(packet); } catch (final StateChangingError error) { throw new StateChangingException(error.state); } } } - private OnIqPacketReceived getIqPacketReceivedCallback(final IqPacket stanza) + private Consumer getIqPacketReceivedCallback(final Iq stanza) throws StateChangingException { final boolean isRequest = - stanza.getType() == IqPacket.TYPE.GET || stanza.getType() == IqPacket.TYPE.SET; + stanza.getType() == Iq.Type.GET || stanza.getType() == Iq.Type.SET; if (isRequest) { if (isBound) { return this.unregisteredIqListener; @@ -1328,8 +1272,9 @@ private OnIqPacketReceived getIqPacketReceivedCallback(final IqPacket stanza) } private void processMessage(final Tag currentTag) throws IOException { - final MessagePacket packet = (MessagePacket) processPacket(currentTag, PACKET_MESSAGE); - if (!packet.valid()) { + final var packet = + processPacket(currentTag, im.conversations.android.xmpp.model.stanza.Message.class); + if (packet.isInvalid()) { Log.e( Config.LOGTAG, "encountered invalid message from='" @@ -1346,12 +1291,12 @@ private void processMessage(final Tag currentTag) throws IOException { + "Not processing message. Thread was interrupted"); return; } - this.messageListener.onMessagePacketReceived(account, packet); + this.messageListener.accept(packet); } private void processPresence(final Tag currentTag) throws IOException { - final PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE); - if (!packet.valid()) { + final var packet = processPacket(currentTag, Presence.class); + if (packet.isInvalid()) { Log.e( Config.LOGTAG, "encountered invalid presence from='" @@ -1368,17 +1313,15 @@ private void processPresence(final Tag currentTag) throws IOException { + "Not processing presence. Thread was interrupted"); return; } - this.presenceListener.onPresencePacketReceived(account, packet); + this.presenceListener.accept(packet); } private void sendStartTLS() throws IOException { - final Tag startTLS = Tag.empty("starttls"); - startTLS.setAttribute("xmlns", Namespace.TLS); - tagWriter.writeTag(startTLS); + tagWriter.writeElement(new StartTls()); } - private void switchOverToTls() throws XmlPullParserException, IOException { - tagReader.readTag(); + private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException { + tagReader.readElement(currentTag, Proceed.class); final Socket socket = this.socket; final SSLSocket sslSocket = upgradeSocketToTls(socket); this.socket = sslSocket; @@ -1439,11 +1382,13 @@ private SSLSocket upgradeSocketToTls(final Socket socket) throws IOException { } private void processStreamFeatures(final Tag currentTag) throws IOException { - this.streamFeatures = tagReader.readElement(currentTag); + this.streamFeatures = + tagReader.readElement( + currentTag, im.conversations.android.xmpp.model.streams.Features.class); final boolean isSecure = isSecure(); final boolean needsBinding = !isBound && !account.isOptionSet(Account.OPTION_REGISTER); if (this.quickStartInProgress) { - if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2)) { + if (this.streamFeatures.hasStreamFeature(Authentication.class)) { Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -1452,8 +1397,7 @@ private void processStreamFeatures(final Tag currentTag) throws IOException { if (SaslMechanism.hashedToken(LoginInfo.mechanism(this.loginInfo))) { return; } - if (isFastTokenAvailable( - this.streamFeatures.findChild("authentication", Namespace.SASL_2))) { + if (isFastTokenAvailable(this.streamFeatures.getExtension(Authentication.class))) { Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -1471,8 +1415,7 @@ private void processStreamFeatures(final Tag currentTag) throws IOException { mXmppConnectionService.databaseBackend.updateAccount(account); throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } - if (this.streamFeatures.hasChild("starttls", Namespace.TLS) - && !features.encryptionEnabled) { + if (this.streamFeatures.hasExtension(StartTls.class) && !features.encryptionEnabled) { sendStartTLS(); } else if (this.streamFeatures.hasChild("register", Namespace.REGISTER_STREAM_FEATURE) && account.isOptionSet(Account.OPTION_REGISTER)) { @@ -1489,15 +1432,15 @@ private void processStreamFeatures(final Tag currentTag) throws IOException { } else if (!this.streamFeatures.hasChild("register", Namespace.REGISTER_STREAM_FEATURE) && account.isOptionSet(Account.OPTION_REGISTER)) { throw new StateChangingException(Account.State.REGISTRATION_NOT_SUPPORTED); - } else if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2) + } else if (this.streamFeatures.hasStreamFeature(Authentication.class) && shouldAuthenticate && isSecure) { authenticate(SaslMechanism.Version.SASL_2); - } else if (this.streamFeatures.hasChild("mechanisms", Namespace.SASL) + } else if (this.streamFeatures.hasStreamFeature(Mechanisms.class) && shouldAuthenticate && isSecure) { authenticate(SaslMechanism.Version.SASL); - } else if (this.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT) + } else if (this.streamFeatures.streamManagement() && isSecure && LoginInfo.isSuccess(loginInfo) && streamId != null @@ -1509,7 +1452,7 @@ private void processStreamFeatures(final Tag currentTag) throws IOException { + ": resuming after stanza #" + stanzasReceived); } - final ResumePacket resume = new ResumePacket(this.streamId.id, stanzasReceived); + final var resume = new Resume(this.streamId.id, stanzasReceived); this.mSmCatchupMessageCounter.set(0); this.mWaitingForSmCatchup.set(true); this.tagWriter.writeStanzaAsync(resume); @@ -1537,9 +1480,9 @@ private void processStreamFeatures(final Tag currentTag) throws IOException { private void authenticate() throws IOException { final boolean isSecure = isSecure(); - if (isSecure && this.streamFeatures.hasChild("authentication", Namespace.SASL_2)) { + if (isSecure && this.streamFeatures.hasStreamFeature(Authentication.class)) { authenticate(SaslMechanism.Version.SASL_2); - } else if (isSecure && this.streamFeatures.hasChild("mechanisms", Namespace.SASL)) { + } else if (isSecure && this.streamFeatures.hasStreamFeature(Mechanisms.class)) { authenticate(SaslMechanism.Version.SASL); } else { throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); @@ -1551,13 +1494,13 @@ private boolean isSecure() { } private void authenticate(final SaslMechanism.Version version) throws IOException { - final Element authElement; + final AuthenticationStreamFeature authElement; if (version == SaslMechanism.Version.SASL) { - authElement = this.streamFeatures.findChild("mechanisms", Namespace.SASL); + authElement = this.streamFeatures.getExtension(Mechanisms.class); } else { - authElement = this.streamFeatures.findChild("authentication", Namespace.SASL_2); + authElement = this.streamFeatures.getExtension(Authentication.class); } - final Collection mechanisms = SaslMechanism.mechanisms(authElement); + final Collection mechanisms = authElement.getMechanismNames(); final Element cbElement = this.streamFeatures.findChild("sasl-channel-binding", Namespace.CHANNEL_BINDING); final Collection channelBindings = ChannelBinding.of(cbElement); @@ -1569,26 +1512,27 @@ private void authenticate(final SaslMechanism.Version version) throws IOExceptio final String firstMessage = saslMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)); final boolean usingFast = SaslMechanism.hashedToken(saslMechanism); - final Element authenticate; + final StreamElement authenticate; if (version == SaslMechanism.Version.SASL) { - authenticate = new Element("auth", Namespace.SASL); + authenticate = new Auth(); if (!Strings.isNullOrEmpty(firstMessage)) { authenticate.setContent(firstMessage); } quickStartAvailable = false; this.loginInfo = new LoginInfo(saslMechanism, version, Collections.emptyList()); } else if (version == SaslMechanism.Version.SASL_2) { - final Element inline = authElement.findChild("inline", Namespace.SASL_2); - final boolean sm = inline != null && inline.hasChild("sm", Namespace.STREAM_MANAGEMENT); + final Authentication authentication = (Authentication) authElement; + final var inline = authentication.getInline(); + final boolean sm = inline != null && inline.hasExtension(StreamManagement.class); final HashedToken.Mechanism hashTokenRequest; if (usingFast) { hashTokenRequest = null; - } else { - final Element fast = - inline == null ? null : inline.findChild("fast", Namespace.FAST); - final Collection fastMechanisms = SaslMechanism.mechanisms(fast); + } else if (inline != null) { hashTokenRequest = - HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket)); + HashedToken.Mechanism.best( + inline.getFastMechanisms(), SSLSockets.version(this.socket)); + } else { + hashTokenRequest = null; } final Collection bindFeatures = Bind2.features(inline); quickStartAvailable = @@ -1633,9 +1577,9 @@ private void authenticate(final SaslMechanism.Version version) throws IOExceptio } } - private static boolean isFastTokenAvailable(final Element authentication) { - final Element inline = authentication == null ? null : authentication.findChild("inline"); - return inline != null && inline.hasChild("fast", Namespace.FAST); + private static boolean isFastTokenAvailable(final Authentication authentication) { + final var inline = authentication == null ? null : authentication.getInline(); + return inline != null && inline.hasExtension(Fast.class); } private void validate( @@ -1649,7 +1593,7 @@ private void validate( + mechanisms); throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } - validateRequireChannelBinding(saslMechanism); + checkRequireChannelBinding(saslMechanism); if (SaslMechanism.hashedToken(saslMechanism)) { return; } @@ -1668,7 +1612,7 @@ private void validate( } } - private void validateRequireChannelBinding(@NonNull final SaslMechanism mechanism) + private void checkRequireChannelBinding(@NonNull final SaslMechanism mechanism) throws StateChangingException { if (appSettings.isRequireChannelBinding()) { if (mechanism instanceof ChannelBindingMechanism) { @@ -1679,19 +1623,44 @@ private void validateRequireChannelBinding(@NonNull final SaslMechanism mechanis } } - private Element generateAuthenticationRequest( + private void checkAssignedDomainOrThrow(final Jid jid) throws StateChangingException { + if (jid == null) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": bind response is missing jid"); + throw new StateChangingException(Account.State.BIND_FAILURE); + } + final var current = this.account.getJid().getDomain(); + if (jid.getDomain().equals(current)) { + return; + } + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": server tried to re-assign domain to " + + jid.getDomain()); + throw new StateChangingException(Account.State.BIND_FAILURE); + } + + private void checkAssignedDomain(final Jid jid) { + try { + checkAssignedDomainOrThrow(jid); + } catch (final StateChangingException e) { + throw new StateChangingError(e.state); + } + } + + private Authenticate generateAuthenticationRequest( final String firstMessage, final boolean usingFast) { return generateAuthenticationRequest( firstMessage, usingFast, null, Bind2.QUICKSTART_FEATURES, true); } - private Element generateAuthenticationRequest( + private Authenticate generateAuthenticationRequest( final String firstMessage, final boolean usingFast, final HashedToken.Mechanism hashedTokenRequest, final Collection bind, final boolean inlineStreamManagement) { - final Element authenticate = new Element("authenticate", Namespace.SASL_2); + final var authenticate = new Authenticate(); if (!Strings.isNullOrEmpty(firstMessage)) { authenticate.addChild("initial-response").setContent(firstMessage); } @@ -1712,31 +1681,29 @@ private Element generateAuthenticationRequest( authenticate.addChild(generateBindRequest(bind)); } if (inlineStreamManagement && streamId != null) { - final ResumePacket resume = new ResumePacket(this.streamId.id, stanzasReceived); + final var resume = new Resume(this.streamId.id, stanzasReceived); this.mSmCatchupMessageCounter.set(0); this.mWaitingForSmCatchup.set(true); - authenticate.addChild(resume); + authenticate.addExtension(resume); } if (hashedTokenRequest != null) { - authenticate - .addChild("request-token", Namespace.FAST) - .setAttribute("mechanism", hashedTokenRequest.name()); + authenticate.addExtension(new RequestToken(hashedTokenRequest)); } if (usingFast) { - authenticate.addChild("fast", Namespace.FAST); + authenticate.addExtension(new Fast()); } return authenticate; } - private Element generateBindRequest(final Collection bindFeatures) { + private Bind generateBindRequest(final Collection bindFeatures) { Log.d(Config.LOGTAG, "inline bind features: " + bindFeatures); - final Element bind = new Element("bind", Namespace.BIND2); + final var bind = new Bind(); bind.addChild("tag").setContent(mXmppConnectionService.getString(R.string.app_name)); if (bindFeatures.contains(Namespace.CARBONS)) { - bind.addChild("enable", Namespace.CARBONS); + bind.addExtension(new im.conversations.android.xmpp.model.carbons.Enable()); } if (bindFeatures.contains(Namespace.STREAM_MANAGEMENT)) { - bind.addChild(new EnablePacket()); + bind.addExtension(new Enable()); } return bind; } @@ -1744,12 +1711,12 @@ private Element generateBindRequest(final Collection bindFeatures) { private void register() { final String preAuth = account.getKey(Account.KEY_PRE_AUTH_REGISTRATION_TOKEN); if (preAuth != null && features.invite()) { - final IqPacket preAuthRequest = new IqPacket(IqPacket.TYPE.SET); + final Iq preAuthRequest = new Iq(Iq.Type.SET); preAuthRequest.addChild("preauth", Namespace.PARS).setAttribute("token", preAuth); sendUnmodifiedIqPacket( preAuthRequest, - (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { sendRegistryRequest(); } else { final String error = response.getErrorCondition(); @@ -1768,21 +1735,21 @@ private void register() { } private void sendRegistryRequest() { - final IqPacket register = new IqPacket(IqPacket.TYPE.GET); + final Iq register = new Iq(Iq.Type.GET); register.query(Namespace.REGISTER); register.setTo(account.getDomain()); sendUnmodifiedIqPacket( register, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + (packet) -> { + if (packet.getType() == Iq.Type.TIMEOUT) { return; } - if (packet.getType() == IqPacket.TYPE.ERROR) { + if (packet.getType() == Iq.Type.ERROR) { throw new StateChangingError(Account.State.REGISTRATION_FAILED); } final Element query = packet.query(Namespace.REGISTER); if (query.hasChild("username") && (query.hasChild("password"))) { - final IqPacket register1 = new IqPacket(IqPacket.TYPE.SET); + final Iq register1 = new Iq(Iq.Type.SET); final Element username = new Element("username").setContent(account.getUsername()); final Element password = @@ -1790,7 +1757,7 @@ private void sendRegistryRequest() { register1.query(Namespace.REGISTER).addChild(username); register1.query().addChild(password); register1.setFrom(account.getJid().asBareJid()); - sendUnmodifiedIqPacket(register1, registrationResponseListener, true); + sendUnmodifiedIqPacket(register1, this::processRegistrationResponse, true); } else if (query.hasChild("x", Namespace.DATA)) { final Data data = Data.parse(query.findChild("x", Namespace.DATA)); final Element blob = query.findChild("data", "urn:xmpp:bob"); @@ -1858,6 +1825,45 @@ private void sendRegistryRequest() { true); } + public void sendCreateAccountWithCaptchaPacket(final String id, final Data data) { + final Iq request = IqGenerator.generateCreateAccountWithCaptcha(account, id, data); + this.sendUnmodifiedIqPacket(request, this::processRegistrationResponse, true); + } + + private void processRegistrationResponse(final Iq response) { + if (response.getType() == Iq.Type.RESULT) { + account.setOption(Account.OPTION_REGISTER, false); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": successfully registered new account on server"); + throw new StateChangingError(Account.State.REGISTRATION_SUCCESSFUL); + } else { + final Account.State state = getRegistrationFailedState(response); + throw new StateChangingError(state); + } + } + + @NonNull + private static Account.State getRegistrationFailedState(final Iq response) { + final List PASSWORD_TOO_WEAK_MESSAGES = + Arrays.asList("The password is too weak", "Please use a longer password."); + final var error = response.getError(); + final var condition = error == null ? null : error.getCondition(); + final Account.State state; + if (condition instanceof Condition.Conflict) { + state = Account.State.REGISTRATION_CONFLICT; + } else if (condition instanceof Condition.ResourceConstraint) { + state = Account.State.REGISTRATION_PLEASE_WAIT; + } else if (condition instanceof Condition.NotAcceptable + && PASSWORD_TOO_WEAK_MESSAGES.contains(error.getTextAsString())) { + state = Account.State.REGISTRATION_PASSWORD_TOO_WEAK; + } else { + state = Account.State.REGISTRATION_FAILED; + } + return state; + } + private void setAccountCreationFailed(final String url) { final HttpUrl httpUrl = url == null ? null : HttpUrl.parse(url); if (httpUrl != null && httpUrl.isHttps()) { @@ -1905,61 +1911,36 @@ private void sendBindRequest() { } else { fixResource(mXmppConnectionService, account); } - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + final Iq iq = new Iq(Iq.Type.SET); final String resource = Config.USE_RANDOM_RESOURCE_ON_EVERY_BIND ? nextRandomId() : account.getResource(); - iq.addChild("bind", Namespace.BIND).addChild("resource").setContent(resource); + iq.addExtension(new im.conversations.android.xmpp.model.bind.Bind()).setResource(resource); this.sendUnmodifiedIqPacket( iq, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + (packet) -> { + if (packet.getType() == Iq.Type.TIMEOUT) { return; } - final Element bind = packet.findChild("bind"); - if (bind != null && packet.getType() == IqPacket.TYPE.RESULT) { + final var bind = + packet.getExtension( + im.conversations.android.xmpp.model.bind.Bind.class); + if (bind != null && packet.getType() == Iq.Type.RESULT) { isBound = true; - final Element jid = bind.findChild("jid"); - if (jid != null && jid.getContent() != null) { - try { - Jid assignedJid = Jid.ofEscaped(jid.getContent()); - if (!account.getJid().getDomain().equals(assignedJid.getDomain())) { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": server tried to re-assign domain to " - + assignedJid.getDomain()); - throw new StateChangingError(Account.State.BIND_FAILURE); - } - if (account.setJid(assignedJid)) { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": jid changed during bind. updating database"); - mXmppConnectionService.databaseBackend.updateAccount(account); - } - if (streamFeatures.hasChild("session") - && !streamFeatures - .findChild("session") - .hasChild("optional")) { - sendStartSession(); - } else { - final boolean waitForDisco = enableStreamManagement(); - sendPostBindInitialization(waitForDisco, false); - } - return; - } catch (final IllegalArgumentException e) { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": server reported invalid jid (" - + jid.getContent() - + ") on bind"); - } - } else { + final Jid assignedJid = bind.getJid(); + checkAssignedDomain(assignedJid); + if (account.setJid(assignedJid)) { Log.d( Config.LOGTAG, - account.getJid() - + ": disconnecting because of bind failure. (no jid)"); + account.getJid().asBareJid() + + ": jid changed during bind. updating database"); + mXmppConnectionService.databaseBackend.updateAccount(account); + } + if (streamFeatures.hasChild("session") + && !streamFeatures.findChild("session").hasChild("optional")) { + sendStartSession(); + } else { + final boolean waitForDisco = enableStreamManagement(); + sendPostBindInitialization(waitForDisco, false); } } else { Log.d( @@ -1967,23 +1948,24 @@ private void sendBindRequest() { account.getJid() + ": disconnecting because of bind failure (" + packet); + final var error = packet.getError(); + // TODO error.is(Condition) + if (packet.getType() == Iq.Type.ERROR + && error != null + && error.hasChild("conflict")) { + account.setResource(createNewResource()); + } + throw new StateChangingError(Account.State.BIND_FAILURE); } - final Element error = packet.findChild("error"); - if (packet.getType() == IqPacket.TYPE.ERROR - && error != null - && error.hasChild("conflict")) { - account.setResource(createNewResource()); - } - throw new StateChangingError(Account.State.BIND_FAILURE); }, true); } private void clearIqCallbacks() { - final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.TIMEOUT); - final ArrayList callbacks = new ArrayList<>(); + final Iq failurePacket = new Iq(Iq.Type.TIMEOUT); + final ArrayList> callbacks = new ArrayList<>(); synchronized (this.packetCallbacks) { - if (this.packetCallbacks.size() == 0) { + if (this.packetCallbacks.isEmpty()) { return; } Log.d( @@ -1992,17 +1974,16 @@ private void clearIqCallbacks() { + ": clearing " + this.packetCallbacks.size() + " iq callbacks"); - final Iterator> iterator = - this.packetCallbacks.values().iterator(); + final var iterator = this.packetCallbacks.values().iterator(); while (iterator.hasNext()) { - Pair entry = iterator.next(); + final var entry = iterator.next(); callbacks.add(entry.second); iterator.remove(); } } - for (OnIqPacketReceived callback : callbacks) { + for (final var callback : callbacks) { try { - callback.onIqPacketReceived(account, failurePacket); + callback.accept(failurePacket); } catch (StateChangingError error) { Log.d( Config.LOGTAG, @@ -2034,15 +2015,15 @@ private void sendStartSession() { Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": sending legacy session to outdated server"); - final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET); + final Iq startSession = new Iq(Iq.Type.SET); startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session"); this.sendUnmodifiedIqPacket( startSession, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { final boolean waitForDisco = enableStreamManagement(); sendPostBindInitialization(waitForDisco, false); - } else if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + } else if (packet.getType() != Iq.Type.TIMEOUT) { throw new StateChangingError(Account.State.SESSION_FAILURE); } }, @@ -2050,11 +2031,10 @@ private void sendStartSession() { } private boolean enableStreamManagement() { - final boolean streamManagement = - this.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT); + final boolean streamManagement = this.streamFeatures.streamManagement(); if (streamManagement) { synchronized (this.mStanzaQueue) { - final EnablePacket enable = new EnablePacket(); + final var enable = new Enable(); tagWriter.writeStanzaAsync(enable); stanzasSent = 0; mStanzaQueue.clear(); @@ -2111,13 +2091,13 @@ private void sendPostBindInitialization( private void sendServiceDiscoveryInfo(final Jid jid) { mPendingServiceDiscoveries.incrementAndGet(); - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + final Iq iq = new Iq(Iq.Type.GET); iq.setTo(jid); iq.query("http://jabber.org/protocol/disco#info"); this.sendIqPacket( iq, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { boolean advancedStreamFeaturesLoaded; synchronized (XmppConnection.this.disco) { ServiceDiscoveryResult result = new ServiceDiscoveryResult(packet); @@ -2135,7 +2115,7 @@ private void sendServiceDiscoveryInfo(final Jid jid) { || jid.equals(account.getJid().asBareJid()))) { enableAdvancedStreamFeatures(); } - } else if (packet.getType() == IqPacket.TYPE.ERROR) { + } else if (packet.getType() == Iq.Type.ERROR) { Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -2159,7 +2139,7 @@ private void sendServiceDiscoveryInfo(final Jid jid) { enableAdvancedStreamFeatures(); } } - if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + if (packet.getType() != Iq.Type.TIMEOUT) { if (mPendingServiceDiscoveries.decrementAndGet() == 0 && mWaitForDisco.compareAndSet(true, false)) { finalizeBind(); @@ -2169,12 +2149,12 @@ private void sendServiceDiscoveryInfo(final Jid jid) { } private void discoverMamPreferences() { - IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.addChild("prefs", MessageArchiveService.Version.MAM_2.namespace); sendIqPacket( request, - (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { Element prefs = response.findChild( "prefs", MessageArchiveService.Version.MAM_2.namespace); @@ -2189,13 +2169,13 @@ private void discoverMamPreferences() { } private void discoverCommands() { - final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.setTo(account.getDomain()); request.addChild("query", Namespace.DISCO_ITEMS).setAttribute("node", Namespace.COMMANDS); sendIqPacket( request, - (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element query = response.findChild("query", Namespace.DISCO_ITEMS); if (query == null) { return; @@ -2224,17 +2204,14 @@ public boolean isMamPreferenceAlways() { private void finalizeBind() { this.offlineMessagesRetrieved = false; - if (bindListener != null) { - bindListener.onBind(account); - } - changeStatusToOnline(); + this.bindListener.run(); + this.changeStatusToOnline(); } private void enableAdvancedStreamFeatures() { if (getFeatures().blocking() && !features.blockListRequested) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": Requesting block list"); - this.sendIqPacket( - getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser()); + this.sendIqPacket(getIqGenerator().generateGetBlockList(), unregisteredIqListener); } for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) { @@ -2250,13 +2227,13 @@ private void enableAdvancedStreamFeatures() { private void sendServiceDiscoveryItems(final Jid server) { mPendingServiceDiscoveries.incrementAndGet(); - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + final Iq iq = new Iq(Iq.Type.GET); iq.setTo(server.getDomain()); iq.query("http://jabber.org/protocol/disco#items"); this.sendIqPacket( iq, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { final HashSet items = new HashSet<>(); final List elements = packet.query().getChildren(); for (final Element element : elements) { @@ -2279,7 +2256,7 @@ private void sendServiceDiscoveryItems(final Jid server) { + ": could not query disco items of " + server); } - if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + if (packet.getType() != Iq.Type.TIMEOUT) { if (mPendingServiceDiscoveries.decrementAndGet() == 0 && mWaitForDisco.compareAndSet(true, false)) { finalizeBind(); @@ -2289,12 +2266,12 @@ private void sendServiceDiscoveryItems(final Jid server) { } private void sendEnableCarbons() { - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + final Iq iq = new Iq(Iq.Type.SET); iq.addChild("enable", Namespace.CARBONS); this.sendIqPacket( iq, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": successfully enabled carbons"); @@ -2363,8 +2340,8 @@ private void processStreamError(final Tag currentTag) throws IOException { private void failPendingMessages(final String error) { synchronized (this.mStanzaQueue) { for (int i = 0; i < mStanzaQueue.size(); ++i) { - final AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i); - if (stanza instanceof MessagePacket packet) { + final Stanza stanza = mStanzaQueue.valueAt(i); + if (stanza instanceof im.conversations.android.xmpp.model.stanza.Message packet) { final String id = packet.getId(); final Jid to = packet.getTo(); mXmppConnectionService.markMessage( @@ -2398,7 +2375,7 @@ private boolean establishStream(final SSLSockets.Version sslVersion) SaslMechanism.Version.SASL_2, Bind2.QUICKSTART_FEATURES); final boolean usingFast = quickStartMechanism instanceof HashedToken; - final Element authenticate = + final StreamElement authenticate = generateAuthenticationRequest( quickStartMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)), usingFast); @@ -2445,13 +2422,14 @@ private String nextRandomId(final boolean s) { return CryptoHelper.random(s ? 3 : 9); } - public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { + public String sendIqPacket(final Iq packet, final Consumer callback) { packet.setFrom(account.getJid()); return this.sendUnmodifiedIqPacket(packet, callback, false); } public synchronized String sendUnmodifiedIqPacket( - final IqPacket packet, final OnIqPacketReceived callback, boolean force) { + final Iq packet, final Consumer callback, boolean force) { + // TODO if callback != null verify that type is get or set if (packet.getId() == null) { packet.setAttribute("id", nextRandomId()); } @@ -2464,19 +2442,19 @@ public synchronized String sendUnmodifiedIqPacket( return packet.getId(); } - public void sendMessagePacket(final MessagePacket packet) { + public void sendMessagePacket(final im.conversations.android.xmpp.model.stanza.Message packet) { this.sendPacket(packet); } - public void sendPresencePacket(final PresencePacket packet) { + public void sendPresencePacket(final Presence packet) { this.sendPacket(packet); } - private synchronized void sendPacket(final AbstractStanza packet) { + private synchronized void sendPacket(final StreamElement packet) { sendPacket(packet, false); } - private synchronized void sendPacket(final AbstractStanza packet, final boolean force) { + private synchronized void sendPacket(final StreamElement packet, final boolean force) { if (stanzasSent == Integer.MAX_VALUE) { resetStreamId(); disconnect(true); @@ -2492,7 +2470,7 @@ private synchronized void sendPacket(final AbstractStanza packet, final boolean + " do not write stanza to unbound stream " + packet.toString()); } - if (packet instanceof AbstractAcknowledgeableStanza stanza) { + if (packet instanceof Stanza stanza) { if (this.mStanzaQueue.size() != 0) { int currentHighestKey = this.mStanzaQueue.keyAt(this.mStanzaQueue.size() - 1); if (currentHighestKey != stanzasSent) { @@ -2511,7 +2489,9 @@ private synchronized void sendPacket(final AbstractStanza packet, final boolean + stanzasSent); } this.mStanzaQueue.append(stanzasSent, stanza); - if (stanza instanceof MessagePacket && stanza.getId() != null && inSmacksSession) { + if (stanza instanceof im.conversations.android.xmpp.model.stanza.Message + && stanza.getId() != null + && inSmacksSession) { if (Config.EXTENDED_SM_LOGGING) { Log.d( Config.LOGTAG, @@ -2519,7 +2499,7 @@ private synchronized void sendPacket(final AbstractStanza packet, final boolean + ": requesting ack for message stanza #" + stanzasSent); } - tagWriter.writeStanzaAsync(new RequestPacket()); + tagWriter.writeStanzaAsync(new Request()); } } } @@ -2527,7 +2507,7 @@ private synchronized void sendPacket(final AbstractStanza packet, final boolean public void sendPing() { if (!r()) { - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + final Iq iq = new Iq(Iq.Type.GET); iq.setFrom(account.getJid()); iq.addChild("ping", Namespace.PING); this.sendIqPacket(iq, null); @@ -2535,18 +2515,6 @@ public void sendPing() { this.lastPingSent = SystemClock.elapsedRealtime(); } - public void setOnMessagePacketReceivedListener(final OnMessagePacketReceived listener) { - this.messageListener = listener; - } - - public void setOnUnregisteredIqPacketReceivedListener(final OnIqPacketReceived listener) { - this.unregisteredIqListener = listener; - } - - public void setOnPresencePacketReceivedListener(final OnPresencePacketReceived listener) { - this.presenceListener = listener; - } - public void setOnJinglePacketReceivedListener(final OnJinglePacketReceived listener) { this.jingleListener = listener; } @@ -2555,10 +2523,6 @@ public void setOnStatusChangedListener(final OnStatusChanged listener) { this.statusListener = listener; } - public void setOnBindListener(final OnBindListener listener) { - this.bindListener = listener; - } - public void setOnMessageAcknowledgeListener(final OnMessageAcknowledged listener) { this.acknowledgedListener = listener; } @@ -2654,7 +2618,7 @@ public Jid findDiscoItemByFeature(final String feature) { public boolean r() { if (getFeatures().sm()) { - this.tagWriter.writeStanzaAsync(new RequestPacket()); + this.tagWriter.writeStanzaAsync(new Request()); return true; } else { return false; @@ -2732,11 +2696,11 @@ public long getLastPacketReceived() { } public void sendActive() { - this.sendPacket(new ActivePacket()); + this.sendPacket(new Active()); } public void sendInactive() { - this.sendPacket(new InactivePacket()); + this.sendPacket(new Inactive()); } public void resetAttemptCount(boolean resetConnectTime) { @@ -2756,11 +2720,11 @@ private IqGenerator getIqGenerator() { public void trackOfflineMessageRetrieval(boolean trackOfflineMessageRetrieval) { if (trackOfflineMessageRetrieval) { - final IqPacket iqPing = new IqPacket(IqPacket.TYPE.GET); + final Iq iqPing = new Iq(Iq.Type.GET); iqPing.addChild("ping", Namespace.PING); this.sendIqPacket( iqPing, - (a, response) -> { + (response) -> { Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -2776,6 +2740,20 @@ public boolean isOfflineMessagesRetrieved() { return this.offlineMessagesRetrieved; } + public void fetchRoster() { + final Iq iqPacket = new Iq(Iq.Type.GET); + final var version = account.getRosterVersion(); + if (Strings.isNullOrEmpty(account.getRosterVersion())) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching roster"); + } else { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + ": fetching roster version " + version); + } + iqPacket.query(Namespace.ROSTER).setAttribute("ver", version); + sendIqPacket(iqPacket, unregisteredIqListener); + } + private class MyKeyManager implements X509KeyManager { @Override public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { @@ -2957,13 +2935,12 @@ public boolean invite() { public boolean sm() { return streamId != null || (connection.streamFeatures != null - && connection.streamFeatures.hasChild( - "sm", Namespace.STREAM_MANAGEMENT)); + && connection.streamFeatures.streamManagement()); } public boolean csi() { return connection.streamFeatures != null - && connection.streamFeatures.hasChild("csi", Namespace.CSI); + && connection.streamFeatures.clientStateIndication(); } public boolean pep() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractContentMap.java index 847678a05..2c3c3f762 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractContentMap.java @@ -8,7 +8,8 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; +import im.conversations.android.xmpp.model.jingle.Jingle; +import im.conversations.android.xmpp.model.stanza.Iq; import java.util.List; import java.util.Map; @@ -47,8 +48,9 @@ public List getNames() { return ImmutableList.copyOf(contents.keySet()); } - JinglePacket toJinglePacket(final JinglePacket.Action action, final String sessionId) { - final JinglePacket jinglePacket = new JinglePacket(action, sessionId); + Iq toJinglePacket(final Jingle.Action action, final String sessionId) { + final Iq iq = new Iq(Iq.Type.SET); + final var jinglePacket = iq.addExtension(new Jingle(action, sessionId)); for (final Map.Entry> entry : this.contents.entrySet()) { final DescriptionTransport descriptionTransport = entry.getValue(); final Content content = @@ -65,7 +67,7 @@ JinglePacket toJinglePacket(final JinglePacket.Action action, final String sessi if (this.group != null) { jinglePacket.addGroup(this.group); } - return jinglePacket; + return iq; } void requireContentDescriptions() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java index 6aeb348c1..771679288 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java @@ -19,9 +19,9 @@ import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.jingle.Jingle; +import im.conversations.android.xmpp.model.stanza.Iq; import java.util.Arrays; import java.util.Collection; @@ -184,10 +184,10 @@ boolean isTerminated() { return TERMINATED.contains(this.state); } - abstract void deliverPacket(JinglePacket jinglePacket); + abstract void deliverPacket(Iq jinglePacket); protected void receiveOutOfOrderAction( - final JinglePacket jinglePacket, final JinglePacket.Action action) { + final Iq jinglePacket, final Jingle.Action action) { Log.d( Config.LOGTAG, String.format( @@ -205,7 +205,7 @@ protected void receiveOutOfOrderAction( } } - protected void terminateWithOutOfOrder(final JinglePacket jinglePacket) { + protected void terminateWithOutOfOrder(final Iq jinglePacket) { Log.d( Config.LOGTAG, id.account.getJid().asBareJid() + ": terminating session with out-of-order"); @@ -235,37 +235,38 @@ protected void sendSessionTerminate( if (previous != State.NULL && trigger != null) { trigger.accept(target); } - final JinglePacket jinglePacket = - new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId); + final var iq = new Iq(Iq.Type.SET); + final var jinglePacket = + iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId)); jinglePacket.setReason(reason, text); - send(jinglePacket); + send(iq); finish(); } - protected void send(final JinglePacket jinglePacket) { + protected void send(final Iq jinglePacket) { jinglePacket.setTo(id.with); xmppConnectionService.sendIqPacket(id.account, jinglePacket, this::handleIqResponse); } - protected void respondOk(final JinglePacket jinglePacket) { + protected void respondOk(final Iq jinglePacket) { xmppConnectionService.sendIqPacket( - id.account, jinglePacket.generateResponse(IqPacket.TYPE.RESULT), null); + id.account, jinglePacket.generateResponse(Iq.Type.RESULT), null); } - protected void respondWithTieBreak(final JinglePacket jinglePacket) { + protected void respondWithTieBreak(final Iq jinglePacket) { respondWithJingleError(jinglePacket, "tie-break", "conflict", "cancel"); } - protected void respondWithOutOfOrder(final JinglePacket jinglePacket) { + protected void respondWithOutOfOrder(final Iq jinglePacket) { respondWithJingleError(jinglePacket, "out-of-order", "unexpected-request", "wait"); } - protected void respondWithItemNotFound(final JinglePacket jinglePacket) { + protected void respondWithItemNotFound(final Iq jinglePacket) { respondWithJingleError(jinglePacket, null, "item-not-found", "cancel"); } private void respondWithJingleError( - final IqPacket original, + final Iq original, String jingleCondition, String condition, String conditionType) { @@ -273,18 +274,18 @@ private void respondWithJingleError( id.account, original, jingleCondition, condition, conditionType); } - private synchronized void handleIqResponse(final Account account, final IqPacket response) { - if (response.getType() == IqPacket.TYPE.ERROR) { + private synchronized void handleIqResponse(final Iq response) { + if (response.getType() == Iq.Type.ERROR) { handleIqErrorResponse(response); return; } - if (response.getType() == IqPacket.TYPE.TIMEOUT) { + if (response.getType() == Iq.Type.TIMEOUT) { handleIqTimeoutResponse(response); } } - protected void handleIqErrorResponse(final IqPacket response) { - Preconditions.checkArgument(response.getType() == IqPacket.TYPE.ERROR); + protected void handleIqErrorResponse(final Iq response) { + Preconditions.checkArgument(response.getType() == Iq.Type.ERROR); final String errorCondition = response.getErrorCondition(); Log.d( Config.LOGTAG, @@ -316,8 +317,8 @@ protected void handleIqErrorResponse(final IqPacket response) { this.finish(); } - protected void handleIqTimeoutResponse(final IqPacket response) { - Preconditions.checkArgument(response.getType() == IqPacket.TYPE.TIMEOUT); + protected void handleIqTimeoutResponse(final Iq response) { + Preconditions.checkArgument(response.getType() == Iq.Type.TIMEOUT); Log.d( Config.LOGTAG, id.account.getJid().asBareJid() @@ -361,8 +362,8 @@ private Id(final Account account, final Jid with, final String sessionId) { this.sessionId = sessionId; } - public static Id of(Account account, JinglePacket jinglePacket) { - return new Id(account, jinglePacket.getFrom(), jinglePacket.getSessionId()); + public static Id of(Account account, Iq iq, final Jingle jingle) { + return new Id(account, iq.getFrom(), jingle.getSessionId()); } public static Id of(Account account, Jid with, final String sessionId) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/FileTransferContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/FileTransferContentMap.java index c678c91cb..d0f39747e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/FileTransferContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/FileTransferContentMap.java @@ -13,11 +13,10 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.IbbTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; -import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.WebRTCDataChannelTransportInfo; import eu.siacs.conversations.xmpp.jingle.transports.Transport; +import im.conversations.android.xmpp.model.jingle.Jingle; import java.util.Arrays; import java.util.Collections; @@ -39,7 +38,7 @@ protected FileTransferContentMap( super(group, contents); } - public static FileTransferContentMap of(final JinglePacket jinglePacket) { + public static FileTransferContentMap of(final Jingle jinglePacket) { final Map> contents = of(jinglePacket.getJingleContents()); return new FileTransferContentMap(jinglePacket.getGroup(), contents); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/IceServers.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/IceServers.java index 7b2f88457..5149470f0 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/IceServers.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/IceServers.java @@ -10,7 +10,7 @@ import eu.siacs.conversations.utils.IP; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import org.webrtc.PeerConnection; @@ -20,9 +20,9 @@ public final class IceServers { - public static List parse(final IqPacket response) { + public static List parse(final Iq response) { ImmutableList.Builder listBuilder = new ImmutableList.Builder<>(); - if (response.getType() == IqPacket.TYPE.RESULT) { + if (response.getType() == Iq.Type.RESULT) { final Element services = response.findChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY); final List children = 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 c3080d692..95047919c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -34,14 +34,13 @@ import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jingle.stanzas.Content; import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.Propose; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.jingle.transports.InbandBytestreamsTransport; import eu.siacs.conversations.xmpp.jingle.transports.Transport; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import im.conversations.android.xmpp.model.jingle.Jingle; +import im.conversations.android.xmpp.model.stanza.Iq; import java.lang.ref.WeakReference; import java.security.SecureRandom; @@ -77,9 +76,11 @@ static String nextRandomId() { return Base64.encodeToString(id, Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE); } - public void deliverPacket(final Account account, final JinglePacket packet) { - final String sessionId = packet.getSessionId(); - final JinglePacket.Action action = packet.getAction(); + public void deliverPacket(final Account account, final Iq packet) { + final var jingle = packet.getExtension(Jingle.class); + Preconditions.checkNotNull(jingle,"Passed iq packet w/o jingle extension to Connection Manager"); + final String sessionId = jingle.getSessionId(); + final Jingle.Action action = jingle.getAction(); if (sessionId == null) { respondWithJingleError(account, packet, "unknown-session", "item-not-found", "cancel"); return; @@ -88,13 +89,13 @@ public void deliverPacket(final Account account, final JinglePacket packet) { respondWithJingleError(account, packet, null, "bad-request", "cancel"); return; } - final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, packet); + final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, packet, jingle); final AbstractJingleConnection existingJingleConnection = connections.get(id); if (existingJingleConnection != null) { existingJingleConnection.deliverPacket(packet); - } else if (action == JinglePacket.Action.SESSION_INITIATE) { + } else if (action == Jingle.Action.SESSION_INITIATE) { final Jid from = packet.getFrom(); - final Content content = packet.getJingleContent(); + final Content content = jingle.getJingleContent(); final String descriptionNamespace = content == null ? null : content.getDescriptionNamespace(); final AbstractJingleConnection connection; @@ -162,14 +163,14 @@ private void addNewIncomingCall(final JingleRtpConnection rtpConnection) { } private void sendSessionTerminate( - final Account account, final IqPacket request, final AbstractJingleConnection.Id id) { + final Account account, final Iq request, final AbstractJingleConnection.Id id) { mXmppConnectionService.sendIqPacket( - account, request.generateResponse(IqPacket.TYPE.RESULT), null); - final JinglePacket sessionTermination = - new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId); - sessionTermination.setTo(id.with); + account, request.generateResponse(Iq.Type.RESULT), null); + final var iq = new Iq(Iq.Type.SET); + iq.setTo(id.with); + final var sessionTermination = iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId)); sessionTermination.setReason(Reason.BUSY, null); - mXmppConnectionService.sendIqPacket(account, sessionTermination, null); + mXmppConnectionService.sendIqPacket(account, iq, null); } private boolean isUsingClearNet(final Account account) { @@ -263,11 +264,11 @@ ScheduledFuture schedule( void respondWithJingleError( final Account account, - final IqPacket original, + final Iq original, final String jingleCondition, final String condition, final String conditionType) { - final IqPacket response = original.generateResponse(IqPacket.TYPE.ERROR); + final Iq response = original.generateResponse(Iq.Type.ERROR); final Element error = response.addChild("error"); error.setAttribute("type", conditionType); error.addChild(condition, "urn:ietf:params:xml:ns:xmpp-stanzas"); @@ -438,7 +439,7 @@ && isUsingClearNet(account)) { final int activeDevices = account.activeDevicesWithRtpCapability(); Log.d(Config.LOGTAG, "active devices with rtp capability: " + activeDevices); if (activeDevices == 0) { - final MessagePacket reject = + final var reject = mXmppConnectionService .getMessageGenerator() .sessionReject(from, sessionId); @@ -492,10 +493,11 @@ && isUsingClearNet(account)) { if (remoteMsgId == null) { return; } - final MessagePacket errorMessage = new MessagePacket(); + final var errorMessage = + new im.conversations.android.xmpp.model.stanza.Message(); errorMessage.setTo(from); errorMessage.setId(remoteMsgId); - errorMessage.setType(MessagePacket.TYPE_ERROR); + errorMessage.setType(im.conversations.android.xmpp.model.stanza.Message.Type.ERROR); final Element error = errorMessage.addChild("error"); error.setAttribute("code", "404"); error.setAttribute("type", "cancel"); @@ -720,7 +722,7 @@ private void retractSessionProposal( rtpSessionProposal.sessionId, RtpEndUserState.RETRACTED); } - final MessagePacket messagePacket = + final var messagePacket = mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal); writeLogMissedOutgoing( account, @@ -790,7 +792,7 @@ public JingleRtpConnection initializeRtpSession( this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING); mXmppConnectionService.notifyJingleRtpConnectionUpdate( account, proposal.with, proposal.sessionId, RtpEndUserState.FINDING_DEVICE); - final MessagePacket messagePacket = + final var messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal); mXmppConnectionService.sendMessagePacket(account, messagePacket); return proposal; @@ -800,7 +802,7 @@ public JingleRtpConnection initializeRtpSession( public void sendJingleMessageFinish( final Contact contact, final String sessionId, final Reason reason) { final var account = contact.getAccount(); - final MessagePacket messagePacket = + final var messagePacket = mXmppConnectionService .getMessageGenerator() .sessionFinish(contact.getJid(), sessionId, reason); @@ -842,7 +844,7 @@ public boolean hasMatchingProposal(final Account account, final Jid with) { return false; } - public void deliverIbbPacket(final Account account, final IqPacket packet) { + public void deliverIbbPacket(final Account account, final Iq packet) { final String sid; final Element payload; final InbandBytestreamsTransport.PacketType packetType; @@ -868,7 +870,7 @@ public void deliverIbbPacket(final Account account, final IqPacket packet) { Config.LOGTAG, account.getJid().asBareJid() + ": unable to deliver ibb packet. missing sid"); account.getXmppConnection() - .sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null); + .sendIqPacket(packet.generateResponse(Iq.Type.ERROR), null); return; } for (final AbstractJingleConnection connection : this.connections.values()) { @@ -879,11 +881,11 @@ public void deliverIbbPacket(final Account account, final IqPacket packet) { if (inBandTransport.deliverPacket(packetType, packet.getFrom(), payload)) { account.getXmppConnection() .sendIqPacket( - packet.generateResponse(IqPacket.TYPE.RESULT), null); + packet.generateResponse(Iq.Type.RESULT), null); } else { account.getXmppConnection() .sendIqPacket( - packet.generateResponse(IqPacket.TYPE.ERROR), null); + packet.generateResponse(Iq.Type.ERROR), null); } return; } @@ -894,7 +896,7 @@ public void deliverIbbPacket(final Account account, final IqPacket packet) { Config.LOGTAG, account.getJid().asBareJid() + ": unable to deliver ibb packet with sid=" + sid); account.getXmppConnection() - .sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null); + .sendIqPacket(packet.generateResponse(Iq.Type.ERROR), null); } public void notifyRebound(final Account account) { @@ -945,7 +947,7 @@ private void resendSessionProposals(final Account account) { account.getJid().asBareJid() + ": resending session proposal to " + proposal.with); - final MessagePacket messagePacket = + final var messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal); mXmppConnectionService.sendMessagePacket(account, messagePacket); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java index 68564ec3c..cb8e1d48d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java @@ -31,7 +31,6 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.IbbTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.WebRTCDataChannelTransportInfo; @@ -39,7 +38,9 @@ import eu.siacs.conversations.xmpp.jingle.transports.SocksByteStreamsTransport; import eu.siacs.conversations.xmpp.jingle.transports.Transport; import eu.siacs.conversations.xmpp.jingle.transports.WebRTCDataChannelTransport; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; + +import im.conversations.android.xmpp.model.jingle.Jingle; +import im.conversations.android.xmpp.model.stanza.Iq; import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.io.CipherInputStream; @@ -112,22 +113,23 @@ public JingleFileTransferConnection( } @Override - void deliverPacket(final JinglePacket jinglePacket) { - switch (jinglePacket.getAction()) { - case SESSION_ACCEPT -> receiveSessionAccept(jinglePacket); - case SESSION_INITIATE -> receiveSessionInitiate(jinglePacket); - case SESSION_INFO -> receiveSessionInfo(jinglePacket); - case SESSION_TERMINATE -> receiveSessionTerminate(jinglePacket); - case TRANSPORT_ACCEPT -> receiveTransportAccept(jinglePacket); - case TRANSPORT_INFO -> receiveTransportInfo(jinglePacket); - case TRANSPORT_REPLACE -> receiveTransportReplace(jinglePacket); + void deliverPacket(final Iq iq) { + final var jingle = iq.getExtension(Jingle.class); + switch (jingle.getAction()) { + case SESSION_ACCEPT -> receiveSessionAccept(iq, jingle); + case SESSION_INITIATE -> receiveSessionInitiate(iq, jingle); + case SESSION_INFO -> receiveSessionInfo(iq, jingle); + case SESSION_TERMINATE -> receiveSessionTerminate(iq, jingle); + case TRANSPORT_ACCEPT -> receiveTransportAccept(iq, jingle); + case TRANSPORT_INFO -> receiveTransportInfo(iq, jingle); + case TRANSPORT_REPLACE -> receiveTransportReplace(iq, jingle); default -> { - respondOk(jinglePacket); + respondOk(iq); Log.d( Config.LOGTAG, String.format( "%s: received unhandled jingle action %s", - id.account.getJid().asBareJid(), jinglePacket.getAction())); + id.account.getJid().asBareJid(), jingle.getAction())); } } } @@ -203,33 +205,34 @@ private void sendSessionInitialize( if (transition( State.SESSION_INITIALIZED, () -> this.initiatorFileTransferContentMap = contentMap)) { - final var jinglePacket = - contentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); + final var iq = + contentMap.toJinglePacket(Jingle.Action.SESSION_INITIATE, id.sessionId); + final var jingle = iq.getExtension(Jingle.class); if (xmppAxolotlMessage != null) { this.transportSecurity = new TransportSecurity( xmppAxolotlMessage.getInnerKey(), xmppAxolotlMessage.getIV()); - final var contents = jinglePacket.getJingleContents(); + final var contents = jingle.getJingleContents(); final var rawContent = contents.get(Iterables.getOnlyElement(contentMap.contents.keySet())); if (rawContent != null) { rawContent.setSecurity(xmppAxolotlMessage); } } - jinglePacket.setTo(id.with); + iq.setTo(id.with); xmppConnectionService.sendIqPacket( id.account, - jinglePacket, - (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + iq, + (response) -> { + if (response.getType() == Iq.Type.RESULT) { xmppConnectionService.markMessage(message, Message.STATUS_OFFERED); return; } - if (response.getType() == IqPacket.TYPE.ERROR) { + if (response.getType() == Iq.Type.ERROR) { handleIqErrorResponse(response); return; } - if (response.getType() == IqPacket.TYPE.TIMEOUT) { + if (response.getType() == Iq.Type.TIMEOUT) { handleIqTimeoutResponse(response); } }); @@ -237,15 +240,15 @@ private void sendSessionInitialize( } } - private void receiveSessionAccept(final JinglePacket jinglePacket) { + private void receiveSessionAccept(final Iq jinglePacket, final Jingle jingle) { Log.d(Config.LOGTAG, "receive file transfer session accept"); if (isResponder()) { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_ACCEPT); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_ACCEPT); return; } final FileTransferContentMap contentMap; try { - contentMap = FileTransferContentMap.of(jinglePacket); + contentMap = FileTransferContentMap.of(jingle); contentMap.requireOnlyFileTransferDescription(); } catch (final RuntimeException e) { Log.d( @@ -261,7 +264,7 @@ private void receiveSessionAccept(final JinglePacket jinglePacket) { } private void receiveSessionAccept( - final JinglePacket jinglePacket, final FileTransferContentMap contentMap) { + final Iq jinglePacket, final FileTransferContentMap contentMap) { if (transition(State.SESSION_ACCEPTED, () -> setRemoteContentMap(contentMap))) { respondOk(jinglePacket); final var transport = this.transport; @@ -280,7 +283,7 @@ private void receiveSessionAccept( Log.d( Config.LOGTAG, id.account.getJid().asBareJid() + ": receive out of order session-accept"); - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_ACCEPT); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_ACCEPT); } } @@ -309,16 +312,16 @@ private static boolean configureTransportWithPeerInfo( } } - private void receiveSessionInitiate(final JinglePacket jinglePacket) { + private void receiveSessionInitiate(final Iq jinglePacket, final Jingle jingle) { if (isInitiator()) { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_INITIATE); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_INITIATE); return; } Log.d(Config.LOGTAG, "receive session initiate " + jinglePacket); final FileTransferContentMap contentMap; final FileTransferDescription.File file; try { - contentMap = FileTransferContentMap.of(jinglePacket); + contentMap = FileTransferContentMap.of(jingle); contentMap.requireContentDescriptions(); file = contentMap.requireOnlyFile(); // TODO check is offer @@ -332,7 +335,7 @@ private void receiveSessionInitiate(final JinglePacket jinglePacket) { return; } final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage; - final var contents = jinglePacket.getJingleContents(); + final var contents = jingle.getJingleContents(); final var rawContent = contents.get(Iterables.getOnlyElement(contentMap.contents.keySet())); final var security = rawContent == null ? null : rawContent.getSecurity(jinglePacket.getFrom()); @@ -349,7 +352,7 @@ private void receiveSessionInitiate(final JinglePacket jinglePacket) { } private void receiveSessionInitiate( - final JinglePacket jinglePacket, + final Iq jinglePacket, final FileTransferContentMap contentMap, final FileTransferDescription.File file, final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage) { @@ -396,7 +399,7 @@ private void receiveSessionInitiate( Log.d( Config.LOGTAG, id.account.getJid().asBareJid() + ": receive out of order session-initiate"); - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_INITIATE); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_INITIATE); } } @@ -453,9 +456,9 @@ public void onFailure(@NonNull Throwable throwable) { private void sendSessionAccept(final FileTransferContentMap contentMap) { setLocalContentMap(contentMap); - final var jinglePacket = - contentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId); - send(jinglePacket); + final var iq = + contentMap.toJinglePacket(Jingle.Action.SESSION_ACCEPT, id.sessionId); + send(iq); // this needs to come after session-accept or else our candidate-error might arrive first this.transport.connect(); this.transport.readyToSentAdditionalCandidates(); @@ -541,9 +544,9 @@ private void failureToAcceptSession(final Throwable throwable) { sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage()); } - private void receiveSessionInfo(final JinglePacket jinglePacket) { + private void receiveSessionInfo(final Iq jinglePacket, final Jingle jingle) { respondOk(jinglePacket); - final var sessionInfo = FileTransferDescription.getSessionInfo(jinglePacket); + final var sessionInfo = FileTransferDescription.getSessionInfo(jingle); if (sessionInfo instanceof FileTransferDescription.Checksum checksum) { receiveSessionInfoChecksum(checksum); } else if (sessionInfo instanceof FileTransferDescription.Received received) { @@ -559,9 +562,9 @@ private void receiveSessionInfoReceived(final FileTransferDescription.Received r Log.d(Config.LOGTAG, "peer confirmed received " + received); } - private void receiveSessionTerminate(final JinglePacket jinglePacket) { + private void receiveSessionTerminate(final Iq jinglePacket, final Jingle jingle) { respondOk(jinglePacket); - final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason(); + final Jingle.ReasonWrapper wrapper = jingle.getReason(); final State previous = this.state; Log.d( Config.LOGTAG, @@ -590,15 +593,15 @@ private void receiveSessionTerminate(final JinglePacket jinglePacket) { finish(); } - private void receiveTransportAccept(final JinglePacket jinglePacket) { + private void receiveTransportAccept(final Iq jinglePacket, final Jingle jingle) { if (isResponder()) { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.TRANSPORT_ACCEPT); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.TRANSPORT_ACCEPT); return; } Log.d(Config.LOGTAG, "receive transport accept " + jinglePacket); final GenericTransportInfo transportInfo; try { - transportInfo = FileTransferContentMap.of(jinglePacket).requireOnlyTransportInfo(); + transportInfo = FileTransferContentMap.of(jingle).requireOnlyTransportInfo(); } catch (final RuntimeException e) { Log.d( Config.LOGTAG, @@ -610,15 +613,15 @@ private void receiveTransportAccept(final JinglePacket jinglePacket) { return; } if (isInState(State.SESSION_ACCEPTED)) { - final var group = jinglePacket.getGroup(); + final var group = jingle.getGroup(); receiveTransportAccept(jinglePacket, new Transport.TransportInfo(transportInfo, group)); } else { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.TRANSPORT_ACCEPT); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.TRANSPORT_ACCEPT); } } private void receiveTransportAccept( - final JinglePacket jinglePacket, final Transport.TransportInfo transportInfo) { + final Iq jinglePacket, final Transport.TransportInfo transportInfo) { final FileTransferContentMap remoteContentMap = getRemoteContentMap().withTransport(transportInfo); setRemoteContentMap(remoteContentMap); @@ -637,11 +640,11 @@ private void receiveTransportAccept( } } - private void receiveTransportInfo(final JinglePacket jinglePacket) { + private void receiveTransportInfo(final Iq jinglePacket, final Jingle jingle) { final FileTransferContentMap contentMap; final GenericTransportInfo transportInfo; try { - contentMap = FileTransferContentMap.of(jinglePacket); + contentMap = FileTransferContentMap.of(jingle); transportInfo = contentMap.requireOnlyTransportInfo(); } catch (final RuntimeException e) { Log.d( @@ -725,14 +728,14 @@ private void receiveTransportInfo( } } - private void receiveTransportReplace(final JinglePacket jinglePacket) { + private void receiveTransportReplace(final Iq jinglePacket, final Jingle jingle) { if (isInitiator()) { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.TRANSPORT_REPLACE); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.TRANSPORT_REPLACE); return; } final GenericTransportInfo transportInfo; try { - transportInfo = FileTransferContentMap.of(jinglePacket).requireOnlyTransportInfo(); + transportInfo = FileTransferContentMap.of(jingle).requireOnlyTransportInfo(); } catch (final RuntimeException e) { Log.d( Config.LOGTAG, @@ -746,12 +749,12 @@ private void receiveTransportReplace(final JinglePacket jinglePacket) { if (isInState(State.SESSION_ACCEPTED)) { receiveTransportReplace(jinglePacket, transportInfo); } else { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.TRANSPORT_REPLACE); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.TRANSPORT_REPLACE); } } private void receiveTransportReplace( - final JinglePacket jinglePacket, final GenericTransportInfo transportInfo) { + final Iq jinglePacket, final GenericTransportInfo transportInfo) { respondOk(jinglePacket); final Transport currentTransport = this.transport; if (currentTransport != null) { @@ -796,11 +799,11 @@ public void onFailure(@NonNull Throwable throwable) { private void sendTransportAccept(final FileTransferContentMap contentMap) { setLocalContentMap(contentMap); - final var jinglePacket = + final var iq = contentMap .transportInfo() - .toJinglePacket(JinglePacket.Action.TRANSPORT_ACCEPT, id.sessionId); - send(jinglePacket); + .toJinglePacket(Jingle.Action.TRANSPORT_ACCEPT, id.sessionId); + send(iq); transport.connect(); } @@ -982,11 +985,10 @@ private void sendSessionInfoChecksum(List hashes) } private void sendSessionInfo(final FileTransferDescription.SessionInfo sessionInfo) { - final var jinglePacket = - new JinglePacket(JinglePacket.Action.SESSION_INFO, this.id.sessionId); - jinglePacket.addJingleChild(sessionInfo.asElement()); - jinglePacket.setTo(this.id.with); - send(jinglePacket); + final var iq = new Iq(Iq.Type.SET); + final var jinglePacket = iq.addExtension(new Jingle(Jingle.Action.SESSION_INFO, this.id.sessionId)); + jinglePacket.addChild(sessionInfo.asElement()); + send(iq); } @Override @@ -1039,11 +1041,11 @@ public void onFailure(@NonNull Throwable throwable) { private void sendTransportReplace(final FileTransferContentMap contentMap) { setLocalContentMap(contentMap); - final var jinglePacket = + final var iq = contentMap .transportInfo() - .toJinglePacket(JinglePacket.Action.TRANSPORT_REPLACE, id.sessionId); - send(jinglePacket); + .toJinglePacket(Jingle.Action.TRANSPORT_REPLACE, id.sessionId); + send(iq); } @Override @@ -1068,9 +1070,9 @@ public void sendTransportInfo( + contentName); return; } - final JinglePacket jinglePacket = - transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); - send(jinglePacket); + final Iq iq = + transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId); + send(iq); } @Override @@ -1081,12 +1083,12 @@ public void onCandidateUsed( Log.e(Config.LOGTAG, "local content map is null on candidate used"); return; } - final var jinglePacket = + final var iq = contentMap .candidateUsed(streamId, candidate.cid) - .toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); - Log.d(Config.LOGTAG, "sending candidate used " + jinglePacket); - send(jinglePacket); + .toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId); + Log.d(Config.LOGTAG, "sending candidate used " + iq); + send(iq); } @Override @@ -1096,12 +1098,12 @@ public void onCandidateError(final String streamId) { Log.e(Config.LOGTAG, "local content map is null on candidate used"); return; } - final var jinglePacket = + final var iq = contentMap .candidateError(streamId) - .toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); - Log.d(Config.LOGTAG, "sending candidate error " + jinglePacket); - send(jinglePacket); + .toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId); + Log.d(Config.LOGTAG, "sending candidate error " + iq); + send(iq); } @Override @@ -1111,11 +1113,11 @@ public void onProxyActivated(String streamId, SocksByteStreamsTransport.Candidat Log.e(Config.LOGTAG, "local content map is null on candidate used"); return; } - final var jinglePacket = + final var iq = contentMap .proxyActivated(streamId, candidate.cid) - .toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); - send(jinglePacket); + .toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId); + send(iq); } @Override @@ -1251,10 +1253,10 @@ private boolean stopFileTransfer(final Reason reason) { message, Message.STATUS_SEND_FAILED, Message.ERROR_MESSAGE_CANCELLED); } terminateTransport(); - final JinglePacket jinglePacket = - new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId); - jinglePacket.setReason(reason, "User requested to stop file transfer"); - send(jinglePacket); + final Iq iq = new Iq(Iq.Type.SET); + final var jingle = iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId)); + jingle.setReason(reason, "User requested to stop file transfer"); + send(iq); finish(); return true; } else { 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 26e62373c..4471cfc62 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -43,13 +43,13 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.Content; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.Proceed; import eu.siacs.conversations.xmpp.jingle.stanzas.Propose; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; + +import im.conversations.android.xmpp.model.jingle.Jingle; +import im.conversations.android.xmpp.model.stanza.Iq; import org.webrtc.EglBase; import org.webrtc.IceCandidate; @@ -139,24 +139,25 @@ public class JingleRtpConnection extends AbstractJingleConnection } @Override - synchronized void deliverPacket(final JinglePacket jinglePacket) { - switch (jinglePacket.getAction()) { - case SESSION_INITIATE -> receiveSessionInitiate(jinglePacket); - case TRANSPORT_INFO -> receiveTransportInfo(jinglePacket); - case SESSION_ACCEPT -> receiveSessionAccept(jinglePacket); - case SESSION_TERMINATE -> receiveSessionTerminate(jinglePacket); - case CONTENT_ADD -> receiveContentAdd(jinglePacket); - case CONTENT_ACCEPT -> receiveContentAccept(jinglePacket); - case CONTENT_REJECT -> receiveContentReject(jinglePacket); - case CONTENT_REMOVE -> receiveContentRemove(jinglePacket); - case CONTENT_MODIFY -> receiveContentModify(jinglePacket); + synchronized void deliverPacket(final Iq iq) { + final var jingle = iq.getExtension(Jingle.class); + switch (jingle.getAction()) { + case SESSION_INITIATE -> receiveSessionInitiate(iq, jingle); + case TRANSPORT_INFO -> receiveTransportInfo(iq, jingle); + case SESSION_ACCEPT -> receiveSessionAccept(iq, jingle); + case SESSION_TERMINATE -> receiveSessionTerminate(iq); + case CONTENT_ADD -> receiveContentAdd(iq, jingle); + case CONTENT_ACCEPT -> receiveContentAccept(iq); + case CONTENT_REJECT -> receiveContentReject(iq, jingle); + case CONTENT_REMOVE -> receiveContentRemove(iq, jingle); + case CONTENT_MODIFY -> receiveContentModify(iq, jingle); default -> { - respondOk(jinglePacket); + respondOk(iq); Log.d( Config.LOGTAG, String.format( "%s: received unhandled jingle action %s", - id.account.getJid().asBareJid(), jinglePacket.getAction())); + id.account.getJid().asBareJid(), jingle.getAction())); } } } @@ -183,9 +184,10 @@ synchronized void notifyRebound() { } } - private void receiveSessionTerminate(final JinglePacket jinglePacket) { + private void receiveSessionTerminate(final Iq jinglePacket) { respondOk(jinglePacket); - final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason(); + final var jingle = jinglePacket.getExtension(Jingle.class); + final Jingle.ReasonWrapper wrapper = jingle.getReason(); final State previous = this.state; Log.d( Config.LOGTAG, @@ -214,7 +216,7 @@ private void receiveSessionTerminate(final JinglePacket jinglePacket) { finish(); } - private void receiveTransportInfo(final JinglePacket jinglePacket) { + private void receiveTransportInfo(final Iq jinglePacket, final Jingle jingle) { // Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to // INITIALIZED only after transport-info has been received if (isInState( @@ -225,7 +227,7 @@ private void receiveTransportInfo(final JinglePacket jinglePacket) { State.SESSION_ACCEPTED)) { final RtpContentMap contentMap; try { - contentMap = RtpContentMap.of(jinglePacket); + contentMap = RtpContentMap.of(jingle); } catch (final IllegalArgumentException | NullPointerException e) { Log.d( Config.LOGTAG, @@ -255,7 +257,7 @@ private void receiveTransportInfo(final JinglePacket jinglePacket) { } private void receiveTransportInfo( - final JinglePacket jinglePacket, final RtpContentMap contentMap) { + final Iq jinglePacket, final RtpContentMap contentMap) { final Set>> candidates = contentMap.contents.entrySet(); final RtpContentMap remote = getRemoteContentMap(); @@ -294,17 +296,17 @@ private void receiveTransportInfo( } } - private void receiveContentAdd(final JinglePacket jinglePacket) { + private void receiveContentAdd(final Iq iq, final Jingle jingle) { final RtpContentMap modification; try { - modification = RtpContentMap.of(jinglePacket); + modification = RtpContentMap.of(jingle); modification.requireContentDescriptions(); } catch (final RuntimeException e) { Log.d( Config.LOGTAG, id.getAccount().getJid().asBareJid() + ": improperly formatted contents", Throwables.getRootCause(e)); - respondOk(jinglePacket); + respondOk(iq); webRTCWrapper.close(); sendSessionTerminate(Reason.of(e), e.getMessage()); return; @@ -320,12 +322,12 @@ private void receiveContentAdd(final JinglePacket jinglePacket) { new FutureCallback<>() { @Override public void onSuccess(final RtpContentMap rtpContentMap) { - receiveContentAdd(jinglePacket, rtpContentMap); + receiveContentAdd(iq, rtpContentMap); } @Override public void onFailure(@NonNull Throwable throwable) { - respondOk(jinglePacket); + respondOk(iq); final Throwable rootCause = Throwables.getRootCause(throwable); Log.d( Config.LOGTAG, @@ -339,12 +341,12 @@ public void onFailure(@NonNull Throwable throwable) { }, MoreExecutors.directExecutor()); } else { - terminateWithOutOfOrder(jinglePacket); + terminateWithOutOfOrder(iq); } } private void receiveContentAdd( - final JinglePacket jinglePacket, final RtpContentMap modification) { + final Iq jinglePacket, final RtpContentMap modification) { final RtpContentMap remote = getRemoteContentMap(); if (!Collections.disjoint(modification.getNames(), remote.getNames())) { respondOk(jinglePacket); @@ -396,10 +398,11 @@ private void receiveContentAdd( } } - private void receiveContentAccept(final JinglePacket jinglePacket) { + private void receiveContentAccept(final Iq jinglePacket) { + final var jingle = jinglePacket.getExtension(Jingle.class); final RtpContentMap receivedContentAccept; try { - receivedContentAccept = RtpContentMap.of(jinglePacket); + receivedContentAccept = RtpContentMap.of(jingle); receivedContentAccept.requireContentDescriptions(); } catch (final RuntimeException e) { Log.d( @@ -484,14 +487,14 @@ private void receiveContentAccept(final RtpContentMap receivedContentAccept) { updateEndUserState(); } - private void receiveContentModify(final JinglePacket jinglePacket) { + private void receiveContentModify(final Iq jinglePacket, final Jingle jingle) { if (this.state != State.SESSION_ACCEPTED) { terminateWithOutOfOrder(jinglePacket); return; } final Map modification = Maps.transformEntries( - jinglePacket.getJingleContents(), (key, value) -> value.getSenders()); + jingle.getJingleContents(), (key, value) -> value.getSenders()); final boolean isInitiator = isInitiator(); final RtpContentMap currentOutgoing = this.outgoingContentAdd; final RtpContentMap remoteContentMap = this.getRemoteContentMap(); @@ -594,10 +597,10 @@ private static ImmutableMultimap parseCan return candidateBuilder.build(); } - private void receiveContentReject(final JinglePacket jinglePacket) { + private void receiveContentReject(final Iq jinglePacket, final Jingle jingle) { final RtpContentMap receivedContentReject; try { - receivedContentReject = RtpContentMap.of(jinglePacket); + receivedContentReject = RtpContentMap.of(jingle); } catch (final RuntimeException e) { Log.d( Config.LOGTAG, @@ -650,10 +653,10 @@ private void receiveContentReject(final Set summary) { + summary); } - private void receiveContentRemove(final JinglePacket jinglePacket) { + private void receiveContentRemove(final Iq jinglePacket, final Jingle jingle) { final RtpContentMap receivedContentRemove; try { - receivedContentRemove = RtpContentMap.of(jinglePacket); + receivedContentRemove = RtpContentMap.of(jingle); receivedContentRemove.requireContentDescriptions(); } catch (final RuntimeException e) { Log.d( @@ -687,8 +690,8 @@ private void receiveContentRemove(final RtpContentMap receivedContentRemove) { String.format( "%s only supports %s as a means to retract a not yet accepted %s", BuildConfig.APP_NAME, - JinglePacket.Action.CONTENT_REMOVE, - JinglePacket.Action.CONTENT_ADD)); + Jingle.Action.CONTENT_REMOVE, + Jingle.Action.CONTENT_ADD)); } } @@ -713,10 +716,10 @@ public synchronized void retractContentAdd() { return; } this.outgoingContentAdd = null; - final JinglePacket retract = + final Iq retract = outgoingContentAdd .toStub() - .toJinglePacket(JinglePacket.Action.CONTENT_REMOVE, id.sessionId); + .toJinglePacket(Jingle.Action.CONTENT_REMOVE, id.sessionId); this.send(retract); Log.d( Config.LOGTAG, @@ -772,16 +775,16 @@ public synchronized void acceptContentAdd( "content addition is receive only. we want to upgrade to 'both'"); final RtpContentMap modifiedSenders = incomingContentAdd.modifiedSenders(Content.Senders.BOTH); - final JinglePacket proposedContentModification = + final Iq proposedContentModification = modifiedSenders .toStub() - .toJinglePacket(JinglePacket.Action.CONTENT_MODIFY, id.sessionId); + .toJinglePacket(Jingle.Action.CONTENT_MODIFY, id.sessionId); proposedContentModification.setTo(id.with); xmppConnectionService.sendIqPacket( id.account, proposedContentModification, - (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { Log.d( Config.LOGTAG, id.account.getJid().asBareJid() @@ -875,7 +878,7 @@ public void onSuccess(final RtpContentMap rtpContentMap) { @Override public void onFailure(@NonNull final Throwable throwable) { - failureToPerformAction(JinglePacket.Action.CONTENT_ACCEPT, throwable); + failureToPerformAction(Jingle.Action.CONTENT_ACCEPT, throwable); } }, MoreExecutors.directExecutor()); @@ -887,9 +890,9 @@ public void onFailure(@NonNull final Throwable throwable) { } private void sendContentAccept(final RtpContentMap contentAcceptMap) { - final JinglePacket jinglePacket = - contentAcceptMap.toJinglePacket(JinglePacket.Action.CONTENT_ACCEPT, id.sessionId); - send(jinglePacket); + final Iq iq = + contentAcceptMap.toJinglePacket(Jingle.Action.CONTENT_ACCEPT, id.sessionId); + send(iq); } public synchronized void rejectContentAdd() { @@ -903,20 +906,20 @@ public synchronized void rejectContentAdd() { } private void rejectContentAdd(final RtpContentMap contentMap) { - final JinglePacket jinglePacket = + final Iq iq = contentMap .toStub() - .toJinglePacket(JinglePacket.Action.CONTENT_REJECT, id.sessionId); + .toJinglePacket(Jingle.Action.CONTENT_REJECT, id.sessionId); Log.d( Config.LOGTAG, id.getAccount().getJid().asBareJid() + ": rejecting content " + ContentAddition.summary(contentMap)); - send(jinglePacket); + send(iq); } private boolean checkForIceRestart( - final JinglePacket jinglePacket, final RtpContentMap rtpContentMap) { + final Iq jinglePacket, final RtpContentMap rtpContentMap) { final RtpContentMap existing = getRemoteContentMap(); final Set existingCredentials; final IceUdpTransportInfo.Credentials newCredentials; @@ -995,7 +998,7 @@ private void storePeerDtlsSetup(final IceUdpTransportInfo.Setup setup) { } private boolean applyIceRestart( - final JinglePacket jinglePacket, + final Iq jinglePacket, final RtpContentMap restartContentMap, final boolean isOffer) throws ExecutionException, InterruptedException { @@ -1096,7 +1099,7 @@ private List toIdentificationTags(final RtpContentMap rtpContentMap) { } private ListenableFuture receiveRtpContentMap( - final JinglePacket jinglePacket, final boolean expectVerification) { + final Jingle jinglePacket, final boolean expectVerification) { try { return receiveRtpContentMap(RtpContentMap.of(jinglePacket), expectVerification); } catch (final Exception e) { @@ -1139,12 +1142,12 @@ private ListenableFuture receiveRtpContentMap( } } - private void receiveSessionInitiate(final JinglePacket jinglePacket) { + private void receiveSessionInitiate(final Iq jinglePacket, final Jingle jingle) { if (isInitiator()) { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_INITIATE); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_INITIATE); return; } - final ListenableFuture future = receiveRtpContentMap(jinglePacket, false); + final ListenableFuture future = receiveRtpContentMap(jingle, false); Futures.addCallback( future, new FutureCallback<>() { @@ -1163,7 +1166,7 @@ public void onFailure(@NonNull final Throwable throwable) { } private void receiveSessionInitiate( - final JinglePacket jinglePacket, final RtpContentMap contentMap) { + final Iq jinglePacket, final RtpContentMap contentMap) { try { contentMap.requireContentDescriptions(); contentMap.requireDTLSFingerprint(true); @@ -1223,13 +1226,13 @@ private void receiveSessionInitiate( } } - private void receiveSessionAccept(final JinglePacket jinglePacket) { + private void receiveSessionAccept(final Iq jinglePacket, final Jingle jingle) { if (isResponder()) { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_ACCEPT); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_ACCEPT); return; } final ListenableFuture future = - receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint()); + receiveRtpContentMap(jingle, this.omemoVerification.hasFingerprint()); Futures.addCallback( future, new FutureCallback<>() { @@ -1254,7 +1257,7 @@ public void onFailure(@NonNull final Throwable throwable) { } private void receiveSessionAccept( - final JinglePacket jinglePacket, final RtpContentMap contentMap) { + final Iq jinglePacket, final RtpContentMap contentMap) { try { contentMap.requireContentDescriptions(); contentMap.requireDTLSFingerprint(); @@ -1399,7 +1402,7 @@ private void failureToAcceptSession(final Throwable throwable) { } private void failureToPerformAction( - final JinglePacket.Action action, final Throwable throwable) { + final Jingle.Action action, final Throwable throwable) { if (isTerminated()) { return; } @@ -1470,8 +1473,8 @@ private void sendSessionAccept(final RtpContentMap rtpContentMap) { return; } transitionOrThrow(State.SESSION_ACCEPTED); - final JinglePacket sessionAccept = - rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId); + final Iq sessionAccept = + rtpContentMap.toJinglePacket(Jingle.Action.SESSION_ACCEPT, id.sessionId); send(sessionAccept); } @@ -1939,8 +1942,8 @@ private void sendSessionInitiate(final RtpContentMap rtpContentMap, final State return; } this.transitionOrThrow(targetState); - final JinglePacket sessionInitiate = - rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); + final Iq sessionInitiate = + rtpContentMap.toJinglePacket(Jingle.Action.SESSION_INITIATE, id.sessionId); send(sessionInitiate); } @@ -2008,9 +2011,9 @@ private void sendTransportInfo( + contentName); return; } - final JinglePacket jinglePacket = - transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); - send(jinglePacket); + final Iq iq = + transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId); + send(iq); } public RtpEndUserState getEndUserState() { @@ -2364,8 +2367,8 @@ private void sendJingleMessage(final String action) { } private void sendJingleMessage(final String action, final Jid to) { - final MessagePacket messagePacket = new MessagePacket(); - messagePacket.setType(MessagePacket.TYPE_CHAT); // we want to carbon copy those + final var messagePacket = new im.conversations.android.xmpp.model.stanza.Message(); + messagePacket.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); // we want to carbon copy those messagePacket.setTo(to); final Element intent = messagePacket @@ -2386,7 +2389,7 @@ private void sendJingleMessage(final String action, final Jid to) { private void sendJingleMessageFinish(final Reason reason) { final var account = id.getAccount(); - final MessagePacket messagePacket = + final var messagePacket = xmppConnectionService .getMessageGenerator() .sessionFinish(id.with, id.sessionId, reason); @@ -2545,34 +2548,34 @@ private synchronized void renegotiate() { private void initiateIceRestart(final RtpContentMap rtpContentMap) { final RtpContentMap transportInfo = rtpContentMap.transportInfo(); - final JinglePacket jinglePacket = - transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); - Log.d(Config.LOGTAG, "initiating ice restart: " + jinglePacket); - jinglePacket.setTo(id.with); + final Iq iq = + transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId); + Log.d(Config.LOGTAG, "initiating ice restart: " + iq); + iq.setTo(id.with); xmppConnectionService.sendIqPacket( id.account, - jinglePacket, - (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + iq, + (response) -> { + if (response.getType() == Iq.Type.RESULT) { Log.d(Config.LOGTAG, "received success to our ice restart"); setLocalContentMap(rtpContentMap); webRTCWrapper.setIsReadyToReceiveIceCandidates(true); return; } - if (response.getType() == IqPacket.TYPE.ERROR) { + if (response.getType() == Iq.Type.ERROR) { if (isTieBreak(response)) { Log.d(Config.LOGTAG, "received tie-break as result of ice restart"); return; } handleIqErrorResponse(response); } - if (response.getType() == IqPacket.TYPE.TIMEOUT) { + if (response.getType() == Iq.Type.TIMEOUT) { handleIqTimeoutResponse(response); } }); } - private boolean isTieBreak(final IqPacket response) { + private boolean isTieBreak(final Iq response) { final Element error = response.findChild("error"); return error != null && error.hasChild("tie-break", Namespace.JINGLE_ERRORS); } @@ -2593,7 +2596,7 @@ public void onSuccess(final RtpContentMap outgoingContentMap) { @Override public void onFailure(@NonNull Throwable throwable) { - failureToPerformAction(JinglePacket.Action.CONTENT_ADD, throwable); + failureToPerformAction(Jingle.Action.CONTENT_ADD, throwable); } }, MoreExecutors.directExecutor()); @@ -2601,21 +2604,21 @@ public void onFailure(@NonNull Throwable throwable) { private void sendContentAdd(final RtpContentMap contentAdd) { - final JinglePacket jinglePacket = - contentAdd.toJinglePacket(JinglePacket.Action.CONTENT_ADD, id.sessionId); - jinglePacket.setTo(id.with); + final Iq iq = + contentAdd.toJinglePacket(Jingle.Action.CONTENT_ADD, id.sessionId); + iq.setTo(id.with); xmppConnectionService.sendIqPacket( id.account, - jinglePacket, - (connection, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + iq, + (response) -> { + if (response.getType() == Iq.Type.RESULT) { Log.d( Config.LOGTAG, id.getAccount().getJid().asBareJid() + ": received ACK to our content-add"); return; } - if (response.getType() == IqPacket.TYPE.ERROR) { + if (response.getType() == Iq.Type.ERROR) { if (isTieBreak(response)) { this.outgoingContentAdd = null; Log.d(Config.LOGTAG, "received tie-break as result of our content-add"); @@ -2623,7 +2626,7 @@ private void sendContentAdd(final RtpContentMap contentAdd) { } handleIqErrorResponse(response); } - if (response.getType() == IqPacket.TYPE.TIMEOUT) { + if (response.getType() == Iq.Type.TIMEOUT) { handleIqTimeoutResponse(response); } }); @@ -2821,13 +2824,13 @@ && isResponder()) { private void discoverIceServers(final OnIceServersDiscovered onIceServersDiscovered) { if (id.account.getXmppConnection().getFeatures().externalServiceDiscovery()) { - final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.setTo(id.account.getDomain()); request.addChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY); xmppConnectionService.sendIqPacket( id.account, request, - (account, response) -> { + (response) -> { final var iceServers = IceServers.parse(response); if (iceServers.isEmpty()) { Log.w( diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java index 9a60b3924..263905051 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java @@ -1,9 +1,8 @@ package eu.siacs.conversations.xmpp.jingle; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.PacketReceived; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; +import im.conversations.android.xmpp.model.stanza.Iq; -public interface OnJinglePacketReceived extends PacketReceived { - void onJinglePacketReceived(Account account, JinglePacket packet); +public interface OnJinglePacketReceived { + void onJinglePacketReceived(Account account, Iq packet); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java index 94f8ca300..5036b2b85 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java @@ -18,9 +18,9 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; +import im.conversations.android.xmpp.model.jingle.Jingle; import java.util.Collection; import java.util.LinkedHashMap; @@ -38,7 +38,7 @@ public RtpContentMap( super(group, contents); } - public static RtpContentMap of(final JinglePacket jinglePacket) { + public static RtpContentMap of(final Jingle jinglePacket) { final Map> contents = of(jinglePacket.getJingleContents()); if (isOmemoVerified(contents)) { @@ -52,7 +52,7 @@ private static boolean isOmemoVerified( Map> contents) { final Collection> values = contents.values(); - if (values.size() == 0) { + if (values.isEmpty()) { return false; } for (final DescriptionTransport descriptionTransport : diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java index 3878d98d9..0553e203e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java @@ -15,6 +15,7 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.xmpp.model.jingle.Jingle; import java.util.List; @@ -55,15 +56,11 @@ public File getFile() { return new File(size, name, mediaType, hashes); } - public static SessionInfo getSessionInfo(@NonNull final JinglePacket jinglePacket) { - Preconditions.checkNotNull(jinglePacket); + public static SessionInfo getSessionInfo(@NonNull final Jingle jingle) { + Preconditions.checkNotNull(jingle); Preconditions.checkArgument( - jinglePacket.getAction() == JinglePacket.Action.SESSION_INFO, + jingle.getAction() == Jingle.Action.SESSION_INFO, "jingle packet is not a session-info"); - final Element jingle = jinglePacket.findChild("jingle", Namespace.JINGLE); - if (jingle == null) { - return null; - } final Element checksum = jingle.findChild("checksum", Namespace.JINGLE_APPS_FILE_TRANSFER); if (checksum != null) { final Element file = checksum.findChild("file", Namespace.JINGLE_APPS_FILE_TRANSFER); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/InbandBytestreamsTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/InbandBytestreamsTransport.java index ce2d4b31f..849b2a404 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/InbandBytestreamsTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/InbandBytestreamsTransport.java @@ -16,7 +16,7 @@ import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jingle.stanzas.IbbTransportInfo; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import java.io.Closeable; import java.io.IOException; @@ -96,7 +96,7 @@ public CountDownLatch getTerminationLatch() { } private void openInBandTransport() { - final var iqPacket = new IqPacket(IqPacket.TYPE.SET); + final var iqPacket = new Iq(Iq.Type.SET); iqPacket.setTo(with); final var open = iqPacket.addChild("open", Namespace.IBB); open.setAttribute("block-size", this.blockSize); @@ -106,8 +106,8 @@ private void openInBandTransport() { xmppConnection.sendIqPacket(iqPacket, this::receiveResponseToOpen); } - private void receiveResponseToOpen(final Account account, final IqPacket response) { - if (response.getType() == IqPacket.TYPE.RESULT) { + private void receiveResponseToOpen(final Iq response) { + if (response.getType() == Iq.Type.RESULT) { Log.d(Config.LOGTAG, "ibb open was accepted"); this.transportCallback.onTransportEstablished(); this.blockSenderThread.start(); @@ -284,7 +284,7 @@ public void run() { private void sendIbbBlock(final int sequence, final byte[] block) { Log.d(Config.LOGTAG, "sending ibb block #" + sequence + " " + block.length + " bytes"); - final var iqPacket = new IqPacket(IqPacket.TYPE.SET); + final var iqPacket = new Iq(Iq.Type.SET); iqPacket.setTo(with); final var data = iqPacket.addChild("data", Namespace.IBB); data.setAttribute("sid", this.streamId); @@ -292,8 +292,8 @@ private void sendIbbBlock(final int sequence, final byte[] block) { data.setContent(BaseEncoding.base64().encode(block)); this.xmppConnection.sendIqPacket( iqPacket, - (a, response) -> { - if (response.getType() != IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() != Iq.Type.RESULT) { Log.d( Config.LOGTAG, "received iq error in response to data block #" + sequence); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java index bbda1c622..2925592ea 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java @@ -32,7 +32,7 @@ import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection; import eu.siacs.conversations.xmpp.jingle.DirectConnectionUtils; import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import java.io.IOException; import java.io.InputStream; @@ -250,7 +250,7 @@ private ConnectionWithOwner selectConnection( private ListenableFuture activateProxy(final Candidate candidate) { Log.d(Config.LOGTAG, "trying to activate our proxy " + candidate); final SettableFuture iqFuture = SettableFuture.create(); - final IqPacket proxyActivation = new IqPacket(IqPacket.TYPE.SET); + final Iq proxyActivation = new Iq(Iq.Type.SET); proxyActivation.setTo(candidate.jid); final Element query = proxyActivation.addChild("query", Namespace.BYTE_STREAMS); query.setAttribute("sid", this.streamId); @@ -258,17 +258,18 @@ private ListenableFuture activateProxy(final Candidate candidate) { activate.setContent(id.with.toEscapedString()); xmppConnection.sendIqPacket( proxyActivation, - (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { Log.d(Config.LOGTAG, "our proxy has been activated"); transportCallback.onProxyActivated(this.streamId, candidate); iqFuture.set(candidate.cid); - } else if (response.getType() == IqPacket.TYPE.TIMEOUT) { + } else if (response.getType() == Iq.Type.TIMEOUT) { iqFuture.setException(new TimeoutException()); } else { + final var account = id.account; Log.d( Config.LOGTAG, - a.getJid().asBareJid() + account.getJid().asBareJid() + ": failed to activate proxy on " + candidate.jid); iqFuture.setException(new IllegalStateException("Proxy activation failed")); @@ -314,14 +315,14 @@ private ListenableFuture getProxyCandidate() { return Futures.immediateFailedFuture( new IllegalStateException("No proxy/streamer found")); } - final IqPacket iqRequest = new IqPacket(IqPacket.TYPE.GET); + final Iq iqRequest = new Iq(Iq.Type.GET); iqRequest.setTo(streamer); iqRequest.query(Namespace.BYTE_STREAMS); final SettableFuture candidateFuture = SettableFuture.create(); xmppConnection.sendIqPacket( iqRequest, - (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element query = response.findChild("query", Namespace.BYTE_STREAMS); final Element streamHost = query == null @@ -349,7 +350,7 @@ private ListenableFuture getProxyCandidate() { 655360 + (initiator ? 0 : 15), CandidateType.PROXY)); - } else if (response.getType() == IqPacket.TYPE.TIMEOUT) { + } else if (response.getType() == Iq.Type.TIMEOUT) { candidateFuture.setException(new TimeoutException()); } else { candidateFuture.setException( diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java index e4dd730d5..904c4aba8 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java @@ -22,7 +22,7 @@ import eu.siacs.conversations.xmpp.jingle.WebRTCWrapper; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.WebRTCDataChannelTransportInfo; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import org.webrtc.CandidatePairChangeEvent; import org.webrtc.DataChannel; @@ -234,14 +234,14 @@ private ListenableFuture> getIceServers() { if (xmppConnection.getFeatures().externalServiceDiscovery()) { final SettableFuture> iceServerFuture = SettableFuture.create(); - final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.setTo(this.account.getDomain()); request.addChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY); xmppConnection.sendIqPacket( request, - (account, response) -> { + (response) -> { final var iceServers = IceServers.parse(response); - if (iceServers.size() == 0) { + if (iceServers.isEmpty()) { Log.w( Config.LOGTAG, account.getJid().asBareJid() diff --git a/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java b/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java index ef1da8561..ee3770ead 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java +++ b/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java @@ -4,7 +4,7 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; public class PublishOptions { @@ -37,8 +37,8 @@ public static Bundle persistentWhitelistAccessMaxItems() { return options; } - public static boolean preconditionNotMet(IqPacket response) { - final Element error = response.getType() == IqPacket.TYPE.ERROR ? response.findChild("error") : null; + public static boolean preconditionNotMet(Iq response) { + final Element error = response.getType() == Iq.Type.ERROR ? response.findChild("error") : null; return error != null && error.hasChild("precondition-not-met", Namespace.PUBSUB_ERROR); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java deleted file mode 100644 index 2291a9896..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java +++ /dev/null @@ -1,42 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas; - -import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.InvalidJid; - -abstract public class AbstractAcknowledgeableStanza extends AbstractStanza { - - protected AbstractAcknowledgeableStanza(String name) { - super(name); - } - - - public String getId() { - return this.getAttribute("id"); - } - - public void setId(final String id) { - setAttribute("id", id); - } - - private Element getErrorConditionElement() { - final Element error = findChild("error"); - if (error == null) { - return null; - } - for (final Element element : error.getChildren()) { - if (!element.getName().equals("text")) { - return element; - } - } - return null; - } - - public String getErrorCondition() { - final Element condition = getErrorConditionElement(); - return condition == null ? null : condition.getName(); - } - - public boolean valid() { - return InvalidJid.isValid(getFrom()) && InvalidJid.isValid(getTo()); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java deleted file mode 100644 index c0b3d07bd..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java +++ /dev/null @@ -1,53 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas; - -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.Jid; - -public class AbstractStanza extends Element { - - protected AbstractStanza(final String name) { - super(name); - } - - public Jid getTo() { - return getAttributeAsJid("to"); - } - - public Jid getFrom() { - return getAttributeAsJid("from"); - } - - public void setTo(final Jid to) { - if (to != null) { - setAttribute("to", to); - } - } - - public void setFrom(final Jid from) { - if (from != null) { - setAttribute("from", from); - } - } - - public boolean fromServer(final Account account) { - final Jid from = getFrom(); - return from == null - || from.equals(account.getDomain()) - || from.equals(account.getJid().asBareJid()) - || from.equals(account.getJid()); - } - - public boolean toServer(final Account account) { - final Jid to = getTo(); - return to == null - || to.equals(account.getDomain()) - || to.equals(account.getJid().asBareJid()) - || to.equals(account.getJid()); - } - - public boolean fromAccount(final Account account) { - final Jid from = getFrom(); - return from != null && from.asBareJid().equals(account.getJid().asBareJid()); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java deleted file mode 100644 index ba8588e1f..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java +++ /dev/null @@ -1,75 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas; - -import eu.siacs.conversations.xml.Element; - -public class IqPacket extends AbstractAcknowledgeableStanza { - - public enum TYPE { - ERROR, - SET, - RESULT, - GET, - INVALID, - TIMEOUT - } - - public IqPacket(final TYPE type) { - super("iq"); - if (type != TYPE.INVALID) { - this.setAttribute("type", type.toString().toLowerCase()); - } - } - - public IqPacket() { - super("iq"); - } - - public Element query() { - Element query = findChild("query"); - if (query == null) { - query = addChild("query"); - } - return query; - } - - public Element query(final String xmlns) { - final Element query = query(); - query.setAttribute("xmlns", xmlns); - return query(); - } - - public TYPE getType() { - final String type = getAttribute("type"); - if (type == null) { - return TYPE.INVALID; - } - switch (type) { - case "error": - return TYPE.ERROR; - case "result": - return TYPE.RESULT; - case "set": - return TYPE.SET; - case "get": - return TYPE.GET; - case "timeout": - return TYPE.TIMEOUT; - default: - return TYPE.INVALID; - } - } - - public IqPacket generateResponse(final TYPE type) { - final IqPacket packet = new IqPacket(type); - packet.setTo(this.getFrom()); - packet.setId(this.getId()); - return packet; - } - - @Override - public boolean valid() { - String id = getId(); - return id != null && super.valid(); - } - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java deleted file mode 100644 index 86068bf77..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java +++ /dev/null @@ -1,100 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas; - -import android.util.Pair; - -import eu.siacs.conversations.parser.AbstractParser; -import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xml.LocalizedContent; - -public class MessagePacket extends AbstractAcknowledgeableStanza { - public static final int TYPE_CHAT = 0; - public static final int TYPE_NORMAL = 2; - public static final int TYPE_GROUPCHAT = 3; - public static final int TYPE_ERROR = 4; - public static final int TYPE_HEADLINE = 5; - - public MessagePacket() { - super("message"); - } - - public LocalizedContent getBody() { - return findInternationalizedChildContentInDefaultNamespace("body"); - } - - public void setBody(String text) { - this.children.remove(findChild("body")); - Element body = new Element("body"); - body.setContent(text); - this.children.add(0, body); - } - - public void setAxolotlMessage(Element axolotlMessage) { - this.children.remove(findChild("body")); - this.children.add(0, axolotlMessage); - } - - public void setType(int type) { - switch (type) { - case TYPE_CHAT: - this.setAttribute("type", "chat"); - break; - case TYPE_GROUPCHAT: - this.setAttribute("type", "groupchat"); - break; - case TYPE_NORMAL: - break; - case TYPE_ERROR: - this.setAttribute("type","error"); - break; - default: - this.setAttribute("type", "chat"); - break; - } - } - - public int getType() { - String type = getAttribute("type"); - if (type == null) { - return TYPE_NORMAL; - } else if (type.equals("normal")) { - return TYPE_NORMAL; - } else if (type.equals("chat")) { - return TYPE_CHAT; - } else if (type.equals("groupchat")) { - return TYPE_GROUPCHAT; - } else if (type.equals("error")) { - return TYPE_ERROR; - } else if (type.equals("headline")) { - return TYPE_HEADLINE; - } else { - return TYPE_NORMAL; - } - } - - public Pair getForwardedMessagePacket(String name, String namespace) { - Element wrapper = findChild(name, namespace); - if (wrapper == null) { - return null; - } - Element forwarded = wrapper.findChild("forwarded", "urn:xmpp:forward:0"); - if (forwarded == null) { - return null; - } - MessagePacket packet = create(forwarded.findChild("message")); - if (packet == null) { - return null; - } - Long timestamp = AbstractParser.parseTimestamp(forwarded, null); - return new Pair(packet,timestamp); - } - - public static MessagePacket create(Element element) { - if (element == null) { - return null; - } - MessagePacket packet = new MessagePacket(); - packet.setAttributes(element.getAttributes()); - packet.setChildren(element.getChildren()); - return packet; - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java deleted file mode 100644 index c321498d8..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java +++ /dev/null @@ -1,8 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas; - -public class PresencePacket extends AbstractAcknowledgeableStanza { - - public PresencePacket() { - super("presence"); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java deleted file mode 100644 index e1c465f72..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java +++ /dev/null @@ -1,11 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas.csi; - -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; - -public class ActivePacket extends AbstractStanza { - public ActivePacket() { - super("active"); - setAttribute("xmlns", Namespace.CSI); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java deleted file mode 100644 index 1b74de066..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java +++ /dev/null @@ -1,11 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas.csi; - -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; - -public class InactivePacket extends AbstractStanza { - public InactivePacket() { - super("inactive"); - setAttribute("xmlns", Namespace.CSI); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java deleted file mode 100644 index 9e7b991a4..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java +++ /dev/null @@ -1,14 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas.streammgmt; - -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; - -public class AckPacket extends AbstractStanza { - - public AckPacket(final int sequence) { - super("a"); - this.setAttribute("xmlns", Namespace.STREAM_MANAGEMENT); - this.setAttribute("h", Integer.toString(sequence)); - } - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java deleted file mode 100644 index 95558b143..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java +++ /dev/null @@ -1,14 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas.streammgmt; - -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; - -public class EnablePacket extends AbstractStanza { - - public EnablePacket() { - super("enable"); - this.setAttribute("xmlns", Namespace.STREAM_MANAGEMENT); - this.setAttribute("resume", "true"); - } - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java deleted file mode 100644 index 4e0e0f11a..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java +++ /dev/null @@ -1,13 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas.streammgmt; - -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; - -public class RequestPacket extends AbstractStanza { - - public RequestPacket() { - super("r"); - this.setAttribute("xmlns", Namespace.STREAM_MANAGEMENT); - } - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java deleted file mode 100644 index 38681d7c1..000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java +++ /dev/null @@ -1,15 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas.streammgmt; - -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; - -public class ResumePacket extends AbstractStanza { - - public ResumePacket(final String id, final int sequence) { - super("resume"); - this.setAttribute("xmlns", Namespace.STREAM_MANAGEMENT); - this.setAttribute("previd", id); - this.setAttribute("h", Integer.toString(sequence)); - } - -} diff --git a/src/main/java/im/conversations/android/xmpp/Entity.java b/src/main/java/im/conversations/android/xmpp/Entity.java new file mode 100644 index 000000000..a578d2507 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/Entity.java @@ -0,0 +1,34 @@ +package im.conversations.android.xmpp; + +import org.jxmpp.jid.Jid; + +public abstract class Entity { + + public final Jid address; + + private Entity(final Jid address) { + this.address = address; + } + + public static class DiscoItem extends Entity { + + private DiscoItem(Jid address) { + super(address); + } + } + + public static class Presence extends Entity { + + private Presence(Jid address) { + super(address); + } + } + + public static Presence presence(final Jid address) { + return new Presence(address); + } + + public static DiscoItem discoItem(final Jid address) { + return new DiscoItem(address); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java b/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java new file mode 100644 index 000000000..b282c0791 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java @@ -0,0 +1,133 @@ +package im.conversations.android.xmpp; + +import com.google.common.base.Strings; +import com.google.common.collect.Collections2; +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Ordering; +import com.google.common.hash.Hashing; +import com.google.common.io.BaseEncoding; +import im.conversations.android.xmpp.model.data.Data; +import im.conversations.android.xmpp.model.data.Field; +import im.conversations.android.xmpp.model.disco.info.Feature; +import im.conversations.android.xmpp.model.disco.info.Identity; +import im.conversations.android.xmpp.model.disco.info.InfoQuery; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +public final class EntityCapabilities { + public static EntityCapsHash hash(final InfoQuery info) { + final StringBuilder s = new StringBuilder(); + final List orderedIdentities = + Ordering.from( + (Comparator) + (a, b) -> + ComparisonChain.start() + .compare( + blankNull(a.getCategory()), + blankNull(b.getCategory())) + .compare( + blankNull(a.getType()), + blankNull(b.getType())) + .compare( + blankNull(a.getLang()), + blankNull(b.getLang())) + .compare( + blankNull(a.getIdentityName()), + blankNull(b.getIdentityName())) + .result()) + .sortedCopy(info.getIdentities()); + + for (final Identity id : orderedIdentities) { + s.append(blankNull(id.getCategory())) + .append("/") + .append(blankNull(id.getType())) + .append("/") + .append(blankNull(id.getLang())) + .append("/") + .append(blankNull(id.getIdentityName())) + .append("<"); + } + + final List features = + Ordering.natural() + .sortedCopy(Collections2.transform(info.getFeatures(), Feature::getVar)); + for (final String feature : features) { + s.append(clean(feature)).append("<"); + } + + final List extensions = + Ordering.from(Comparator.comparing(Data::getFormType)) + .sortedCopy(info.getExtensions(Data.class)); + + for (final Data extension : extensions) { + s.append(clean(extension.getFormType())).append("<"); + final List fields = + Ordering.from( + Comparator.comparing( + (Field lhs) -> Strings.nullToEmpty(lhs.getFieldName()))) + .sortedCopy(extension.getFields()); + for (final Field field : fields) { + s.append(Strings.nullToEmpty(field.getFieldName())).append("<"); + final List values = Ordering.natural().sortedCopy(field.getValues()); + for (final String value : values) { + s.append(blankNull(value)).append("<"); + } + } + } + return new EntityCapsHash( + Hashing.sha1().hashString(s.toString(), StandardCharsets.UTF_8).asBytes()); + } + + private static String clean(String s) { + return s.replace("<", "<"); + } + + private static String blankNull(String s) { + return s == null ? "" : clean(s); + } + + public abstract static class Hash { + public final byte[] hash; + + protected Hash(byte[] hash) { + this.hash = hash; + } + + public String encoded() { + return BaseEncoding.base64().encode(hash); + } + + public abstract String capabilityNode(final String node); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Hash hash1 = (Hash) o; + return Arrays.equals(hash, hash1.hash); + } + + @Override + public int hashCode() { + return Arrays.hashCode(hash); + } + } + + public static class EntityCapsHash extends Hash { + + protected EntityCapsHash(byte[] hash) { + super(hash); + } + + @Override + public String capabilityNode(String node) { + return String.format("%s#%s", node, encoded()); + } + + public static EntityCapsHash of(final String encoded) { + return new EntityCapsHash(BaseEncoding.base64().decode(encoded)); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java b/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java new file mode 100644 index 000000000..1d8a35a68 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java @@ -0,0 +1,185 @@ +package im.conversations.android.xmpp; + +import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.collect.Collections2; +import com.google.common.collect.Ordering; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; +import com.google.common.io.BaseEncoding; +import com.google.common.primitives.Bytes; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.xmpp.model.Hash; +import im.conversations.android.xmpp.model.data.Data; +import im.conversations.android.xmpp.model.data.Field; +import im.conversations.android.xmpp.model.data.Value; +import im.conversations.android.xmpp.model.disco.info.Feature; +import im.conversations.android.xmpp.model.disco.info.Identity; +import im.conversations.android.xmpp.model.disco.info.InfoQuery; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Objects; + +public class EntityCapabilities2 { + + private static final char UNIT_SEPARATOR = 0x1f; + private static final char RECORD_SEPARATOR = 0x1e; + + private static final char GROUP_SEPARATOR = 0x1d; + + private static final char FILE_SEPARATOR = 0x1c; + + public static EntityCaps2Hash hash(final InfoQuery info) { + return hash(Hash.Algorithm.SHA_256, info); + } + + public static EntityCaps2Hash hash(final Hash.Algorithm algorithm, final InfoQuery info) { + final String result = algorithm(info); + final var hashFunction = toHashFunction(algorithm); + return new EntityCaps2Hash( + algorithm, hashFunction.hashString(result, StandardCharsets.UTF_8).asBytes()); + } + + private static HashFunction toHashFunction(final Hash.Algorithm algorithm) { + switch (algorithm) { + case SHA_1: + return Hashing.sha1(); + case SHA_256: + return Hashing.sha256(); + case SHA_512: + return Hashing.sha512(); + default: + throw new IllegalArgumentException("Unknown hash algorithm"); + } + } + + private static String asHex(final String message) { + return Joiner.on(' ') + .join( + Collections2.transform( + Bytes.asList(message.getBytes(StandardCharsets.UTF_8)), + b -> String.format("%02x", b))); + } + + private static String algorithm(final InfoQuery infoQuery) { + return features(infoQuery.getFeatures()) + + identities(infoQuery.getIdentities()) + + extensions(infoQuery.getExtensions(Data.class)); + } + + private static String identities(final Collection identities) { + return Joiner.on("") + .join( + Ordering.natural() + .sortedCopy( + Collections2.transform( + identities, EntityCapabilities2::identity))) + + FILE_SEPARATOR; + } + + private static String identity(final Identity identity) { + return Strings.nullToEmpty(identity.getCategory()) + + UNIT_SEPARATOR + + Strings.nullToEmpty(identity.getType()) + + UNIT_SEPARATOR + + Strings.nullToEmpty(identity.getLang()) + + UNIT_SEPARATOR + + Strings.nullToEmpty(identity.getIdentityName()) + + UNIT_SEPARATOR + + RECORD_SEPARATOR; + } + + private static String features(Collection features) { + return Joiner.on("") + .join( + Ordering.natural() + .sortedCopy( + Collections2.transform( + features, EntityCapabilities2::feature))) + + FILE_SEPARATOR; + } + + private static String feature(final Feature feature) { + return Strings.nullToEmpty(feature.getVar()) + UNIT_SEPARATOR; + } + + private static String value(final Value value) { + return Strings.nullToEmpty(value.getContent()) + UNIT_SEPARATOR; + } + + private static String values(final Collection values) { + return Joiner.on("") + .join( + Ordering.natural() + .sortedCopy( + Collections2.transform( + values, EntityCapabilities2::value))); + } + + private static String field(final Field field) { + return Strings.nullToEmpty(field.getFieldName()) + + UNIT_SEPARATOR + + values(field.getExtensions(Value.class)) + + RECORD_SEPARATOR; + } + + private static String fields(final Collection fields) { + return Joiner.on("") + .join( + Ordering.natural() + .sortedCopy( + Collections2.transform( + fields, EntityCapabilities2::field))) + + GROUP_SEPARATOR; + } + + private static String extension(final Data data) { + return fields(data.getExtensions(Field.class)); + } + + private static String extensions(final Collection extensions) { + return Joiner.on("") + .join( + Ordering.natural() + .sortedCopy( + Collections2.transform( + extensions, + EntityCapabilities2::extension))) + + FILE_SEPARATOR; + } + + public static class EntityCaps2Hash extends EntityCapabilities.Hash { + + public final Hash.Algorithm algorithm; + + protected EntityCaps2Hash(final Hash.Algorithm algorithm, byte[] hash) { + super(hash); + this.algorithm = algorithm; + } + + public static EntityCaps2Hash of(final Hash.Algorithm algorithm, final String encoded) { + return new EntityCaps2Hash(algorithm, BaseEncoding.base64().decode(encoded)); + } + + @Override + public String capabilityNode(String node) { + return String.format( + "%s#%s.%s", Namespace.ENTITY_CAPABILITIES_2, algorithm.toString(), encoded()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + EntityCaps2Hash that = (EntityCaps2Hash) o; + return algorithm == that.algorithm; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), algorithm); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java b/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java new file mode 100644 index 000000000..04352b559 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java @@ -0,0 +1,78 @@ +package im.conversations.android.xmpp; + + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + +import eu.siacs.conversations.xml.Element; + +import im.conversations.android.xmpp.model.Extension; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +public final class ExtensionFactory { + + public static Element create(final String name, final String namespace) { + final Class clazz = of(name, namespace); + if (clazz == null) { + return new Element(name, namespace); + } + final Constructor constructor; + try { + constructor = clazz.getDeclaredConstructor(); + } catch (final NoSuchMethodException e) { + throw new IllegalStateException( + String.format("%s has no default constructor", clazz.getName()),e); + } + try { + return constructor.newInstance(); + } catch (final IllegalAccessException + | InstantiationException + | InvocationTargetException e) { + throw new IllegalStateException( + String.format("%s has inaccessible default constructor", clazz.getName()),e); + } + } + + private static Class of(final String name, final String namespace) { + return Extensions.EXTENSION_CLASS_MAP.get(new Id(name, namespace)); + } + + public static Id id(final Class clazz) { + return Extensions.EXTENSION_CLASS_MAP.inverse().get(clazz); + } + + private ExtensionFactory() {} + + public static class Id { + public final String name; + public final String namespace; + + public Id(String name, String namespace) { + this.name = name; + this.namespace = namespace; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Id id = (Id) o; + return Objects.equal(name, id.name) && Objects.equal(namespace, id.namespace); + } + + @Override + public int hashCode() { + return Objects.hashCode(name, namespace); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("name", name) + .add("namespace", namespace) + .toString(); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java b/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java new file mode 100644 index 000000000..81a55f18c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java @@ -0,0 +1,112 @@ +package im.conversations.android.xmpp; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.common.collect.ImmutableMap; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +public class NodeConfiguration implements Map { + + private static final String PERSIST_ITEMS = "pubsub#persist_items"; + private static final String ACCESS_MODEL = "pubsub#access_model"; + private static final String SEND_LAST_PUBLISHED_ITEM = "pubsub#send_last_published_item"; + private static final String MAX_ITEMS = "pubsub#max_items"; + private static final String NOTIFY_DELETE = "pubsub#notify_delete"; + private static final String NOTIFY_RETRACT = "pubsub#notify_retract"; + + public static final NodeConfiguration OPEN = + new NodeConfiguration( + new ImmutableMap.Builder() + .put(PERSIST_ITEMS, Boolean.TRUE) + .put(ACCESS_MODEL, "open") + .build()); + public static final NodeConfiguration PRESENCE = + new NodeConfiguration( + new ImmutableMap.Builder() + .put(PERSIST_ITEMS, Boolean.TRUE) + .put(ACCESS_MODEL, "presence") + .build()); + public static final NodeConfiguration WHITELIST_MAX_ITEMS = + new NodeConfiguration( + new ImmutableMap.Builder() + .put(PERSIST_ITEMS, Boolean.TRUE) + .put(ACCESS_MODEL, "whitelist") + .put(SEND_LAST_PUBLISHED_ITEM, "never") + .put(MAX_ITEMS, "max") + .put(NOTIFY_DELETE, Boolean.TRUE) + .put(NOTIFY_RETRACT, Boolean.TRUE) + .build()); + private final Map delegate; + + private NodeConfiguration(Map map) { + this.delegate = map; + } + + @Override + public int size() { + return this.delegate.size(); + } + + @Override + public boolean isEmpty() { + return this.delegate.isEmpty(); + } + + @Override + public boolean containsKey(@Nullable Object o) { + return this.delegate.containsKey(o); + } + + @Override + public boolean containsValue(@Nullable Object o) { + return this.delegate.containsValue(o); + } + + @Nullable + @Override + public Object get(@Nullable Object o) { + return this.delegate.get(o); + } + + @Nullable + @Override + public Object put(String s, Object o) { + return this.delegate.put(s, o); + } + + @Nullable + @Override + public Object remove(@Nullable Object o) { + return this.delegate.remove(o); + } + + @Override + public void putAll(@NonNull Map map) { + this.delegate.putAll(map); + } + + @Override + public void clear() { + this.delegate.clear(); + } + + @NonNull + @Override + public Set keySet() { + return this.delegate.keySet(); + } + + @NonNull + @Override + public Collection values() { + return this.delegate.values(); + } + + @NonNull + @Override + public Set> entrySet() { + return this.delegate.entrySet(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/Page.java b/src/main/java/im/conversations/android/xmpp/Page.java new file mode 100644 index 000000000..21aa219a4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/Page.java @@ -0,0 +1,31 @@ +package im.conversations.android.xmpp; + +import androidx.annotation.NonNull; +import com.google.common.base.MoreObjects; + +public class Page { + + public final String first; + public final String last; + public final Integer count; + + public Page(String first, String last, Integer count) { + this.first = first; + this.last = last; + this.count = count; + } + + public static Page emptyWithCount(final String id, final Integer count) { + return new Page(id, id, count); + } + + @NonNull + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("first", first) + .add("last", last) + .add("count", count) + .toString(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/Range.java b/src/main/java/im/conversations/android/xmpp/Range.java new file mode 100644 index 000000000..8aff5094e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/Range.java @@ -0,0 +1,40 @@ +package im.conversations.android.xmpp; + +import androidx.annotation.NonNull; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + +public class Range { + + public final Order order; + public final String id; + + public Range(final Order order, final String id) { + this.order = order; + this.id = id; + } + + @NonNull + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("order", order).add("id", id).toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Range range = (Range) o; + return order == range.order && Objects.equal(id, range.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(order, id); + } + + public enum Order { + NORMAL, + REVERSE + } +} diff --git a/src/main/java/im/conversations/android/xmpp/Timestamps.java b/src/main/java/im/conversations/android/xmpp/Timestamps.java new file mode 100644 index 000000000..0135901ab --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/Timestamps.java @@ -0,0 +1,44 @@ +package im.conversations.android.xmpp; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public final class Timestamps { + + private Timestamps() { + throw new IllegalStateException("Do not instantiate me"); + } + + public static long parse(final String input) throws ParseException { + if (input == null) { + throw new IllegalArgumentException("timestamp should not be null"); + } + final String timestamp = input.replace("Z", "+0000"); + final SimpleDateFormat simpleDateFormat = + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US); + final long milliseconds = getMilliseconds(timestamp); + final String formatted = + timestamp.substring(0, 19) + timestamp.substring(timestamp.length() - 5); + final Date date = simpleDateFormat.parse(formatted); + if (date == null) { + throw new IllegalArgumentException("Date was null"); + } + return date.getTime() + milliseconds; + } + + private static long getMilliseconds(final String timestamp) { + if (timestamp.length() >= 25 && timestamp.charAt(19) == '.') { + final String millis = timestamp.substring(19, timestamp.length() - 5); + try { + double fractions = Double.parseDouble("0" + millis); + return Math.round(1000 * fractions); + } catch (final NumberFormatException e) { + return 0; + } + } else { + return 0; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/AuthenticationStreamFeature.java b/src/main/java/im/conversations/android/xmpp/model/AuthenticationStreamFeature.java new file mode 100644 index 000000000..3b9a03761 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/AuthenticationStreamFeature.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model; + +import java.util.Collection; + +public abstract class AuthenticationStreamFeature extends StreamFeature{ + + public AuthenticationStreamFeature(final Class clazz) { + super(clazz); + } + + public abstract Collection getMechanismNames(); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/ByteContent.java b/src/main/java/im/conversations/android/xmpp/model/ByteContent.java new file mode 100644 index 000000000..0ca6212ff --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/ByteContent.java @@ -0,0 +1,33 @@ +package im.conversations.android.xmpp.model; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Strings; +import com.google.common.io.BaseEncoding; + +import eu.siacs.conversations.xml.Element; + +public interface ByteContent { + + String getContent(); + + default byte[] asBytes() { + final var content = this.getContent(); + if (Strings.isNullOrEmpty(content)) { + throw new IllegalStateException( + String.format("%s element is lacking content", getClass().getName())); + } + final var contentCleaned = CharMatcher.whitespace().removeFrom(content); + if (BaseEncoding.base64().canDecode(contentCleaned)) { + return BaseEncoding.base64().decode(contentCleaned); + } else { + throw new IllegalStateException( + String.format("%s element contains invalid base64", getClass().getName())); + } + } + + default void setContent(final byte[] bytes) { + setContent(BaseEncoding.base64().encode(bytes)); + } + + Element setContent(final String content); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java b/src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java new file mode 100644 index 000000000..00e2b652a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model; + +public abstract class DeliveryReceipt extends Extension { + + protected DeliveryReceipt(Class clazz) { + super(clazz); + } + + public abstract String getId(); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java b/src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java new file mode 100644 index 000000000..a5a7533bb --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java @@ -0,0 +1,8 @@ +package im.conversations.android.xmpp.model; + +public abstract class DeliveryReceiptRequest extends Extension { + + protected DeliveryReceiptRequest(Class clazz) { + super(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/Extension.java b/src/main/java/im/conversations/android/xmpp/model/Extension.java new file mode 100644 index 000000000..094ed6ae5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/Extension.java @@ -0,0 +1,62 @@ +package im.conversations.android.xmpp.model; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Collections2; +import com.google.common.collect.Iterables; + +import eu.siacs.conversations.xml.Element; + +import im.conversations.android.xmpp.ExtensionFactory; + +import java.util.Collection; + +public class Extension extends Element { + + private Extension(final ExtensionFactory.Id id) { + super(id.name, id.namespace); + } + + public Extension(final Class clazz) { + this( + Preconditions.checkNotNull( + ExtensionFactory.id(clazz), + String.format( + "%s does not seem to be annotated with @XmlElement", + clazz.getName()))); + Preconditions.checkArgument( + getClass().equals(clazz), "clazz passed in constructor must match class"); + } + + public boolean hasExtension(final Class clazz) { + return Iterables.any(this.children, clazz::isInstance); + } + + public E getExtension(final Class clazz) { + final var extension = Iterables.find(this.children, clazz::isInstance, null); + if (extension == null) { + return null; + } + return clazz.cast(extension); + } + + public Collection getExtensions(final Class clazz) { + return Collections2.transform( + Collections2.filter(this.children, clazz::isInstance), clazz::cast); + } + + public Collection getExtensionIds() { + return Collections2.transform( + this.children, c -> new ExtensionFactory.Id(c.getName(), c.getNamespace())); + } + + public T addExtension(T child) { + this.addChild(child); + return child; + } + + public void addExtensions(final Collection extensions) { + for (final Extension extension : extensions) { + addExtension(extension); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/Hash.java b/src/main/java/im/conversations/android/xmpp/model/Hash.java new file mode 100644 index 000000000..8c41add8d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/Hash.java @@ -0,0 +1,46 @@ +package im.conversations.android.xmpp.model; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.common.base.CaseFormat; +import com.google.common.base.Strings; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; + +@XmlElement(namespace = Namespace.HASHES) +public class Hash extends Extension { + public Hash() { + super(Hash.class); + } + + public Algorithm getAlgorithm() { + return Algorithm.tryParse(this.getAttribute("algo")); + } + + public void setAlgorithm(final Algorithm algorithm) { + this.setAttribute("algo", algorithm.toString()); + } + + public enum Algorithm { + SHA_1, + SHA_256, + SHA_512; + + public static Algorithm tryParse(@Nullable final String name) { + try { + return valueOf( + CaseFormat.LOWER_HYPHEN.to( + CaseFormat.UPPER_UNDERSCORE, Strings.nullToEmpty(name))); + } catch (final IllegalArgumentException e) { + return null; + } + } + + @NonNull + @Override + public String toString() { + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, super.toString()); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/StreamElement.java b/src/main/java/im/conversations/android/xmpp/model/StreamElement.java new file mode 100644 index 000000000..ca5fd0053 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/StreamElement.java @@ -0,0 +1,8 @@ +package im.conversations.android.xmpp.model; + +public abstract class StreamElement extends Extension { + + protected StreamElement(Class clazz) { + super(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/StreamFeature.java b/src/main/java/im/conversations/android/xmpp/model/StreamFeature.java new file mode 100644 index 000000000..eadd8d8c4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/StreamFeature.java @@ -0,0 +1,8 @@ +package im.conversations.android.xmpp.model; + +public abstract class StreamFeature extends Extension{ + + public StreamFeature(Class clazz) { + super(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/addressing/Address.java b/src/main/java/im/conversations/android/xmpp/model/addressing/Address.java new file mode 100644 index 000000000..f812ec53b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/addressing/Address.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.addressing; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Address extends Extension { + public Address() { + super(Address.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/addressing/Addresses.java b/src/main/java/im/conversations/android/xmpp/model/addressing/Addresses.java new file mode 100644 index 000000000..3ecafc530 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/addressing/Addresses.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.addressing; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Addresses extends Extension { + public Addresses() { + super(Addresses.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/addressing/package-info.java b/src/main/java/im/conversations/android/xmpp/model/addressing/package-info.java new file mode 100644 index 000000000..6c504489e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/addressing/package-info.java @@ -0,0 +1,6 @@ +@XmlPackage(namespace = Namespace.ADDRESSING) +package im.conversations.android.xmpp.model.addressing; + +import eu.siacs.conversations.xml.Namespace; + +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/avatar/Data.java b/src/main/java/im/conversations/android/xmpp/model/avatar/Data.java new file mode 100644 index 000000000..b661bca3a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/avatar/Data.java @@ -0,0 +1,14 @@ +package im.conversations.android.xmpp.model.avatar; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.ByteContent; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.AVATAR_DATA) +public class Data extends Extension implements ByteContent { + + public Data() { + super(Data.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java b/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java new file mode 100644 index 000000000..f544af72f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java @@ -0,0 +1,37 @@ +package im.conversations.android.xmpp.model.avatar; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.AVATAR_METADATA) +public class Info extends Extension { + + public Info() { + super(Info.class); + } + + public long getHeight() { + return this.getLongAttribute("height"); + } + + public long getWidth() { + return this.getLongAttribute("width"); + } + + public long getBytes() { + return this.getLongAttribute("bytes"); + } + + public String getType() { + return this.getAttribute("type"); + } + + public String getUrl() { + return this.getAttribute("url"); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java b/src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java new file mode 100644 index 000000000..400f98957 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.avatar; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.AVATAR_METADATA) +public class Metadata extends Extension { + + public Metadata() { + super(Metadata.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java new file mode 100644 index 000000000..2321c2e49 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java @@ -0,0 +1,60 @@ +package im.conversations.android.xmpp.model.axolotl; + +import com.google.common.collect.Iterables; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.whispersystems.libsignal.ecc.ECPublicKey; +import org.whispersystems.libsignal.state.PreKeyRecord; + +@XmlElement +public class Bundle extends Extension { + + public Bundle() { + super(Bundle.class); + } + + public SignedPreKey getSignedPreKey() { + return this.getExtension(SignedPreKey.class); + } + + public SignedPreKeySignature getSignedPreKeySignature() { + return this.getExtension(SignedPreKeySignature.class); + } + + public IdentityKey getIdentityKey() { + return this.getExtension(IdentityKey.class); + } + + public PreKey getRandomPreKey() { + final var preKeys = this.getExtension(PreKeys.class); + final Collection preKeyList = + preKeys == null ? Collections.emptyList() : preKeys.getExtensions(PreKey.class); + return Iterables.get(preKeyList, (int) (preKeyList.size() * Math.random()), null); + } + + public void setIdentityKey(final ECPublicKey ecPublicKey) { + final var identityKey = this.addExtension(new IdentityKey()); + identityKey.setContent(ecPublicKey); + } + + public void setSignedPreKey( + final int id, final ECPublicKey ecPublicKey, final byte[] signature) { + final var signedPreKey = this.addExtension(new SignedPreKey()); + signedPreKey.setId(id); + signedPreKey.setContent(ecPublicKey); + final var signedPreKeySignature = this.addExtension(new SignedPreKeySignature()); + signedPreKeySignature.setContent(signature); + } + + public void addPreKeys(final List preKeyRecords) { + final var preKeys = this.addExtension(new PreKeys()); + for (final PreKeyRecord preKeyRecord : preKeyRecords) { + final var preKey = preKeys.addExtension(new PreKey()); + preKey.setId(preKeyRecord.getId()); + preKey.setContent(preKeyRecord.getKeyPair().getPublicKey()); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java new file mode 100644 index 000000000..0ad10d702 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java @@ -0,0 +1,22 @@ +package im.conversations.android.xmpp.model.axolotl; + +import com.google.common.base.Strings; +import com.google.common.primitives.Ints; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Device extends Extension { + + public Device() { + super(Device.class); + } + + public Integer getDeviceId() { + return Ints.tryParse(Strings.nullToEmpty(this.getAttribute("id"))); + } + + public void setDeviceId(int deviceId) { + this.setAttribute("id", deviceId); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java new file mode 100644 index 000000000..ec4fce469 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java @@ -0,0 +1,35 @@ +package im.conversations.android.xmpp.model.axolotl; + +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableSet; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import java.util.Objects; +import java.util.Set; + +@XmlElement(name = "list") +public class DeviceList extends Extension { + + public DeviceList() { + super(DeviceList.class); + } + + public Collection getDevices() { + return this.getExtensions(Device.class); + } + + public Set getDeviceIds() { + return ImmutableSet.copyOf( + Collections2.filter( + Collections2.transform(getDevices(), Device::getDeviceId), + Objects::nonNull)); + } + + public void setDeviceIds(Collection deviceIds) { + for (final Integer deviceId : deviceIds) { + final var device = this.addExtension(new Device()); + device.setDeviceId(deviceId); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java new file mode 100644 index 000000000..2008fb017 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java @@ -0,0 +1,23 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.xmpp.model.ByteContent; +import org.whispersystems.libsignal.InvalidKeyException; +import org.whispersystems.libsignal.ecc.Curve; +import org.whispersystems.libsignal.ecc.ECPublicKey; + +public interface ECPublicKeyContent extends ByteContent { + + default ECPublicKey asECPublicKey() { + try { + return Curve.decodePoint(asBytes(), 0); + } catch (InvalidKeyException e) { + throw new IllegalStateException( + String.format("%s does not contain a valid ECPublicKey", getClass().getName()), + e); + } + } + + default void setContent(final ECPublicKey ecPublicKey) { + setContent(ecPublicKey.serialize()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java new file mode 100644 index 000000000..1a98068ab --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java @@ -0,0 +1,24 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Encrypted extends Extension { + + public Encrypted() { + super(Encrypted.class); + } + + public boolean hasPayload() { + return hasExtension(Payload.class); + } + + public Header getHeader() { + return getExtension(Header.class); + } + + public Payload getPayload() { + return getExtension(Payload.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java new file mode 100644 index 000000000..91e2bd87b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java @@ -0,0 +1,45 @@ +package im.conversations.android.xmpp.model.axolotl; + +import com.google.common.base.Optional; +import com.google.common.collect.Iterables; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import java.util.Objects; + +@XmlElement +public class Header extends Extension { + + public Header() { + super(Header.class); + } + + public void addIv(byte[] iv) { + this.addExtension(new IV()).setContent(iv); + } + + public void setSourceDevice(long sourceDeviceId) { + this.setAttribute("sid", sourceDeviceId); + } + + public Optional getSourceDevice() { + return getOptionalIntAttribute("sid"); + } + + public Collection getKeys() { + return this.getExtensions(Key.class); + } + + public Key getKey(final int deviceId) { + return Iterables.find( + getKeys(), key -> Objects.equals(key.getRemoteDeviceId(), deviceId), null); + } + + public byte[] getIv() { + final IV iv = this.getExtension(IV.class); + if (iv == null) { + throw new IllegalStateException("No IV in header"); + } + return iv.asBytes(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java new file mode 100644 index 000000000..22164976a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.ByteContent; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "iv") +public class IV extends Extension implements ByteContent { + + public IV() { + super(IV.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java new file mode 100644 index 000000000..f48fcbd7c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "identityKey") +public class IdentityKey extends Extension implements ECPublicKeyContent { + + public IdentityKey() { + super(IdentityKey.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Key.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Key.java new file mode 100644 index 000000000..3ad7357b8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Key.java @@ -0,0 +1,29 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.ByteContent; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Key extends Extension implements ByteContent { + + public Key() { + super(Key.class); + } + + public void setIsPreKey(boolean isPreKey) { + this.setAttribute("prekey", isPreKey); + } + + public boolean isPreKey() { + return this.getAttributeAsBoolean("prekey"); + } + + public void setRemoteDeviceId(final int remoteDeviceId) { + this.setAttribute("rid", remoteDeviceId); + } + + public Integer getRemoteDeviceId() { + return getOptionalIntAttribute("rid").orNull(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java new file mode 100644 index 000000000..9c5870110 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.ByteContent; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Payload extends Extension implements ByteContent { + + public Payload() { + super(Payload.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java new file mode 100644 index 000000000..a7d39c1da --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.axolotl; + +import com.google.common.primitives.Ints; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "preKeyPublic") +public class PreKey extends Extension implements ECPublicKeyContent { + + public PreKey() { + super(PreKey.class); + } + + public int getId() { + return Ints.saturatedCast(this.getLongAttribute("preKeyId")); + } + + public void setId(int id) { + this.setAttribute("preKeyId", id); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java new file mode 100644 index 000000000..3613b8aa8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "prekeys") +public class PreKeys extends Extension { + + public PreKeys() { + super(PreKeys.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java new file mode 100644 index 000000000..0e0ca7282 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.axolotl; + +import com.google.common.primitives.Ints; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "signedPreKeyPublic") +public class SignedPreKey extends Extension implements ECPublicKeyContent { + + public SignedPreKey() { + super(SignedPreKey.class); + } + + public int getId() { + return Ints.saturatedCast(this.getLongAttribute("signedPreKeyId")); + } + + public void setId(final int id) { + this.setAttribute("signedPreKeyId", id); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java new file mode 100644 index 000000000..5051cb1b1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.ByteContent; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "signedPreKeySignature") +public class SignedPreKeySignature extends Extension implements ByteContent { + + public SignedPreKeySignature() { + super(SignedPreKeySignature.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java new file mode 100644 index 000000000..ad3d21c16 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.AXOLOTL) +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java b/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java new file mode 100644 index 000000000..27264f754 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java @@ -0,0 +1,34 @@ +package im.conversations.android.xmpp.model.bind; + +import com.google.common.base.Strings; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Bind extends Extension { + + public Bind() { + super(Bind.class); + } + + public void setResource(final String resource) { + this.addExtension(new Resource(resource)); + } + + public eu.siacs.conversations.xmpp.Jid getJid() { + final var jidExtension = this.getExtension(Jid.class); + if (jidExtension == null) { + return null; + } + final var content = jidExtension.getContent(); + if (Strings.isNullOrEmpty(content)) { + return null; + } + try { + return eu.siacs.conversations.xmpp.Jid.ofEscaped(content); + } catch (final IllegalArgumentException e) { + return null; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/Jid.java b/src/main/java/im/conversations/android/xmpp/model/bind/Jid.java new file mode 100644 index 000000000..04633a009 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind/Jid.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.bind; + + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Jid extends Extension { + + public Jid() { + super(Jid.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/Resource.java b/src/main/java/im/conversations/android/xmpp/model/bind/Resource.java new file mode 100644 index 000000000..b3fd1e5c1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind/Resource.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.bind; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Resource extends Extension { + public Resource() { + super(Resource.class); + } + + public Resource(final String resource) { + this(); + this.setContent(resource); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/package-info.java b/src/main/java/im/conversations/android/xmpp/model/bind/package-info.java new file mode 100644 index 000000000..aebaeeb72 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.BIND) +package im.conversations.android.xmpp.model.bind; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java new file mode 100644 index 000000000..bff72a06b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java @@ -0,0 +1,24 @@ +package im.conversations.android.xmpp.model.bind2; + +import java.util.Collection; +import java.util.Collections; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Bind extends Extension { + + public Bind() { + super(Bind.class); + } + + public Inline getInline() { + return this.getExtension(Inline.class); + } + + public Collection getInlineFeatures() { + final var inline = getInline(); + return inline == null ? Collections.emptyList() : inline.getExtensions(Feature.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Bound.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Bound.java new file mode 100644 index 000000000..0144edb91 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Bound.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.bind2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Bound extends Extension { + public Bound() { + super(Bound.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Feature.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Feature.java new file mode 100644 index 000000000..66720abbc --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Feature.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.bind2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Feature extends Extension { + + public Feature() { + super(Feature.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Inline.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Inline.java new file mode 100644 index 000000000..641a9d4f4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Inline.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.bind2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Inline extends Extension { + + public Inline() { + super(Inline.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/package-info.java b/src/main/java/im/conversations/android/xmpp/model/bind2/package-info.java new file mode 100644 index 000000000..2d8c5e92c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.BIND2) +package im.conversations.android.xmpp.model.bind2; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Block.java b/src/main/java/im/conversations/android/xmpp/model/blocking/Block.java new file mode 100644 index 000000000..6f5d00b3e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/Block.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.blocking; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Block extends Extension { + + public Block() { + super(Block.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java b/src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java new file mode 100644 index 000000000..a56662d77 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.blocking; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Blocklist extends Extension { + public Blocklist() { + super(Blocklist.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java b/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java new file mode 100644 index 000000000..647b0ae99 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.blocking; + +import eu.siacs.conversations.xmpp.Jid; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Item extends Extension { + + public Item() { + super(Item.class); + } + + public Jid getJid() { + return getAttributeAsJid("jid"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java b/src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java new file mode 100644 index 000000000..90cec110c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.blocking; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Unblock extends Extension { + + public Unblock() { + super(Unblock.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java b/src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java new file mode 100644 index 000000000..22d8f0e1f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.BLOCKING) +package im.conversations.android.xmpp.model.blocking; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java b/src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java new file mode 100644 index 000000000..0f924e888 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java @@ -0,0 +1,32 @@ +package im.conversations.android.xmpp.model.bookmark; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Conference extends Extension { + + public Conference() { + super(Conference.class); + } + + public boolean isAutoJoin() { + return this.getAttributeAsBoolean("autojoin"); + } + + public String getConferenceName() { + return this.getAttribute("name"); + } + + public void setAutoJoin(boolean autoJoin) { + setAttribute("autojoin", autoJoin); + } + + public Nick getNick() { + return this.getExtension(Nick.class); + } + + public Extensions getExtensions() { + return this.getExtension(Extensions.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/Extensions.java b/src/main/java/im/conversations/android/xmpp/model/bookmark/Extensions.java new file mode 100644 index 000000000..b9385cf54 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bookmark/Extensions.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.bookmark; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Extensions extends Extension { + + public Extensions() { + super(Extensions.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/Nick.java b/src/main/java/im/conversations/android/xmpp/model/bookmark/Nick.java new file mode 100644 index 000000000..ee5efa386 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bookmark/Nick.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.bookmark; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Nick extends Extension { + + public Nick() { + super(Nick.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java b/src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java new file mode 100644 index 000000000..1bb963be8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.BOOKMARKS2) +package im.conversations.android.xmpp.model.bookmark; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java b/src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java new file mode 100644 index 000000000..d0f23b283 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java @@ -0,0 +1,43 @@ +package im.conversations.android.xmpp.model.capabilties; + +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; +import com.google.common.io.BaseEncoding; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.EntityCapabilities2; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.Hash; + +@XmlElement(name = "c", namespace = Namespace.ENTITY_CAPABILITIES_2) +public class Capabilities extends Extension { + + public Capabilities() { + super(Capabilities.class); + } + + public EntityCapabilities2.EntityCaps2Hash getHash() { + final Optional sha256Hash = + Iterables.tryFind( + getExtensions(Hash.class), h -> h.getAlgorithm() == Hash.Algorithm.SHA_256); + if (sha256Hash.isPresent()) { + final String content = sha256Hash.get().getContent(); + if (Strings.isNullOrEmpty(content)) { + return null; + } + if (BaseEncoding.base64().canDecode(content)) { + return EntityCapabilities2.EntityCaps2Hash.of(Hash.Algorithm.SHA_256, content); + } + } + return null; + } + + public void setHash(final EntityCapabilities2.EntityCaps2Hash caps2Hash) { + final Hash hash = new Hash(); + hash.setAlgorithm(caps2Hash.algorithm); + hash.setContent(caps2Hash.encoded()); + this.addExtension(hash); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java b/src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java new file mode 100644 index 000000000..f8ed4ef66 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java @@ -0,0 +1,39 @@ +package im.conversations.android.xmpp.model.capabilties; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import im.conversations.android.xmpp.model.Extension; + +public interface EntityCapabilities { + + E getExtension(final Class clazz); + + default NodeHash getCapabilities() { + final String node; + final im.conversations.android.xmpp.EntityCapabilities.Hash hash; + final var capabilities = this.getExtension(Capabilities.class); + final var legacyCapabilities = this.getExtension(LegacyCapabilities.class); + if (capabilities != null) { + node = null; + hash = capabilities.getHash(); + } else if (legacyCapabilities != null) { + node = legacyCapabilities.getNode(); + hash = legacyCapabilities.getHash(); + } else { + return null; + } + return hash == null ? null : new NodeHash(node, hash); + } + + class NodeHash { + public final String node; + public final im.conversations.android.xmpp.EntityCapabilities.Hash hash; + + private NodeHash( + @Nullable String node, + @NonNull final im.conversations.android.xmpp.EntityCapabilities.Hash hash) { + this.node = node; + this.hash = hash; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java b/src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java new file mode 100644 index 000000000..797d627cf --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java @@ -0,0 +1,45 @@ +package im.conversations.android.xmpp.model.capabilties; + +import com.google.common.base.Strings; +import com.google.common.io.BaseEncoding; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.EntityCapabilities; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "c", namespace = Namespace.ENTITY_CAPABILITIES) +public class LegacyCapabilities extends Extension { + + private static final String HASH_ALGORITHM = "sha-1"; + + public LegacyCapabilities() { + super(LegacyCapabilities.class); + } + + public String getNode() { + return this.getAttribute("node"); + } + + public EntityCapabilities.EntityCapsHash getHash() { + final String hash = getAttribute("hash"); + final String ver = getAttribute("ver"); + if (Strings.isNullOrEmpty(ver) || Strings.isNullOrEmpty(hash)) { + return null; + } + if (HASH_ALGORITHM.equals(hash) && BaseEncoding.base64().canDecode(ver)) { + return EntityCapabilities.EntityCapsHash.of(ver); + } else { + return null; + } + } + + public void setNode(final String node) { + this.setAttribute("node", node); + } + + public void setHash(final EntityCapabilities.EntityCapsHash hash) { + this.setAttribute("hash", HASH_ALGORITHM); + this.setAttribute("ver", hash.encoded()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java b/src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java new file mode 100644 index 000000000..38b740e8c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.carbons; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Enable extends Extension { + + public Enable() { + super(Enable.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java b/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java new file mode 100644 index 000000000..507869a60 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.carbons; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.forward.Forwarded; + +@XmlElement +public class Received extends Extension { + + public Received() { + super(Received.class); + } + + public Forwarded getForwarded() { + return this.getExtension(Forwarded.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java b/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java new file mode 100644 index 000000000..0201c53c6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.carbons; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.forward.Forwarded; + +@XmlElement +public class Sent extends Extension { + + public Sent() { + super(Sent.class); + } + + public Forwarded getForwarded() { + return this.getExtension(Forwarded.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java b/src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java new file mode 100644 index 000000000..f4c76376a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.CARBONS) +package im.conversations.android.xmpp.model.carbons; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/correction/Replace.java b/src/main/java/im/conversations/android/xmpp/model/correction/Replace.java new file mode 100644 index 000000000..dbd739557 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/correction/Replace.java @@ -0,0 +1,24 @@ +package im.conversations.android.xmpp.model.correction; + +import androidx.annotation.NonNull; +import com.google.common.base.Strings; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.LAST_MESSAGE_CORRECTION) +public class Replace extends Extension { + + public Replace() { + super(Replace.class); + } + + public String getId() { + return Strings.emptyToNull(this.getAttribute("id")); + } + + public void setId(@NonNull final String id) { + this.setAttribute("id", id); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/Active.java b/src/main/java/im/conversations/android/xmpp/model/csi/Active.java new file mode 100644 index 000000000..21fb65bb4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/csi/Active.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.csi; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Active extends StreamElement { + + public Active() { + super(Active.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/ClientStateIndication.java b/src/main/java/im/conversations/android/xmpp/model/csi/ClientStateIndication.java new file mode 100644 index 000000000..60bd59edb --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/csi/ClientStateIndication.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.csi; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamFeature; + +@XmlElement(name = "csi") +public class ClientStateIndication extends StreamFeature { + + public ClientStateIndication() { + super(ClientStateIndication.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java b/src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java new file mode 100644 index 000000000..7c36b593d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.csi; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Inactive extends StreamElement { + + public Inactive() { + super(Inactive.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/package-info.java b/src/main/java/im/conversations/android/xmpp/model/csi/package-info.java new file mode 100644 index 000000000..58d26b1f1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/csi/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.CSI) +package im.conversations.android.xmpp.model.csi; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Data.java b/src/main/java/im/conversations/android/xmpp/model/data/Data.java new file mode 100644 index 000000000..c754ee48d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/Data.java @@ -0,0 +1,110 @@ +package im.conversations.android.xmpp.model.data; + +import com.google.common.collect.Collections2; +import com.google.common.collect.Iterables; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import java.util.Map; + +@XmlElement(name = "x") +public class Data extends Extension { + + private static final String FORM_TYPE = "FORM_TYPE"; + private static final String FIELD_TYPE_HIDDEN = "hidden"; + private static final String FORM_TYPE_SUBMIT = "submit"; + + public Data() { + super(Data.class); + } + + public String getFormType() { + final var fields = this.getExtensions(Field.class); + final var formTypeField = Iterables.find(fields, f -> FORM_TYPE.equals(f.getFieldName())); + return Iterables.getFirst(formTypeField.getValues(), null); + } + + public Collection getFields() { + return Collections2.filter( + this.getExtensions(Field.class), f -> !FORM_TYPE.equals(f.getFieldName())); + } + + private void addField(final String name, final Object value) { + addField(name, value, null); + } + + private void addField(final String name, final Object value, final String type) { + if (value == null) { + throw new IllegalArgumentException("Null values are not supported on data fields"); + } + final var field = this.addExtension(new Field()); + field.setFieldName(name); + if (type != null) { + field.setType(type); + } + if (value instanceof Collection) { + for (final Object subValue : (Collection) value) { + if (subValue instanceof String) { + final var valueExtension = field.addExtension(new Value()); + valueExtension.setContent((String) subValue); + } else { + throw new IllegalArgumentException( + String.format( + "%s is not a supported field value", + subValue.getClass().getSimpleName())); + } + } + } else { + final var valueExtension = field.addExtension(new Value()); + if (value instanceof String) { + valueExtension.setContent((String) value); + } else if (value instanceof Integer) { + valueExtension.setContent(String.valueOf(value)); + } else if (value instanceof Boolean) { + valueExtension.setContent(Boolean.TRUE.equals(value) ? "1" : "0"); + } else { + throw new IllegalArgumentException( + String.format( + "%s is not a supported field value", + value.getClass().getSimpleName())); + } + } + } + + private void setFormType(final String formType) { + this.addField(FORM_TYPE, formType, FIELD_TYPE_HIDDEN); + } + + public static Data of(final String formType, final Map values) { + final var data = new Data(); + data.setType(FORM_TYPE_SUBMIT); + data.setFormType(formType); + for (final Map.Entry entry : values.entrySet()) { + data.addField(entry.getKey(), entry.getValue()); + } + return data; + } + + public Data submit(final Map values) { + final String formType = this.getFormType(); + final var submit = new Data(); + submit.setType(FORM_TYPE_SUBMIT); + if (formType != null) { + submit.setFormType(formType); + } + for (final Field existingField : this.getFields()) { + final var fieldName = existingField.getFieldName(); + final Object submittedValue = values.get(fieldName); + if (submittedValue != null) { + submit.addField(fieldName, submittedValue); + } else { + submit.addField(fieldName, existingField.getValues()); + } + } + return submit; + } + + private void setType(final String type) { + this.setAttribute("type", type); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Field.java b/src/main/java/im/conversations/android/xmpp/model/data/Field.java new file mode 100644 index 000000000..f3f72fab8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/Field.java @@ -0,0 +1,29 @@ +package im.conversations.android.xmpp.model.data; +import eu.siacs.conversations.xml.Element; +import com.google.common.collect.Collections2; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; + +@XmlElement +public class Field extends Extension { + public Field() { + super(Field.class); + } + + public String getFieldName() { + return getAttribute("var"); + } + + public Collection getValues() { + return Collections2.transform(getExtensions(Value.class), Element::getContent); + } + + public void setFieldName(String name) { + this.setAttribute("var", name); + } + + public void setType(String type) { + this.setAttribute("type", type); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Option.java b/src/main/java/im/conversations/android/xmpp/model/data/Option.java new file mode 100644 index 000000000..b9c3e9aae --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/Option.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.data; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Option extends Extension { + + public Option() { + super(Option.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Value.java b/src/main/java/im/conversations/android/xmpp/model/data/Value.java new file mode 100644 index 000000000..8e9eccc4d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/Value.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.data; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Value extends Extension { + + public Value() { + super(Value.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/data/package-info.java b/src/main/java/im/conversations/android/xmpp/model/data/package-info.java new file mode 100644 index 000000000..fcc0e1f79 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.DATA) +package im.conversations.android.xmpp.model.data; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/delay/Delay.java b/src/main/java/im/conversations/android/xmpp/model/delay/Delay.java new file mode 100644 index 000000000..b294f83d4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/delay/Delay.java @@ -0,0 +1,30 @@ +package im.conversations.android.xmpp.model.delay; + +import com.google.common.base.Strings; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.Timestamps; +import im.conversations.android.xmpp.model.Extension; +import java.text.ParseException; +import java.time.Instant; + +@XmlElement(namespace = Namespace.DELAY) +public class Delay extends Extension { + + public Delay() { + super(Delay.class); + } + + public Instant getStamp() { + final var stamp = this.getAttribute("stamp"); + if (Strings.isNullOrEmpty(stamp)) { + return null; + } + try { + return Instant.ofEpochMilli(Timestamps.parse(stamp)); + } catch (final IllegalArgumentException | ParseException e) { + return null; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/external/Service.java b/src/main/java/im/conversations/android/xmpp/model/disco/external/Service.java new file mode 100644 index 000000000..86a93af0d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/external/Service.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.disco.external; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Service extends Extension { + + public Service() { + super(Service.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/external/Services.java b/src/main/java/im/conversations/android/xmpp/model/disco/external/Services.java new file mode 100644 index 000000000..36338083d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/external/Services.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.disco.external; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Services extends Extension { + + public Services() { + super(Services.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/external/package-info.java b/src/main/java/im/conversations/android/xmpp/model/disco/external/package-info.java new file mode 100644 index 000000000..868a5a175 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/external/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.EXTERNAL_SERVICE_DISCOVERY) +package im.conversations.android.xmpp.model.disco.external; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java b/src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java new file mode 100644 index 000000000..dd288918c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java @@ -0,0 +1,19 @@ +package im.conversations.android.xmpp.model.disco.info; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Feature extends Extension { + public Feature() { + super(Feature.class); + } + + public String getVar() { + return this.getAttribute("var"); + } + + public void setVar(final String feature) { + this.setAttribute("var", feature); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java b/src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java new file mode 100644 index 000000000..6da0a4aa2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java @@ -0,0 +1,39 @@ +package im.conversations.android.xmpp.model.disco.info; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Identity extends Extension { + public Identity() { + super(Identity.class); + } + + public String getCategory() { + return this.getAttribute("category"); + } + + public String getType() { + return this.getAttribute("type"); + } + + public String getLang() { + return this.getAttribute("xml:lang"); + } + + public String getIdentityName() { + return this.getAttribute("name"); + } + + public void setIdentityName(final String name) { + this.setAttribute("name", name); + } + + public void setType(final String type) { + this.setAttribute("type", type); + } + + public void setCategory(final String category) { + this.setAttribute("category", category); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java b/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java new file mode 100644 index 000000000..55f104e25 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java @@ -0,0 +1,38 @@ +package im.conversations.android.xmpp.model.disco.info; + +import com.google.common.collect.Iterables; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; + +@XmlElement(name = "query") +public class InfoQuery extends Extension { + + public InfoQuery() { + super(InfoQuery.class); + } + + public void setNode(final String node) { + this.setAttribute("node", node); + } + + public String getNode() { + return this.getAttribute("node"); + } + + public Collection getFeatures() { + return this.getExtensions(Feature.class); + } + + public boolean hasFeature(final String feature) { + return Iterables.any(getFeatures(), f -> feature.equals(f.getVar())); + } + + public Collection getIdentities() { + return this.getExtensions(Identity.class); + } + + public boolean hasIdentityWithCategory(final String category) { + return Iterables.any(getIdentities(), i -> category.equals(i.getCategory())); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java b/src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java new file mode 100644 index 000000000..60eb24a59 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.DISCO_INFO) +package im.conversations.android.xmpp.model.disco.info; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java b/src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java new file mode 100644 index 000000000..f5bf2b984 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java @@ -0,0 +1,22 @@ +package im.conversations.android.xmpp.model.disco.items; + +import androidx.annotation.Nullable; + +import eu.siacs.conversations.xmpp.Jid; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Item extends Extension { + public Item() { + super(Item.class); + } + + public Jid getJid() { + return getAttributeAsJid("jid"); + } + + public @Nullable String getNode() { + return this.getAttribute("node"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java b/src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java new file mode 100644 index 000000000..981132ed6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java @@ -0,0 +1,19 @@ +package im.conversations.android.xmpp.model.disco.items; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "query") +public class ItemsQuery extends Extension { + public ItemsQuery() { + super(ItemsQuery.class); + } + + public void setNode(final String node) { + this.setAttribute("node", node); + } + + public String getNode() { + return this.getAttribute("node"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java b/src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java new file mode 100644 index 000000000..a170e5cee --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.DISCO_ITEMS) +package im.conversations.android.xmpp.model.disco.items; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/error/Condition.java b/src/main/java/im/conversations/android/xmpp/model/error/Condition.java new file mode 100644 index 000000000..bd68c2c43 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/error/Condition.java @@ -0,0 +1,188 @@ +package im.conversations.android.xmpp.model.error; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +public abstract class Condition extends Extension { + + private Condition(Class clazz) { + super(clazz); + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class BadRequest extends Condition { + + public BadRequest() { + super(BadRequest.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class Conflict extends Condition { + + public Conflict() { + super(Conflict.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class FeatureNotImplemented extends Condition { + + public FeatureNotImplemented() { + super(FeatureNotImplemented.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class Forbidden extends Condition { + + public Forbidden() { + super(Forbidden.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class Gone extends Condition { + + public Gone() { + super(Gone.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class InternalServerError extends Condition { + + public InternalServerError() { + super(InternalServerError.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class ItemNotFound extends Condition { + + public ItemNotFound() { + super(ItemNotFound.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class JidMalformed extends Condition { + + public JidMalformed() { + super(JidMalformed.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class NotAcceptable extends Condition { + + public NotAcceptable() { + super(NotAcceptable.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class NotAllowed extends Condition { + + public NotAllowed() { + super(NotAllowed.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class NotAuthorized extends Condition { + + public NotAuthorized() { + super(NotAuthorized.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class PaymentRequired extends Condition { + + public PaymentRequired() { + super(PaymentRequired.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class RecipientUnavailable extends Condition { + + public RecipientUnavailable() { + super(RecipientUnavailable.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class Redirect extends Condition { + + public Redirect() { + super(Redirect.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class RegistrationRequired extends Condition { + + public RegistrationRequired() { + super(RegistrationRequired.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class RemoteServerNotFound extends Condition { + + public RemoteServerNotFound() { + super(RemoteServerNotFound.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class RemoteServerTimeout extends Condition { + + public RemoteServerTimeout() { + super(RemoteServerTimeout.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class ResourceConstraint extends Condition { + + public ResourceConstraint() { + super(ResourceConstraint.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class ServiceUnavailable extends Condition { + + public ServiceUnavailable() { + super(ServiceUnavailable.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class SubscriptionRequired extends Condition { + + public SubscriptionRequired() { + super(SubscriptionRequired.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class UndefinedCondition extends Condition { + + public UndefinedCondition() { + super(UndefinedCondition.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class UnexpectedRequest extends Condition { + + public UnexpectedRequest() { + super(UnexpectedRequest.class); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/error/Error.java b/src/main/java/im/conversations/android/xmpp/model/error/Error.java new file mode 100644 index 000000000..0a07e73f9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/error/Error.java @@ -0,0 +1,55 @@ +package im.conversations.android.xmpp.model.error; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Locale; +import eu.siacs.conversations.xml.Namespace; + +@XmlElement(namespace = Namespace.JABBER_CLIENT) +public class Error extends Extension { + + public Error() { + super(Error.class); + } + + public Condition getCondition() { + return this.getExtension(Condition.class); + } + + public void setCondition(final Condition condition) { + this.addExtension(condition); + } + + public Text getText() { + return this.getExtension(Text.class); + } + + public String getTextAsString() { + final var text = getText(); + return text == null ? null : text.getContent(); + } + + public void setType(final Type type) { + this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT)); + } + + public void addExtensions(final Extension[] extensions) { + for (final Extension extension : extensions) { + this.addExtension(extension); + } + } + + public enum Type { + MODIFY, + CANCEL, + AUTH, + WAIT + } + + public static class Extension extends im.conversations.android.xmpp.model.Extension { + + public Extension(Class clazz) { + super(clazz); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/error/Text.java b/src/main/java/im/conversations/android/xmpp/model/error/Text.java new file mode 100644 index 000000000..478b1f5cd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/error/Text.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.error; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.STANZAS) +public class Text extends Extension { + + public Text() { + super(Text.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/Fast.java b/src/main/java/im/conversations/android/xmpp/model/fast/Fast.java new file mode 100644 index 000000000..1291d8ea0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/Fast.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.fast; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Fast extends Extension { + public Fast() { + super(Fast.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/Mechanism.java b/src/main/java/im/conversations/android/xmpp/model/fast/Mechanism.java new file mode 100644 index 000000000..240f5de0e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/Mechanism.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.fast; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Mechanism extends Extension { + public Mechanism() { + super(Mechanism.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/RequestToken.java b/src/main/java/im/conversations/android/xmpp/model/fast/RequestToken.java new file mode 100644 index 000000000..4ac5a9205 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/RequestToken.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.fast; + +import eu.siacs.conversations.crypto.sasl.HashedToken; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class RequestToken extends Extension { + public RequestToken() { + super(RequestToken.class); + } + + public RequestToken(final HashedToken.Mechanism mechanism) { + this(); + this.setAttribute("mechanism", mechanism.name()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/Token.java b/src/main/java/im/conversations/android/xmpp/model/fast/Token.java new file mode 100644 index 000000000..258cd9aba --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/Token.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.fast; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Token extends Extension { + + public Token() { + super(Token.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/package-info.java b/src/main/java/im/conversations/android/xmpp/model/fast/package-info.java new file mode 100644 index 000000000..effc9e511 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.FAST) +package im.conversations.android.xmpp.model.fast; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java b/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java new file mode 100644 index 000000000..80a646a41 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java @@ -0,0 +1,18 @@ +package im.conversations.android.xmpp.model.forward; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.stanza.Message; + +@XmlElement(namespace = Namespace.FORWARD) +public class Forwarded extends Extension { + + public Forwarded() { + super(Forwarded.class); + } + + public Message getMessage() { + return this.getExtension(Message.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/hints/Store.java b/src/main/java/im/conversations/android/xmpp/model/hints/Store.java new file mode 100644 index 000000000..fe82612ad --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/hints/Store.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.hints; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Store extends Extension { + + public Store() { + super(Store.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/hints/package-info.java b/src/main/java/im/conversations/android/xmpp/model/hints/package-info.java new file mode 100644 index 000000000..76c25d655 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/hints/package-info.java @@ -0,0 +1,6 @@ +@XmlPackage(namespace = Namespace.HINTS) +package im.conversations.android.xmpp.model.hints; + +import eu.siacs.conversations.xml.Namespace; + +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Body.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Body.java new file mode 100644 index 000000000..5857f0585 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Body.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Body extends Extension { + + public Body() { + super(Body.class); + } + + public Body(final String content) { + this(); + setContent(content); + } + + public String getLang() { + return this.getAttribute("xml:lang"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Priority.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Priority.java new file mode 100644 index 000000000..7c5b3bdc9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Priority.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Priority extends Extension { + + public Priority() { + super(Priority.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Show.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Show.java new file mode 100644 index 000000000..44dc512be --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Show.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Show extends Extension { + public Show() { + super(Show.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Status.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Status.java new file mode 100644 index 000000000..3175230d7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Status.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Status extends Extension { + + + public Status() { + super(Status.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Subject.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Subject.java new file mode 100644 index 000000000..4ae3b8ed5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Subject.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Subject extends Extension { + + public Subject() { + super(Subject.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java new file mode 100644 index 000000000..703429ef0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Thread extends Extension { + + public Thread() { + super(Thread.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java b/src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java new file mode 100644 index 000000000..448804d7c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.JABBER_CLIENT) +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java b/src/main/java/im/conversations/android/xmpp/model/jingle/Jingle.java similarity index 62% rename from src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java rename to src/main/java/im/conversations/android/xmpp/model/jingle/Jingle.java index a24040d3d..aeb79ffd2 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java +++ b/src/main/java/im/conversations/android/xmpp/model/jingle/Jingle.java @@ -1,4 +1,4 @@ -package eu.siacs.conversations.xmpp.jingle.stanzas; +package im.conversations.android.xmpp.model.jingle; import androidx.annotation.NonNull; @@ -10,66 +10,38 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import eu.siacs.conversations.xmpp.jingle.stanzas.Content; +import eu.siacs.conversations.xmpp.jingle.stanzas.Group; +import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; -import java.util.Map; - -public class JinglePacket extends IqPacket { - - private JinglePacket() { - super(); - } +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; - public JinglePacket(final Action action, final String sessionId) { - super(TYPE.SET); - final Element jingle = addChild("jingle", Namespace.JINGLE); - jingle.setAttribute("sid", sessionId); - jingle.setAttribute("action", action.toString()); - } +import java.util.Map; - public static JinglePacket upgrade(final IqPacket iqPacket) { - Preconditions.checkArgument(iqPacket.hasChild("jingle", Namespace.JINGLE)); - Preconditions.checkArgument(iqPacket.getType() == TYPE.SET); - final JinglePacket jinglePacket = new JinglePacket(); - jinglePacket.setAttributes(iqPacket.getAttributes()); - jinglePacket.setChildren(iqPacket.getChildren()); - return jinglePacket; - } +@XmlElement +public class Jingle extends Extension { - // TODO deprecate this somehow and make file transfer fail if there are multiple (or something) - public Content getJingleContent() { - final Element content = getJingleChild("content"); - return content == null ? null : Content.upgrade(content); + public Jingle() { + super(Jingle.class); } - public Group getGroup() { - final Element jingle = findChild("jingle", Namespace.JINGLE); - final Element group = jingle.findChild("group", Namespace.JINGLE_APPS_GROUPING); - return group == null ? null : Group.upgrade(group); + public Jingle(final Action action, final String sessionId) { + this(); + this.setAttribute("sid", sessionId); + this.setAttribute("action", action.toString()); } - public void addGroup(final Group group) { - this.addJingleChild(group); - } - - public Map getJingleContents() { - final Element jingle = findChild("jingle", Namespace.JINGLE); - ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); - for (final Element child : jingle.getChildren()) { - if ("content".equals(child.getName())) { - final Content content = Content.upgrade(child); - builder.put(content.getContentName(), content); - } - } - return builder.build(); + public String getSessionId() { + return this.getAttribute("sid"); } - public void addJingleContent(final Content content) { // take content interface - addJingleChild(content); + public Action getAction() { + return Action.of(this.getAttribute("action")); } public ReasonWrapper getReason() { - final Element reasonElement = getJingleChild("reason"); + final Element reasonElement = this.findChild("reason"); if (reasonElement == null) { return new ReasonWrapper(Reason.UNKNOWN, null); } @@ -86,8 +58,7 @@ public ReasonWrapper getReason() { } public void setReason(final Reason reason, final String text) { - final Element jingle = findChild("jingle", Namespace.JINGLE); - final Element reasonElement = jingle.addChild("reason"); + final Element reasonElement = this.addChild("reason"); reasonElement.addChild(reason.toString()); if (!Strings.isNullOrEmpty(text)) { reasonElement.addChild("text").setContent(text); @@ -97,31 +68,44 @@ public void setReason(final Reason reason, final String text) { // RECOMMENDED for session-initiate, NOT RECOMMENDED otherwise public void setInitiator(final Jid initiator) { Preconditions.checkArgument(initiator.isFullJid(), "initiator should be a full JID"); - findChild("jingle", Namespace.JINGLE).setAttribute("initiator", initiator); + this.setAttribute("initiator", initiator); } // RECOMMENDED for session-accept, NOT RECOMMENDED otherwise - public void setResponder(Jid responder) { + public void setResponder(final Jid responder) { Preconditions.checkArgument(responder.isFullJid(), "responder should be a full JID"); - findChild("jingle", Namespace.JINGLE).setAttribute("responder", responder); + this.setAttribute("responder", responder); } - public Element getJingleChild(final String name) { - final Element jingle = findChild("jingle", Namespace.JINGLE); - return jingle == null ? null : jingle.findChild(name); + public Group getGroup() { + final Element group = this.findChild("group", Namespace.JINGLE_APPS_GROUPING); + return group == null ? null : Group.upgrade(group); } - public void addJingleChild(final Element child) { - final Element jingle = findChild("jingle", Namespace.JINGLE); - jingle.addChild(child); + public void addGroup(final Group group) { + this.addChild(group); } - public String getSessionId() { - return findChild("jingle", Namespace.JINGLE).getAttribute("sid"); + // TODO deprecate this somehow and make file transfer fail if there are multiple (or something) + public Content getJingleContent() { + final Element content = this.findChild("content"); + return content == null ? null : Content.upgrade(content); } - public Action getAction() { - return Action.of(findChild("jingle", Namespace.JINGLE).getAttribute("action")); + public void addJingleContent(final Content content) { // take content interface + this.addChild(content); + } + + + public Map getJingleContents() { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + for (final Element child : this.getChildren()) { + if ("content".equals(child.getName())) { + final Content content = Content.upgrade(child); + builder.put(content.getContentName(), content); + } + } + return builder.build(); } public enum Action { diff --git a/src/main/java/im/conversations/android/xmpp/model/jingle/error/JingleCondition.java b/src/main/java/im/conversations/android/xmpp/model/jingle/error/JingleCondition.java new file mode 100644 index 000000000..a0c6dfbbd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jingle/error/JingleCondition.java @@ -0,0 +1,44 @@ +package im.conversations.android.xmpp.model.jingle.error; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.error.Error; + +public abstract class JingleCondition extends Error.Extension { + + private JingleCondition(Class clazz) { + super(clazz); + } + + @XmlElement(namespace = Namespace.JINGLE_ERRORS) + public static class OutOfOrder extends JingleCondition { + + public OutOfOrder() { + super(OutOfOrder.class); + } + } + + @XmlElement(namespace = Namespace.JINGLE_ERRORS) + public static class TieBreak extends JingleCondition { + + public TieBreak() { + super(TieBreak.class); + } + } + + @XmlElement(namespace = Namespace.JINGLE_ERRORS) + public static class UnknownSession extends JingleCondition { + + public UnknownSession() { + super(UnknownSession.class); + } + } + + @XmlElement(namespace = Namespace.JINGLE_ERRORS) + public static class UnsupportedInfo extends JingleCondition { + + public UnsupportedInfo() { + super(UnsupportedInfo.class); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jingle/package-info.java b/src/main/java/im/conversations/android/xmpp/model/jingle/package-info.java new file mode 100644 index 000000000..6af2511e8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jingle/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.JINGLE) +package im.conversations.android.xmpp.model.jingle; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Accept.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Accept.java new file mode 100644 index 000000000..20ae15aee --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Accept.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.jmi; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Accept extends JingleMessage { + + public Accept() { + super(Accept.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/JingleMessage.java b/src/main/java/im/conversations/android/xmpp/model/jmi/JingleMessage.java new file mode 100644 index 000000000..0045844f0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/JingleMessage.java @@ -0,0 +1,14 @@ +package im.conversations.android.xmpp.model.jmi; + +import im.conversations.android.xmpp.model.Extension; + +public abstract class JingleMessage extends Extension { + + public JingleMessage(Class clazz) { + super(clazz); + } + + public String getSessionId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Proceed.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Proceed.java new file mode 100644 index 000000000..b6be44ee0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Proceed.java @@ -0,0 +1,24 @@ +package im.conversations.android.xmpp.model.jmi; + +import com.google.common.primitives.Ints; + +import eu.siacs.conversations.xml.Element; +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Proceed extends JingleMessage { + + public Proceed() { + super(Proceed.class); + } + + public Integer getDeviceId() { + // TODO use proper namespace and create extension + final Element device = this.findChild("device"); + final String id = device == null ? null : device.getAttribute("id"); + if (id == null) { + return null; + } + return Ints.tryParse(id); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Propose.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Propose.java new file mode 100644 index 000000000..d5a48a403 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Propose.java @@ -0,0 +1,38 @@ +package im.conversations.android.xmpp.model.jmi; + +import com.google.common.collect.ImmutableList; + +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription; +import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription; +import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; +import im.conversations.android.annotation.XmlElement; + +import java.util.List; + +@XmlElement +public class Propose extends JingleMessage { + + public Propose() { + super(Propose.class); + } + + public List getDescriptions() { + final ImmutableList.Builder builder = new ImmutableList.Builder<>(); + // TODO create proper extension for description + for (final Element child : this.children) { + if ("description".equals(child.getName())) { + final String namespace = child.getNamespace(); + if (Namespace.JINGLE_APPS_FILE_TRANSFER.contains(namespace)) { + builder.add(FileTransferDescription.upgrade(child)); + } else if (Namespace.JINGLE_APPS_RTP.equals(namespace)) { + builder.add(RtpDescription.upgrade(child)); + } else { + builder.add(GenericDescription.upgrade(child)); + } + } + } + return builder.build(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Reject.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Reject.java new file mode 100644 index 000000000..e71206fd6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Reject.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.jmi; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Reject extends JingleMessage { + + public Reject() { + super(Reject.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Retract.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Retract.java new file mode 100644 index 000000000..7c507156d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Retract.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.jmi; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Retract extends JingleMessage { + + public Retract() { + super(Retract.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/package-info.java b/src/main/java/im/conversations/android/xmpp/model/jmi/package-info.java new file mode 100644 index 000000000..9ce640b1f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.JINGLE_MESSAGE) +package im.conversations.android.xmpp.model.jmi; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/End.java b/src/main/java/im/conversations/android/xmpp/model/mam/End.java new file mode 100644 index 000000000..757ed60c6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/End.java @@ -0,0 +1,15 @@ +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class End extends Extension { + public End() { + super(End.class); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Fin.java b/src/main/java/im/conversations/android/xmpp/model/mam/Fin.java new file mode 100644 index 000000000..534072647 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Fin.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Fin extends Extension { + + public Fin() { + super(Fin.class); + } + + public boolean isComplete() { + return this.getAttributeAsBoolean("complete"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Metadata.java b/src/main/java/im/conversations/android/xmpp/model/mam/Metadata.java new file mode 100644 index 000000000..9f05e08fc --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Metadata.java @@ -0,0 +1,20 @@ +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Metadata extends Extension { + + public Metadata() { + super(Metadata.class); + } + + public Start getStart() { + return this.getExtension(Start.class); + } + + public End getEnd() { + return this.getExtension(End.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Query.java b/src/main/java/im/conversations/android/xmpp/model/mam/Query.java new file mode 100644 index 000000000..d8f701d91 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Query.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Query extends Extension { + + public Query() { + super(Query.class); + } + + public void setQueryId(final String id) { + this.setAttribute("queryid", id); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Result.java b/src/main/java/im/conversations/android/xmpp/model/mam/Result.java new file mode 100644 index 000000000..253499756 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Result.java @@ -0,0 +1,25 @@ +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.forward.Forwarded; + +@XmlElement +public class Result extends Extension { + + public Result() { + super(Result.class); + } + + public Forwarded getForwarded() { + return this.getExtension(Forwarded.class); + } + + public String getId() { + return this.getAttribute("id"); + } + + public String getQueryId() { + return this.getAttribute("queryid"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Start.java b/src/main/java/im/conversations/android/xmpp/model/mam/Start.java new file mode 100644 index 000000000..9ff84b256 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Start.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Start extends Extension { + + public Start() { + super(Start.class); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/package-info.java b/src/main/java/im/conversations/android/xmpp/model/mam/package-info.java new file mode 100644 index 000000000..1aa4982e6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.MESSAGE_ARCHIVE_MANAGEMENT) +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java b/src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java new file mode 100644 index 000000000..be31df35d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.markers; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Displayed extends Extension { + + public Displayed() { + super(Displayed.class); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/Markable.java b/src/main/java/im/conversations/android/xmpp/model/markers/Markable.java new file mode 100644 index 000000000..08161af70 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/markers/Markable.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.markers; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.DeliveryReceiptRequest; + +@XmlElement +public class Markable extends DeliveryReceiptRequest { + + public Markable() { + super(Markable.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/Received.java b/src/main/java/im/conversations/android/xmpp/model/markers/Received.java new file mode 100644 index 000000000..7007cd176 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/markers/Received.java @@ -0,0 +1,20 @@ +package im.conversations.android.xmpp.model.markers; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.DeliveryReceipt; + +@XmlElement +public class Received extends DeliveryReceipt { + + public Received() { + super(Received.class); + } + + public void setId(String id) { + this.setAttribute("id", id); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/package-info.java b/src/main/java/im/conversations/android/xmpp/model/markers/package-info.java new file mode 100644 index 000000000..950963d4f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/markers/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.CHAT_MARKERS) +package im.conversations.android.xmpp.model.markers; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/mds/Displayed.java b/src/main/java/im/conversations/android/xmpp/model/mds/Displayed.java new file mode 100644 index 000000000..9f5275371 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mds/Displayed.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.mds; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.MDS_DISPLAYED) +public class Displayed extends Extension { + public Displayed() { + super(Displayed.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java b/src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java new file mode 100644 index 000000000..6502a16e7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java @@ -0,0 +1,9 @@ +package im.conversations.android.xmpp.model.muc; + +public enum Affiliation { + OWNER, + ADMIN, + MEMBER, + OUTCAST, + NONE; +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/History.java b/src/main/java/im/conversations/android/xmpp/model/muc/History.java new file mode 100644 index 000000000..e09210e60 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/History.java @@ -0,0 +1,20 @@ +package im.conversations.android.xmpp.model.muc; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class History extends Extension { + + public History() { + super(History.class); + } + + public void setMaxChars(final int maxChars) { + this.setAttribute("maxchars", maxChars); + } + + public void setMaxStanzas(final int maxStanzas) { + this.setAttribute("maxstanzas", maxStanzas); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/MultiUserChat.java b/src/main/java/im/conversations/android/xmpp/model/muc/MultiUserChat.java new file mode 100644 index 000000000..33da7b9af --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/MultiUserChat.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.muc; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "x") +public class MultiUserChat extends Extension { + + public MultiUserChat() { + super(MultiUserChat.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/Role.java b/src/main/java/im/conversations/android/xmpp/model/muc/Role.java new file mode 100644 index 000000000..9e9d3d165 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/Role.java @@ -0,0 +1,8 @@ +package im.conversations.android.xmpp.model.muc; + +public enum Role { + MODERATOR, + VISITOR, + PARTICIPANT, + NONE +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/package-info.java b/src/main/java/im/conversations/android/xmpp/model/muc/package-info.java new file mode 100644 index 000000000..41d652f20 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.MUC) +package im.conversations.android.xmpp.model.muc; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/Item.java b/src/main/java/im/conversations/android/xmpp/model/muc/user/Item.java new file mode 100644 index 000000000..7ff712aea --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/user/Item.java @@ -0,0 +1,58 @@ +package im.conversations.android.xmpp.model.muc.user; + +import android.util.Log; + +import com.google.common.base.Strings; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.xmpp.Jid; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.muc.Affiliation; +import im.conversations.android.xmpp.model.muc.Role; + +import java.util.Locale; + +@XmlElement +public class Item extends Extension { + + + public Item() { + super(Item.class); + } + + public Affiliation getAffiliation() { + final var affiliation = this.getAttribute("affiliation"); + if (Strings.isNullOrEmpty(affiliation)) { + return Affiliation.NONE; + } + try { + return Affiliation.valueOf(affiliation.toUpperCase(Locale.ROOT)); + } catch (final IllegalArgumentException e) { + Log.d(Config.LOGTAG,"could not parse affiliation "+affiliation); + return Affiliation.NONE; + } + } + + public Role getRole() { + final var role = this.getAttribute("role"); + if (Strings.isNullOrEmpty(role)) { + return Role.NONE; + } + try { + return Role.valueOf(role.toUpperCase(Locale.ROOT)); + } catch (final IllegalArgumentException e) { + Log.d(Config.LOGTAG,"could not parse role "+ role); + return Role.NONE; + } + } + + public String getNick() { + return this.getAttribute("nick"); + } + + public Jid getJid() { + return this.getAttributeAsJid("jid"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/MucUser.java b/src/main/java/im/conversations/android/xmpp/model/muc/user/MucUser.java new file mode 100644 index 000000000..5496c3ef2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/user/MucUser.java @@ -0,0 +1,27 @@ +package im.conversations.android.xmpp.model.muc.user; + +import com.google.common.collect.Collections2; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import java.util.Objects; + +@XmlElement(name = "x") +public class MucUser extends Extension { + + public static final int STATUS_CODE_SELF_PRESENCE = 110; + + public MucUser() { + super(MucUser.class); + } + + public Item getItem() { + return this.getExtension(Item.class); + } + + public Collection getStatus() { + return Collections2.filter( + Collections2.transform(getExtensions(Status.class), Status::getCode), + Objects::nonNull); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/Status.java b/src/main/java/im/conversations/android/xmpp/model/muc/user/Status.java new file mode 100644 index 000000000..0706585af --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/user/Status.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.muc.user; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Status extends Extension { + + public Status() { + super(Status.class); + } + + public Integer getCode() { + return this.getOptionalIntAttribute("code").orNull(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java b/src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java new file mode 100644 index 000000000..f5bfcaeda --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.MUC_USER) +package im.conversations.android.xmpp.model.muc.user; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java b/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java new file mode 100644 index 000000000..e9a985128 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.nick; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.NICK) +public class Nick extends Extension { + + public Nick() { + super(Nick.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java b/src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java new file mode 100644 index 000000000..29ffc739f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java @@ -0,0 +1,19 @@ +package im.conversations.android.xmpp.model.occupant; + +import com.google.common.base.Strings; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.OCCUPANT_ID) +public class OccupantId extends Extension { + + public OccupantId() { + super(OccupantId.class); + } + + public String getId() { + return Strings.emptyToNull(this.getAttribute("id")); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java b/src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java new file mode 100644 index 000000000..b324332a9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java @@ -0,0 +1,18 @@ +package im.conversations.android.xmpp.model.oob; + +import com.google.common.base.Strings; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "x") +public class OutOfBandData extends Extension { + + public OutOfBandData() { + super(OutOfBandData.class); + } + + public String getURL() { + final URL url = this.getExtension(URL.class); + return url == null ? null : Strings.emptyToNull(url.getContent()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/oob/URL.java b/src/main/java/im/conversations/android/xmpp/model/oob/URL.java new file mode 100644 index 000000000..008b08480 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/oob/URL.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.oob; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "url") +public class URL extends Extension { + + public URL() { + super(URL.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/oob/package-info.java b/src/main/java/im/conversations/android/xmpp/model/oob/package-info.java new file mode 100644 index 000000000..aec4dee24 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/oob/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.OOB) +package im.conversations.android.xmpp.model.oob; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java b/src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java new file mode 100644 index 000000000..d3d4b3910 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.pars; + +import im.conversations.android.annotation.XmlElement; +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.PARS) +public class PreAuth extends Extension { + + public PreAuth() { + super(PreAuth.class); + } + + public void setToken(final String token) { + this.setAttribute("token", token); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pgp/Encrypted.java b/src/main/java/im/conversations/android/xmpp/model/pgp/Encrypted.java new file mode 100644 index 000000000..43e4e2354 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pgp/Encrypted.java @@ -0,0 +1,14 @@ +package im.conversations.android.xmpp.model.pgp; + +import eu.siacs.conversations.xml.Namespace; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "x", namespace = Namespace.PGP_ENCRYPTED) +public class Encrypted extends Extension { + + public Encrypted() { + super(Encrypted.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pgp/Signed.java b/src/main/java/im/conversations/android/xmpp/model/pgp/Signed.java new file mode 100644 index 000000000..c75413972 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pgp/Signed.java @@ -0,0 +1,15 @@ +package im.conversations.android.xmpp.model.pgp; + +import eu.siacs.conversations.xml.Namespace; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "x",namespace = Namespace.PGP_SIGNED) +public class Signed extends Extension { + + + public Signed() { + super(Signed.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/ping/Ping.java b/src/main/java/im/conversations/android/xmpp/model/ping/Ping.java new file mode 100644 index 000000000..7f8f1c3a0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/ping/Ping.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.ping; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.PING) +public class Ping extends Extension { + + public Ping() { + super(Ping.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java new file mode 100644 index 000000000..dbf2c3c23 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model.pubsub; + +import im.conversations.android.xmpp.model.Extension; + +public interface Item { + + T getExtension(final Class clazz); + + String getId(); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java new file mode 100644 index 000000000..ceb1931ca --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java @@ -0,0 +1,52 @@ +package im.conversations.android.xmpp.model.pubsub; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.pubsub.event.Retract; +import java.util.Collection; +import java.util.Map; +import java.util.NoSuchElementException; + +public interface Items { + + Collection getItems(); + + String getNode(); + + Collection getRetractions(); + + default Map getItemMap(final Class clazz) { + final ImmutableMap.Builder builder = ImmutableMap.builder(); + for (final Item item : getItems()) { + final var id = item.getId(); + final T extension = item.getExtension(clazz); + if (extension == null || Strings.isNullOrEmpty(id)) { + continue; + } + builder.put(id, extension); + } + return builder.buildKeepingLast(); + } + + default T getItemOrThrow(final String id, final Class clazz) { + final var map = getItemMap(clazz); + final var item = map.get(id); + if (item == null) { + throw new NoSuchElementException( + String.format("An item with id %s does not exist", id)); + } + return item; + } + + default T getFirstItem(final Class clazz) { + final var map = getItemMap(clazz); + return Iterables.getFirst(map.values(), null); + } + + default T getOnlyItem(final Class clazz) { + final var map = getItemMap(clazz); + return Iterables.getOnlyElement(map.values()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java new file mode 100644 index 000000000..a4fc1ee8e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java @@ -0,0 +1,64 @@ +package im.conversations.android.xmpp.model.pubsub; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.pubsub.event.Retract; +import java.util.Collection; + +@XmlElement(name = "pubsub") +public class PubSub extends Extension { + + public PubSub() { + super(PubSub.class); + } + + public Items getItems() { + return this.getExtension(ItemsWrapper.class); + } + + @XmlElement(name = "items") + public static class ItemsWrapper extends Extension implements Items { + + public ItemsWrapper() { + super(ItemsWrapper.class); + } + + public String getNode() { + return this.getAttribute("node"); + } + + public Collection getItems() { + return this.getExtensions(Item.class); + } + + public Collection getRetractions() { + return this.getExtensions(Retract.class); + } + + public void setNode(String node) { + this.setAttribute("node", node); + } + + public void setMaxItems(final int maxItems) { + this.setAttribute("max_items", maxItems); + } + } + + @XmlElement(name = "item") + public static class Item extends Extension + implements im.conversations.android.xmpp.model.pubsub.Item { + + public Item() { + super(Item.class); + } + + @Override + public String getId() { + return this.getAttribute("id"); + } + + public void setId(String itemId) { + this.setAttribute("id", itemId); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java new file mode 100644 index 000000000..7a384f548 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.pubsub; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Publish extends Extension { + + public Publish() { + super(Publish.class); + } + + public void setNode(String node) { + this.setAttribute("node", node); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java new file mode 100644 index 000000000..ec94f0604 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.pubsub; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.NodeConfiguration; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.data.Data; + +@XmlElement +public class PublishOptions extends Extension { + + public PublishOptions() { + super(PublishOptions.class); + } + + public static PublishOptions of(NodeConfiguration nodeConfiguration) { + final var publishOptions = new PublishOptions(); + publishOptions.addExtension(Data.of(Namespace.PUBSUB_PUBLISH_OPTIONS, nodeConfiguration)); + return publishOptions; + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java new file mode 100644 index 000000000..309381197 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java @@ -0,0 +1,20 @@ +package im.conversations.android.xmpp.model.pubsub; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Retract extends Extension { + + public Retract() { + super(Retract.class); + } + + public void setNode(String node) { + this.setAttribute("node", node); + } + + public void setNotify(boolean notify) { + this.setAttribute("notify", notify ? 1 : 0); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java new file mode 100644 index 000000000..a1c81a659 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java @@ -0,0 +1,19 @@ +package im.conversations.android.xmpp.model.pubsub.error; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +public abstract class PubSubError extends Extension { + + private PubSubError(Class clazz) { + super(clazz); + } + + @XmlElement + public static class PreconditionNotMet extends PubSubError { + + public PreconditionNotMet() { + super(PreconditionNotMet.class); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java new file mode 100644 index 000000000..49d45f8c5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.PUBSUB_ERROR) +package im.conversations.android.xmpp.model.pubsub.error; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java new file mode 100644 index 000000000..1e180c460 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java @@ -0,0 +1,56 @@ +package im.conversations.android.xmpp.model.pubsub.event; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.pubsub.Items; +import java.util.Collection; + +@XmlElement +public class Event extends Extension { + + public Event() { + super(Event.class); + } + + public Items getItems() { + return this.getExtension(ItemsWrapper.class); + } + + public Purge getPurge() { + return this.getExtension(Purge.class); + } + + @XmlElement(name = "items") + public static class ItemsWrapper extends Extension implements Items { + + public ItemsWrapper() { + super(ItemsWrapper.class); + } + + public String getNode() { + return this.getAttribute("node"); + } + + public Collection getItems() { + return this.getExtensions(Item.class); + } + + public Collection getRetractions() { + return this.getExtensions(Retract.class); + } + } + + @XmlElement(name = "item") + public static class Item extends Extension + implements im.conversations.android.xmpp.model.pubsub.Item { + + public Item() { + super(Item.class); + } + + @Override + public String getId() { + return this.getAttribute("id"); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java new file mode 100644 index 000000000..64550e0b7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.pubsub.event; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Purge extends Extension { + + public Purge() { + super(Purge.class); + } + + public String getNode() { + return this.getAttribute("node"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java new file mode 100644 index 000000000..139a49522 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.pubsub.event; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Retract extends Extension { + + public Retract() { + super(Retract.class); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java new file mode 100644 index 000000000..223345c68 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.PUBSUB_EVENT) +package im.conversations.android.xmpp.model.pubsub.event; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java new file mode 100644 index 000000000..53b987f53 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.pubsub.owner; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.data.Data; + +@XmlElement +public class Configure extends Extension { + + public Configure() { + super(Configure.class); + } + + public void setNode(final String node) { + this.setAttribute("node", node); + } + + public Data getData() { + return this.getExtension(Data.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java new file mode 100644 index 000000000..c3a61e619 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.pubsub.owner; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "pubsub") +public class PubSubOwner extends Extension { + + public PubSubOwner() { + super(PubSubOwner.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java new file mode 100644 index 000000000..d3ecb89aa --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.PUBSUB_OWNER) +package im.conversations.android.xmpp.model.pubsub.owner; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java new file mode 100644 index 000000000..a68a021fd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.PUBSUB) +package im.conversations.android.xmpp.model.pubsub; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java b/src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java new file mode 100644 index 000000000..1d854a83a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.reactions; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Reaction extends Extension { + + public Reaction() { + super(Reaction.class); + } + + public Reaction(final String reaction) { + this(); + setContent(reaction); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java b/src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java new file mode 100644 index 000000000..ec3ae9891 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java @@ -0,0 +1,36 @@ +package im.conversations.android.xmpp.model.reactions; + +import com.google.common.base.Strings; +import com.google.common.collect.Collections2; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import java.util.Objects; + +@XmlElement +public class Reactions extends Extension { + + public Reactions() { + super(Reactions.class); + } + + public Collection getReactions() { + return Collections2.filter( + Collections2.transform(getExtensions(Reaction.class), Reaction::getContent), + r -> Objects.nonNull(Strings.nullToEmpty(r))); + } + + public String getId() { + return this.getAttribute("id"); + } + + public void setId(String id) { + this.setAttribute("id", id); + } + + public static Reactions to(final String id) { + final var reactions = new Reactions(); + reactions.setId(id); + return reactions; + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java b/src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java new file mode 100644 index 000000000..bdb8a8dca --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.REACTIONS) +package im.conversations.android.xmpp.model.reactions; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/receipts/Received.java b/src/main/java/im/conversations/android/xmpp/model/receipts/Received.java new file mode 100644 index 000000000..71fe922c1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/receipts/Received.java @@ -0,0 +1,20 @@ +package im.conversations.android.xmpp.model.receipts; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.DeliveryReceipt; + +@XmlElement +public class Received extends DeliveryReceipt { + + public Received() { + super(Received.class); + } + + public void setId(String id) { + this.setAttribute("id", id); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/receipts/Request.java b/src/main/java/im/conversations/android/xmpp/model/receipts/Request.java new file mode 100644 index 000000000..684477af3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/receipts/Request.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.receipts; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.DeliveryReceiptRequest; + +@XmlElement +public class Request extends DeliveryReceiptRequest { + + public Request() { + super(Request.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java b/src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java new file mode 100644 index 000000000..8e3de2cad --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.DELIVERY_RECEIPTS) +package im.conversations.android.xmpp.model.receipts; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Instructions.java b/src/main/java/im/conversations/android/xmpp/model/register/Instructions.java new file mode 100644 index 000000000..cd22f2a3a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Instructions.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model.register; + +import im.conversations.android.xmpp.model.Extension; + +public class Instructions extends Extension { + + public Instructions() { + super(Instructions.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Password.java b/src/main/java/im/conversations/android/xmpp/model/register/Password.java new file mode 100644 index 000000000..9da687c21 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Password.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model.register; + +import im.conversations.android.xmpp.model.Extension; + +public class Password extends Extension { + + public Password() { + super(Password.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Register.java b/src/main/java/im/conversations/android/xmpp/model/register/Register.java new file mode 100644 index 000000000..4a48bd8d1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Register.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.register; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import org.jxmpp.jid.parts.Localpart; + +@XmlElement(name = "query") +public class Register extends Extension { + + public Register() { + super(Register.class); + } + + public void addUsername(final Localpart username) { + this.addExtension(new Username()).setContent(username.toString()); + } + + public void addPassword(final String password) { + this.addExtension(new Password()).setContent(password); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Remove.java b/src/main/java/im/conversations/android/xmpp/model/register/Remove.java new file mode 100644 index 000000000..bbd327bfd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Remove.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model.register; + +import im.conversations.android.xmpp.model.Extension; + +public class Remove extends Extension { + + public Remove() { + super(Remove.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Username.java b/src/main/java/im/conversations/android/xmpp/model/register/Username.java new file mode 100644 index 000000000..bc93581b6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Username.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.register; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Username extends Extension { + + public Username() { + super(Username.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/package-info.java b/src/main/java/im/conversations/android/xmpp/model/register/package-info.java new file mode 100644 index 000000000..9e7a3e8f3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.REGISTER) +package im.conversations.android.xmpp.model.register; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Group.java b/src/main/java/im/conversations/android/xmpp/model/roster/Group.java new file mode 100644 index 000000000..9f36efae7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/roster/Group.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.roster; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Group extends Extension { + + public Group() { + super(Group.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Item.java b/src/main/java/im/conversations/android/xmpp/model/roster/Item.java new file mode 100644 index 000000000..0a2e0ef54 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/roster/Item.java @@ -0,0 +1,61 @@ +package im.conversations.android.xmpp.model.roster; + +import com.google.common.collect.Collections2; + +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.Jid; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +@XmlElement +public class Item extends Extension { + + public static final List RESULT_SUBSCRIPTIONS = + Arrays.asList(Subscription.NONE, Subscription.TO, Subscription.FROM, Subscription.BOTH); + + public Item() { + super(Item.class); + } + + public Jid getJid() { + return getAttributeAsJid("jid"); + } + + public String getItemName() { + return this.getAttribute("name"); + } + + public boolean isPendingOut() { + return "subscribe".equalsIgnoreCase(this.getAttribute("ask")); + } + + public Subscription getSubscription() { + final String value = this.getAttribute("subscription"); + try { + return value == null ? null : Subscription.valueOf(value.toUpperCase(Locale.ROOT)); + } catch (final IllegalArgumentException e) { + return null; + } + } + + public Collection getGroups() { + return Collections2.filter( + Collections2.transform(getExtensions(Group.class), Element::getContent), + Objects::nonNull); + } + + public enum Subscription { + NONE, + TO, + FROM, + BOTH, + REMOVE + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Query.java b/src/main/java/im/conversations/android/xmpp/model/roster/Query.java new file mode 100644 index 000000000..616f6ae0b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/roster/Query.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.roster; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "query", namespace = Namespace.ROSTER) +public class Query extends Extension { + + public Query() { + super(Query.class); + } + + public void setVersion(final String rosterVersion) { + this.setAttribute("ver", rosterVersion); + } + + public String getVersion() { + return this.getAttribute("ver"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/package-info.java b/src/main/java/im/conversations/android/xmpp/model/roster/package-info.java new file mode 100644 index 000000000..eea0703fd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/roster/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.ROSTER) +package im.conversations.android.xmpp.model.roster; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/After.java b/src/main/java/im/conversations/android/xmpp/model/rsm/After.java new file mode 100644 index 000000000..90179bff0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/After.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.rsm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class After extends Extension { + + public After() { + super(After.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Before.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Before.java new file mode 100644 index 000000000..c3c6ac1a8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Before.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.rsm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Before extends Extension { + + public Before() { + super(Before.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Count.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Count.java new file mode 100644 index 000000000..c54f9d5e0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Count.java @@ -0,0 +1,23 @@ +package im.conversations.android.xmpp.model.rsm; + +import com.google.common.base.Strings; +import com.google.common.primitives.Ints; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Count extends Extension { + + public Count() { + super(Count.class); + } + + public Integer getCount() { + final var content = getContent(); + if (Strings.isNullOrEmpty(content)) { + return null; + } else { + return Ints.tryParse(content); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/First.java b/src/main/java/im/conversations/android/xmpp/model/rsm/First.java new file mode 100644 index 000000000..b976632e4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/First.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.rsm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class First extends Extension { + + public First() { + super(First.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Last.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Last.java new file mode 100644 index 000000000..01d53e073 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Last.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.rsm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Last extends Extension { + + public Last() { + super(Last.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Max.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Max.java new file mode 100644 index 000000000..06908be8b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Max.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.rsm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Max extends Extension { + + public Max() { + super(Max.class); + } + + public void setMax(final int max) { + this.setContent(String.valueOf(max)); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Set.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Set.java new file mode 100644 index 000000000..6f428565c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Set.java @@ -0,0 +1,55 @@ +package im.conversations.android.xmpp.model.rsm; + +import com.google.common.base.Strings; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.Page; +import im.conversations.android.xmpp.Range; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Set extends Extension { + + public Set() { + super(Set.class); + } + + public static Set of(final Range range, final Integer max) { + final var set = new Set(); + if (range.order == Range.Order.NORMAL) { + final var after = set.addExtension(new After()); + after.setContent(range.id); + } else if (range.order == Range.Order.REVERSE) { + final var before = set.addExtension(new Before()); + before.setContent(range.id); + } else { + throw new IllegalArgumentException("Invalid order"); + } + if (max != null) { + set.addExtension(new Max()).setMax(max); + } + return set; + } + + public Page asPage() { + final var first = this.getExtension(First.class); + final var last = this.getExtension(Last.class); + + final var firstId = first == null ? null : first.getContent(); + final var lastId = last == null ? null : last.getContent(); + if (Strings.isNullOrEmpty(firstId) || Strings.isNullOrEmpty(lastId)) { + throw new IllegalStateException("Invalid page. Missing first or last"); + } + return new Page(firstId, lastId, this.getCount()); + } + + public boolean isEmpty() { + final var first = this.getExtension(First.class); + final var last = this.getExtension(Last.class); + return first == null && last == null; + } + + public Integer getCount() { + final var count = this.getExtension(Count.class); + return count == null ? null : count.getCount(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/package-info.java b/src/main/java/im/conversations/android/xmpp/model/rsm/package-info.java new file mode 100644 index 000000000..c00fd37c9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.RESULT_SET_MANAGEMENT) +package im.conversations.android.xmpp.model.rsm; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java new file mode 100644 index 000000000..668d10d80 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sasl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Auth extends StreamElement { + + public Auth() { + super(Auth.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanism.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanism.java new file mode 100644 index 000000000..e23087d89 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanism.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sasl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Mechanism extends Extension { + + public Mechanism() { + super(Mechanism.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanisms.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanisms.java new file mode 100644 index 000000000..7612ba358 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanisms.java @@ -0,0 +1,29 @@ +package im.conversations.android.xmpp.model.sasl; + +import com.google.common.collect.Collections2; + +import eu.siacs.conversations.xml.Element; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.AuthenticationStreamFeature; +import im.conversations.android.xmpp.model.StreamFeature; + +import java.util.Collection; +import java.util.Objects; + +@XmlElement +public class Mechanisms extends AuthenticationStreamFeature { + + + public Mechanisms() { + super(Mechanisms.class); + } + + public Collection getMechanisms() { + return getExtensions(Mechanism.class); + } + + public Collection getMechanismNames() { + return Collections2.filter(Collections2.transform(getMechanisms(), Element::getContent), Objects::nonNull); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Response.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Response.java new file mode 100644 index 000000000..5e2ab626e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Response.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sasl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Response extends StreamElement { + + public Response() { + super(Response.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Success.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Success.java new file mode 100644 index 000000000..d7323e478 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Success.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.sasl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Success extends StreamElement { + + + public Success() { + super(Success.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/package-info.java b/src/main/java/im/conversations/android/xmpp/model/sasl/package-info.java new file mode 100644 index 000000000..3b0de4f4a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.SASL) +package im.conversations.android.xmpp.model.sasl; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java new file mode 100644 index 000000000..7869bf010 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sasl2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Authenticate extends StreamElement { + + public Authenticate() { + super(Authenticate.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Authentication.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authentication.java new file mode 100644 index 000000000..ad26d37e6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authentication.java @@ -0,0 +1,30 @@ +package im.conversations.android.xmpp.model.sasl2; + +import com.google.common.collect.Collections2; + +import eu.siacs.conversations.xml.Element; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.AuthenticationStreamFeature; +import im.conversations.android.xmpp.model.StreamFeature; + +import java.util.Collection; +import java.util.Objects; + +@XmlElement +public class Authentication extends AuthenticationStreamFeature { + public Authentication() { + super(Authentication.class); + } + + public Collection getMechanisms() { + return getExtensions(Mechanism.class); + } + + public Collection getMechanismNames() { + return Collections2.filter(Collections2.transform(getMechanisms(), Element::getContent), Objects::nonNull); + } + + public Inline getInline() { + return this.getExtension(Inline.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java new file mode 100644 index 000000000..e29ae7dea --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java @@ -0,0 +1,28 @@ +package im.conversations.android.xmpp.model.sasl2; + +import com.google.common.base.Strings; + +import eu.siacs.conversations.xmpp.Jid; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class AuthorizationIdentifier extends Extension { + + + public AuthorizationIdentifier() { + super(AuthorizationIdentifier.class); + } + + public Jid get() { + final var content = getContent(); + if ( Strings.isNullOrEmpty(content)) { + return null; + } + try { + return Jid.ofEscaped(content); + } catch (final IllegalArgumentException e) { + return null; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java new file mode 100644 index 000000000..6a6ad0dd8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java @@ -0,0 +1,34 @@ +package im.conversations.android.xmpp.model.sasl2; + +import com.google.common.collect.Collections2; + +import eu.siacs.conversations.xml.Element; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.fast.Fast; +import im.conversations.android.xmpp.model.fast.Mechanism; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +@XmlElement +public class Inline extends Extension { + + public Inline() { + super(Inline.class); + } + + public Fast getFast() { + return this.getExtension(Fast.class); + } + + public Collection getFastMechanisms() { + final var fast = getFast(); + final Collection mechanisms = + fast == null ? Collections.emptyList() : fast.getExtensions(Mechanism.class); + return Collections2.filter( + Collections2.transform(mechanisms, Element::getContent), Objects::nonNull); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Mechanism.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Mechanism.java new file mode 100644 index 000000000..d0a615777 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Mechanism.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sasl2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Mechanism extends Extension { + + public Mechanism() { + super(Mechanism.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Response.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Response.java new file mode 100644 index 000000000..91f1b7dab --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Response.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sasl2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Response extends StreamElement { + + public Response() { + super(Response.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Success.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Success.java new file mode 100644 index 000000000..17673b35a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Success.java @@ -0,0 +1,23 @@ +package im.conversations.android.xmpp.model.sasl2; + +import eu.siacs.conversations.xmpp.Jid; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Success extends StreamElement { + + + public Success() { + super(Success.class); + } + + public Jid getAuthorizationIdentifier() { + final var id = this.getExtension(AuthorizationIdentifier.class); + if (id == null) { + return null; + } + return id.get(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java new file mode 100644 index 000000000..10a61d109 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.SASL_2) +package im.conversations.android.xmpp.model.sasl2; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Ack.java b/src/main/java/im/conversations/android/xmpp/model/sm/Ack.java new file mode 100644 index 000000000..5cafc8c1d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Ack.java @@ -0,0 +1,23 @@ +package im.conversations.android.xmpp.model.sm; + +import com.google.common.base.Optional; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement(name = "a") +public class Ack extends StreamElement { + + public Ack() { + super(Ack.class); + } + + public Ack(final int sequence) { + super(Ack.class); + this.setAttribute("h", sequence); + } + + public Optional getHandled() { + return this.getOptionalIntAttribute("h"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Enable.java b/src/main/java/im/conversations/android/xmpp/model/sm/Enable.java new file mode 100644 index 000000000..9b80a93ba --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Enable.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.sm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Enable extends StreamElement { + + public Enable() { + super(Enable.class); + this.setAttribute("resume", "true"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Enabled.java b/src/main/java/im/conversations/android/xmpp/model/sm/Enabled.java new file mode 100644 index 000000000..b900d435c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Enabled.java @@ -0,0 +1,35 @@ +package im.conversations.android.xmpp.model.sm; + +import com.google.common.base.Optional; +import com.google.common.base.Strings; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Enabled extends StreamElement { + + public Enabled() { + super(Enabled.class); + } + + public boolean isResume() { + return this.getAttributeAsBoolean("resume"); + } + + public String getLocation() { + return this.getAttribute("location"); + } + + public Optional getResumeId() { + final var id = this.getAttribute("id"); + if (Strings.isNullOrEmpty(id)) { + return Optional.absent(); + } + if (isResume()) { + return Optional.of(id); + } else { + return Optional.absent(); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Failed.java b/src/main/java/im/conversations/android/xmpp/model/sm/Failed.java new file mode 100644 index 000000000..1e15bfe6c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Failed.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.sm; + +import com.google.common.base.Optional; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Failed extends StreamElement { + public Failed() { + super(Failed.class); + } + + public Optional getHandled() { + return this.getOptionalIntAttribute("h"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Request.java b/src/main/java/im/conversations/android/xmpp/model/sm/Request.java new file mode 100644 index 000000000..ad1de61bc --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Request.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement(name = "r") +public class Request extends StreamElement { + + public Request() { + super(Request.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Resume.java b/src/main/java/im/conversations/android/xmpp/model/sm/Resume.java new file mode 100644 index 000000000..e47b19966 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Resume.java @@ -0,0 +1,18 @@ +package im.conversations.android.xmpp.model.sm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Resume extends StreamElement { + + public Resume() { + super(Resume.class); + } + + public Resume(final String id, final int sequence) { + super(Resume.class); + this.setAttribute("previd", id); + this.setAttribute("h", sequence); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Resumed.java b/src/main/java/im/conversations/android/xmpp/model/sm/Resumed.java new file mode 100644 index 000000000..eb240745f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Resumed.java @@ -0,0 +1,18 @@ +package im.conversations.android.xmpp.model.sm; + +import com.google.common.base.Optional; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Resumed extends StreamElement { + + public Resumed() { + super(Resumed.class); + } + + public Optional getHandled() { + return this.getOptionalIntAttribute("h"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/StreamManagement.java b/src/main/java/im/conversations/android/xmpp/model/sm/StreamManagement.java new file mode 100644 index 000000000..48103755a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/StreamManagement.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamFeature; + +@XmlElement(name = "sm") +public class StreamManagement extends StreamFeature { + + public StreamManagement() { + super(StreamManagement.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/package-info.java b/src/main/java/im/conversations/android/xmpp/model/sm/package-info.java new file mode 100644 index 000000000..dd2e036fc --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.STREAM_MANAGEMENT) +package im.conversations.android.xmpp.model.sm; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java new file mode 100644 index 000000000..9f94400c3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java @@ -0,0 +1,77 @@ +package im.conversations.android.xmpp.model.stanza; + +import com.google.common.base.Strings; + +import eu.siacs.conversations.xml.Element; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.error.Error; + +import java.util.Locale; + +@XmlElement +public class Iq extends Stanza { + + public static Iq TIMEOUT = new Iq(Type.TIMEOUT); + + public Iq() { + super(Iq.class); + } + + public Iq(final Type type) { + super(Iq.class); + this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT)); + } + + // TODO get rid of timeout + public enum Type { + SET, + GET, + ERROR, + RESULT, + TIMEOUT + } + + public Type getType() { + return Type.valueOf( + Strings.nullToEmpty(this.getAttribute("type")).toUpperCase(Locale.ROOT)); + } + + @Override + public boolean isInvalid() { + final var id = getId(); + if (Strings.isNullOrEmpty(id)) { + return true; + } + return super.isInvalid(); + } + + // Legacy methods that need to be refactored: + + public Element query() { + final Element query = findChild("query"); + if (query != null) { + return query; + } + return addChild("query"); + } + + public Element query(final String xmlns) { + final Element query = query(); + query.setAttribute("xmlns", xmlns); + return query(); + } + + public Iq generateResponse(final Iq.Type type) { + final var packet = new Iq(type); + packet.setTo(this.getFrom()); + packet.setId(this.getId()); + return packet; + } + + public String getErrorCondition() { + final Error error = getError(); + final var condition = error == null ? null : error.getCondition(); + return condition == null ? null : condition.getName(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java new file mode 100644 index 000000000..a1c981b9d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java @@ -0,0 +1,64 @@ +package im.conversations.android.xmpp.model.stanza; + +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.LocalizedContent; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.jabber.Body; + +import java.util.Locale; + +@XmlElement +public class Message extends Stanza { + + public Message() { + super(Message.class); + } + + public Message(Type type) { + this(); + this.setType(type); + } + + public LocalizedContent getBody() { + return findInternationalizedChildContentInDefaultNamespace("body"); + } + + public Type getType() { + final var value = this.getAttribute("type"); + if (value == null) { + return Type.NORMAL; + } else { + try { + return Type.valueOf(value.toUpperCase(Locale.ROOT)); + } catch (final IllegalArgumentException e) { + return null; + } + } + } + + public void setType(final Type type) { + if (type == null || type == Type.NORMAL) { + this.removeAttribute("type"); + } else { + this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT)); + } + } + + public void setBody(final String text) { + this.addExtension(new Body(text)); + } + + public void setAxolotlMessage(Element axolotlMessage) { + this.children.remove(findChild("body")); + this.children.add(0, axolotlMessage); + } + + public enum Type { + ERROR, + NORMAL, + GROUPCHAT, + HEADLINE, + CHAT + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java new file mode 100644 index 000000000..129660b00 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.stanza; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.capabilties.EntityCapabilities; + +@XmlElement +public class Presence extends Stanza implements EntityCapabilities { + + public Presence() { + super(Presence.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java new file mode 100644 index 000000000..82a8ce3df --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java @@ -0,0 +1,74 @@ +package im.conversations.android.xmpp.model.stanza; + +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.xmpp.InvalidJid; +import eu.siacs.conversations.xmpp.Jid; + +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.StreamElement; +import im.conversations.android.xmpp.model.error.Error; + +public abstract class Stanza extends StreamElement { + + protected Stanza(final Class clazz) { + super(clazz); + } + + public Jid getTo() { + return this.getAttributeAsJid("to"); + } + + public Jid getFrom() { + return this.getAttributeAsJid("from"); + } + + public String getId() { + return this.getAttribute("id"); + } + + public void setId(final String id) { + this.setAttribute("id", id); + } + + public void setFrom(final Jid from) { + this.setAttribute("from", from); + } + + public void setTo(final Jid to) { + this.setAttribute("to", to); + } + + public Error getError() { + return this.getExtension(Error.class); + } + + public boolean isInvalid() { + final var to = getTo(); + final var from = getFrom(); + if (to instanceof InvalidJid || from instanceof InvalidJid) { + return true; + } + return false; + } + + public boolean fromServer(final Account account) { + final Jid from = getFrom(); + return from == null + || from.equals(account.getDomain()) + || from.equals(account.getJid().asBareJid()) + || from.equals(account.getJid()); + } + + public boolean toServer(final Account account) { + final Jid to = getTo(); + return to == null + || to.equals(account.getDomain()) + || to.equals(account.getJid().asBareJid()) + || to.equals(account.getJid()); + } + + public boolean fromAccount(final Account account) { + final Jid from = getFrom(); + return from != null && from.asBareJid().equals(account.getJid().asBareJid()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java b/src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java new file mode 100644 index 000000000..d12fe56db --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.JABBER_CLIENT) +package im.conversations.android.xmpp.model.stanza; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Active.java b/src/main/java/im/conversations/android/xmpp/model/state/Active.java new file mode 100644 index 000000000..15970bc5b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Active.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Active extends ChatStateNotification { + + public Active() { + super(Active.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java b/src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java new file mode 100644 index 000000000..642ed519d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.xmpp.model.Extension; + +public abstract class ChatStateNotification extends Extension { + + protected ChatStateNotification(Class clazz) { + super(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Composing.java b/src/main/java/im/conversations/android/xmpp/model/state/Composing.java new file mode 100644 index 000000000..9871952e0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Composing.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Composing extends ChatStateNotification { + + public Composing() { + super(Composing.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Gone.java b/src/main/java/im/conversations/android/xmpp/model/state/Gone.java new file mode 100644 index 000000000..a0a74e788 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Gone.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Gone extends ChatStateNotification { + + public Gone() { + super(Gone.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java b/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java new file mode 100644 index 000000000..4a3670308 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Inactive extends ChatStateNotification { + + public Inactive() { + super(Inactive.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Paused.java b/src/main/java/im/conversations/android/xmpp/model/state/Paused.java new file mode 100644 index 000000000..f97f3e504 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Paused.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Paused extends ChatStateNotification { + + public Paused() { + super(Paused.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/package-info.java b/src/main/java/im/conversations/android/xmpp/model/state/package-info.java new file mode 100644 index 000000000..a0cc97deb --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.CHAT_STATES) +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/streams/Features.java b/src/main/java/im/conversations/android/xmpp/model/streams/Features.java new file mode 100644 index 000000000..0597c2241 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/streams/Features.java @@ -0,0 +1,33 @@ +package im.conversations.android.xmpp.model.streams; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.StreamElement; +import im.conversations.android.xmpp.model.StreamFeature; +import im.conversations.android.xmpp.model.capabilties.EntityCapabilities; +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.xmpp.model.sm.StreamManagement; + +@XmlElement +public class Features extends StreamElement implements EntityCapabilities { + public Features() { + super(Features.class); + } + + public boolean streamManagement() { + return hasStreamFeature(StreamManagement.class); + } + + public boolean invite() { + return this.hasChild("register", Namespace.INVITE); + } + + public boolean clientStateIndication() { + return this.hasChild("csi", Namespace.CSI); + } + + + public boolean hasStreamFeature(final Class clazz) { + return hasExtension(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/streams/package-info.java b/src/main/java/im/conversations/android/xmpp/model/streams/package-info.java new file mode 100644 index 000000000..56900532c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/streams/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.STREAMS) +package im.conversations.android.xmpp.model.streams; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/tls/Proceed.java b/src/main/java/im/conversations/android/xmpp/model/tls/Proceed.java new file mode 100644 index 000000000..3e2cf454c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/tls/Proceed.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.tls; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Proceed extends StreamElement { + + public Proceed() { + super(Proceed.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/tls/Required.java b/src/main/java/im/conversations/android/xmpp/model/tls/Required.java new file mode 100644 index 000000000..60f4652ba --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/tls/Required.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.tls; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Required extends Extension { + public Required() { + super(Required.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/tls/StartTls.java b/src/main/java/im/conversations/android/xmpp/model/tls/StartTls.java new file mode 100644 index 000000000..337371c7b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/tls/StartTls.java @@ -0,0 +1,15 @@ +package im.conversations.android.xmpp.model.tls; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement(name = "starttls") +public class StartTls extends StreamElement { + public StartTls() { + super(StartTls.class); + } + + public boolean isRequired() { + return hasExtension(Required.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/tls/package-info.java b/src/main/java/im/conversations/android/xmpp/model/tls/package-info.java new file mode 100644 index 000000000..de3ed3ecd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/tls/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.TLS) +package im.conversations.android.xmpp.model.tls; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java b/src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java new file mode 100644 index 000000000..31a939621 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.unique; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class OriginId extends Extension { + + public OriginId() { + super(OriginId.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java b/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java new file mode 100644 index 000000000..23b0fdcac --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.unique; + +import eu.siacs.conversations.xmpp.Jid; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class StanzaId extends Extension { + + public StanzaId() { + super(StanzaId.class); + } + + public Jid getBy() { + return this.getAttributeAsJid("by"); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/unique/package-info.java b/src/main/java/im/conversations/android/xmpp/model/unique/package-info.java new file mode 100644 index 000000000..31209ee24 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/unique/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.STANZA_IDS) +package im.conversations.android.xmpp.model.unique; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Get.java b/src/main/java/im/conversations/android/xmpp/model/upload/Get.java new file mode 100644 index 000000000..5fad9afd4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Get.java @@ -0,0 +1,22 @@ +package im.conversations.android.xmpp.model.upload; + +import com.google.common.base.Strings; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import okhttp3.HttpUrl; + +@XmlElement +public class Get extends Extension { + + public Get() { + super(Get.class); + } + + public HttpUrl getUrl() { + final var url = this.getAttribute("url"); + if (Strings.isNullOrEmpty(url)) { + return null; + } + return HttpUrl.parse(url); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Header.java b/src/main/java/im/conversations/android/xmpp/model/upload/Header.java new file mode 100644 index 000000000..00546d0d9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Header.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.upload; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Header extends Extension { + + public Header() { + super(Header.class); + } + + public String getHeaderName() { + return this.getAttribute("name"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Put.java b/src/main/java/im/conversations/android/xmpp/model/upload/Put.java new file mode 100644 index 000000000..1b52a495c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Put.java @@ -0,0 +1,27 @@ +package im.conversations.android.xmpp.model.upload; + +import com.google.common.base.Strings; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import okhttp3.HttpUrl; + +@XmlElement +public class Put extends Extension { + + public Put() { + super(Put.class); + } + + public HttpUrl getUrl() { + final var url = this.getAttribute("url"); + if (Strings.isNullOrEmpty(url)) { + return null; + } + return HttpUrl.parse(url); + } + + public Collection
getHeaders() { + return this.getExtensions(Header.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Request.java b/src/main/java/im/conversations/android/xmpp/model/upload/Request.java new file mode 100644 index 000000000..bbf8a98c1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Request.java @@ -0,0 +1,24 @@ +package im.conversations.android.xmpp.model.upload; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Request extends Extension { + + public Request() { + super(Request.class); + } + + public void setFilename(String filename) { + this.setAttribute("filename", filename); + } + + public void setSize(long size) { + this.setAttribute("size", size); + } + + public void setContentType(String type) { + this.setAttribute("content-ype", type); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java b/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java new file mode 100644 index 000000000..df9015781 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.upload; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Slot extends Extension { + + public Slot() { + super(Slot.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/package-info.java b/src/main/java/im/conversations/android/xmpp/model/upload/package-info.java new file mode 100644 index 000000000..e4ccf3d8d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.HTTP_UPLOAD) +package im.conversations.android.xmpp.model.upload; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/BinaryValue.java b/src/main/java/im/conversations/android/xmpp/model/vcard/BinaryValue.java new file mode 100644 index 000000000..273dcfb25 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/BinaryValue.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.vcard; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.ByteContent; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "BINVAL") +public class BinaryValue extends Extension implements ByteContent { + + public BinaryValue() { + super(BinaryValue.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java b/src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java new file mode 100644 index 000000000..92adc6831 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.vcard; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "PHOTO") +public class Photo extends Extension { + public Photo() { + super(Photo.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/VCard.java b/src/main/java/im/conversations/android/xmpp/model/vcard/VCard.java new file mode 100644 index 000000000..20a694977 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/VCard.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.vcard; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "vCard") +public class VCard extends Extension { + + public VCard() { + super(VCard.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/package-info.java b/src/main/java/im/conversations/android/xmpp/model/vcard/package-info.java new file mode 100644 index 000000000..7ee576ca2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.VCARD_TEMP) +package im.conversations.android.xmpp.model.vcard; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/update/Photo.java b/src/main/java/im/conversations/android/xmpp/model/vcard/update/Photo.java new file mode 100644 index 000000000..cb1f86d05 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/update/Photo.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.vcard.update; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Photo extends Extension { + + public Photo() { + super(Photo.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/update/VCardUpdate.java b/src/main/java/im/conversations/android/xmpp/model/vcard/update/VCardUpdate.java new file mode 100644 index 000000000..0be3f94b9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/update/VCardUpdate.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.vcard.update; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "x") +public class VCardUpdate extends Extension { + + public VCardUpdate() { + super(VCardUpdate.class); + } + + public Photo getPhoto() { + return this.getExtension(Photo.class); + } + + public String getHash() { + final var photo = getPhoto(); + return photo == null ? null : photo.getContent(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/update/package-info.java b/src/main/java/im/conversations/android/xmpp/model/vcard/update/package-info.java new file mode 100644 index 000000000..efed15360 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/update/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.VCARD_TEMP_UPDATE) +package im.conversations.android.xmpp.model.vcard.update; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/version/Version.java b/src/main/java/im/conversations/android/xmpp/model/version/Version.java new file mode 100644 index 000000000..7cbd5d22a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/version/Version.java @@ -0,0 +1,25 @@ +package im.conversations.android.xmpp.model.version; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import eu.siacs.conversations.xml.Namespace; + +@XmlElement(name = "query", namespace = Namespace.VERSION) +public class Version extends Extension { + + public Version() { + super(Version.class); + } + + public void setSoftwareName(final String name) { + this.addChild("name").setContent(name); + } + + public void setVersion(final String version) { + this.addChild("version").setContent(version); + } + + public void setOs(final String os) { + this.addChild("os").setContent(os); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java new file mode 100644 index 000000000..bc8097fda --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java @@ -0,0 +1,90 @@ +package im.conversations.android.xmpp.processor; + +import android.text.TextUtils; +import android.util.Log; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.generator.IqGenerator; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.xmpp.XmppConnection; + +import im.conversations.android.xmpp.model.stanza.Iq; + +public class BindProcessor implements Runnable { + + + private final XmppConnectionService service; + private final Account account; + + public BindProcessor(XmppConnectionService service, Account account) { + this.service = service; + this.account = account; + } + + @Override + public void run() { + final XmppConnection connection = account.getXmppConnection(); + service.cancelAvatarFetches(account); + final boolean loggedInSuccessfully = account.setOption(Account.OPTION_LOGGED_IN_SUCCESSFULLY, true); + final boolean gainedFeature = account.setOption(Account.OPTION_HTTP_UPLOAD_AVAILABLE, connection.getFeatures().httpUpload(0)); + if (loggedInSuccessfully || gainedFeature) { + service.databaseBackend.updateAccount(account); + } + + if (loggedInSuccessfully) { + if (!TextUtils.isEmpty(account.getDisplayName())) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": display name wasn't empty on first log in. publishing"); + service.publishDisplayName(account); + } + } + + account.getRoster().clearPresences(); + synchronized (account.inProgressConferenceJoins) { + account.inProgressConferenceJoins.clear(); + } + synchronized (account.inProgressConferencePings) { + account.inProgressConferencePings.clear(); + } + service.getJingleConnectionManager().notifyRebound(account); + service.getQuickConversationsService().considerSyncBackground(false); + + + connection.fetchRoster(); + + if (connection.getFeatures().bookmarks2()) { + service.fetchBookmarks2(account); + } else if (!connection.getFeatures().bookmarksConversion()) { + service.fetchBookmarks(account); + } + + if (connection.getFeatures().mds()) { + service.fetchMessageDisplayedSynchronization(account); + } else { + Log.d(Config.LOGTAG,account.getJid()+": server has no support for mds"); + } + final boolean flexible = connection.getFeatures().flexibleOfflineMessageRetrieval(); + final boolean catchup = service.getMessageArchiveService().inCatchup(account); + final boolean trackOfflineMessageRetrieval; + if (flexible && catchup && connection.isMamPreferenceAlways()) { + trackOfflineMessageRetrieval = false; + connection.sendIqPacket(IqGenerator.purgeOfflineMessages(), (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully purged offline messages"); + } + }); + } else { + trackOfflineMessageRetrieval = true; + } + service.sendPresence(account); + connection.trackOfflineMessageRetrieval(trackOfflineMessageRetrieval); + if (service.getPushManagementService().available(account)) { + service.getPushManagementService().registerPushTokenOnServer(account); + } + service.connectMultiModeConversations(account); + service.syncDirtyContacts(account); + + service.getUnifiedPushBroker().renewUnifiedPushEndpointsOnBind(account); + + } +} diff --git a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java index 45180b5d5..e5f4b9d7c 100644 --- a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java +++ b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java @@ -15,7 +15,8 @@ import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.forms.Data; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; + +import im.conversations.android.xmpp.model.stanza.Iq; public class PushManagementService { @@ -25,7 +26,7 @@ public class PushManagementService { this.mXmppConnectionService = service; } - private static Data findResponseData(IqPacket response) { + private static Data findResponseData(Iq response) { final Element command = response.findChild("command", Namespace.COMMANDS); final Element x = command == null ? null : command.findChild("x", Namespace.DATA); return x == null ? null : Data.parse(x); @@ -35,43 +36,70 @@ private Jid getAppServer() { return Jid.of(mXmppConnectionService.getString(R.string.app_server)); } - void registerPushTokenOnServer(final Account account) { + public void registerPushTokenOnServer(final Account account) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has push support"); - retrieveFcmInstanceToken(token -> { - final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService); - final IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(getAppServer(), token, androidId); - mXmppConnectionService.sendIqPacket(account, packet, (a, response) -> { - final Data data = findResponseData(response); - if (response.getType() == IqPacket.TYPE.RESULT && data != null) { - try { - String node = data.getValue("node"); - String secret = data.getValue("secret"); - Jid jid = Jid.of(data.getValue("jid")); - if (node != null && secret != null) { - enablePushOnServer(a, jid, node, secret); - } - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } - } else { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": failed to enable push. invalid response from app server " + response); - } - }); - }); + retrieveFcmInstanceToken( + token -> { + final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService); + final var packet = + mXmppConnectionService + .getIqGenerator() + .pushTokenToAppServer(getAppServer(), token, androidId); + mXmppConnectionService.sendIqPacket( + account, + packet, + (response) -> { + final Data data = findResponseData(response); + if (response.getType() == Iq.Type.RESULT && data != null) { + final Jid jid; + try { + jid = Jid.ofEscaped(data.getValue("jid")); + } catch (final IllegalArgumentException e) { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": failed to enable push. invalid jid"); + return; + } + final String node = data.getValue("node"); + final String secret = data.getValue("secret"); + if (node != null && secret != null) { + enablePushOnServer(account, jid, node, secret); + } + } else { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": failed to enable push. invalid response from app server " + + response); + } + }); + }); } - private void enablePushOnServer(final Account account, final Jid appServer, final String node, final String secret) { - final IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret); - mXmppConnectionService.sendIqPacket(account, enable, (a, p) -> { - if (p.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on server"); - } else if (p.getType() == IqPacket.TYPE.ERROR) { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": enabling push on server failed"); - } - }); + private void enablePushOnServer( + final Account account, final Jid appServer, final String node, final String secret) { + final Iq enable = + mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret); + mXmppConnectionService.sendIqPacket( + account, + enable, + (p) -> { + if (p.getType() == Iq.Type.RESULT) { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": successfully enabled push on server"); + } else if (p.getType() == Iq.Type.ERROR) { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + ": enabling push on server failed"); + } + }); } - private void retrieveFcmInstanceToken(final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) { + private void retrieveFcmInstanceToken( + final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) { final FirebaseMessaging firebaseMessaging; try { firebaseMessaging = FirebaseMessaging.getInstance(); @@ -79,26 +107,33 @@ private void retrieveFcmInstanceToken(final OnGcmInstanceTokenRetrieved instance Log.d(Config.LOGTAG, "unable to get firebase instance token ", e); return; } - firebaseMessaging.getToken().addOnCompleteListener(task -> { - if (!task.isSuccessful()) { - Log.d(Config.LOGTAG, "unable to get Firebase instance token", task.getException()); - } - final String result; - try { - result = task.getResult(); - } catch (Exception e) { - Log.d(Config.LOGTAG, "unable to get Firebase instance token due to bug in library ", e); - return; - } - if (result != null) { - instanceTokenRetrieved.onGcmInstanceTokenRetrieved(result); - } - }); - + firebaseMessaging + .getToken() + .addOnCompleteListener( + task -> { + if (!task.isSuccessful()) { + Log.d( + Config.LOGTAG, + "unable to get Firebase instance token", + task.getException()); + } + final String result; + try { + result = task.getResult(); + } catch (Exception e) { + Log.d( + Config.LOGTAG, + "unable to get Firebase instance token due to bug in library ", + e); + return; + } + if (result != null) { + instanceTokenRetrieved.onGcmInstanceTokenRetrieved(result); + } + }); } - - public boolean available(Account account) { + public boolean available(final Account account) { final XmppConnection connection = account.getXmppConnection(); return connection != null && connection.getFeatures().sm() @@ -107,7 +142,9 @@ public boolean available(Account account) { } private boolean playServicesAvailable() { - return GoogleApiAvailabilityLight.getInstance().isGooglePlayServicesAvailable(mXmppConnectionService) == ConnectionResult.SUCCESS; + return GoogleApiAvailabilityLight.getInstance() + .isGooglePlayServicesAvailable(mXmppConnectionService) + == ConnectionResult.SUCCESS; } public boolean isStub() { diff --git a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java index 69cef9548..8cf8fe202 100644 --- a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java +++ b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java @@ -69,7 +69,7 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import io.michaelrocks.libphonenumber.android.Phonenumber; public class QuickConversationsService extends AbstractQuickConversationsService { @@ -463,15 +463,15 @@ private boolean considerSync(final Account account, final Map { - if (response.getType() == IqPacket.TYPE.RESULT) { + service.sendIqPacket(account, query, (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element phoneBook = response.findChild("phone-book", Namespace.SYNCHRONIZATION); if (phoneBook != null) { final List withSystemAccounts = account.getRoster().getWithSystemAccounts(PhoneNumberContact.class); @@ -498,7 +498,7 @@ private boolean considerSync(final Account account, final Map Date: Fri, 24 May 2024 08:59:28 +0200 Subject: [PATCH 19/49] bump various libraries --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 738124702..5443d33a9 100644 --- a/build.gradle +++ b/build.gradle @@ -83,12 +83,12 @@ dependencies { implementation 'me.drakeet.support:toastcompat:1.1.0' implementation "com.leinardi.android:speed-dial:3.3.0" - implementation "com.squareup.retrofit2:retrofit:2.9.0" - implementation "com.squareup.retrofit2:converter-gson:2.9.0" + implementation "com.squareup.retrofit2:retrofit:2.11.0" + implementation "com.squareup.retrofit2:converter-gson:2.11.0" implementation "com.squareup.okhttp3:okhttp:4.12.0" implementation 'com.google.guava:guava:32.1.3-android' - quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.13.28' + quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.13.35' implementation 'im.conversations.webrtc:webrtc-android:119.0.1' } From c06ec217437074f5589944d6b792dc5260277011 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 May 2024 19:14:22 +0200 Subject: [PATCH 20/49] fix 'allow pm' checkbox default --- .../java/eu/siacs/conversations/entities/MucOptions.java | 6 ++++++ .../eu/siacs/conversations/ui/util/MucConfiguration.java | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 38e97957a..0f6580005 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -18,6 +18,7 @@ import eu.siacs.conversations.xmpp.pep.Avatar; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -192,6 +193,11 @@ public boolean allowPm() { } } + public boolean allowPmRaw() { + final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowpm"); + return field == null || Arrays.asList("anyone","participants").contains(field.getValue()); + } + public boolean participating() { return self.getRole().ranks(Role.PARTICIPANT) || !moderated(); } diff --git a/src/main/java/eu/siacs/conversations/ui/util/MucConfiguration.java b/src/main/java/eu/siacs/conversations/ui/util/MucConfiguration.java index 1b6c4e6d6..f46b9ea28 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/MucConfiguration.java +++ b/src/main/java/eu/siacs/conversations/ui/util/MucConfiguration.java @@ -57,7 +57,7 @@ public static MucConfiguration get(Context context, boolean advanced, MucOptions mucOptions.nonanonymous(), mucOptions.participantsCanChangeSubject(), mucOptions.moderated(), - mucOptions.allowPm() + mucOptions.allowPmRaw() }; options = new Option[] { From 987b32fb8462a9007c96d7a0d33446b6799218ec Mon Sep 17 00:00:00 2001 From: mazhe Date: Fri, 24 May 2024 08:00:16 +0000 Subject: [PATCH 21/49] Translated using Weblate (French) Currently translated at 99.9% (1019 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/fr/ --- src/main/res/values-fr/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 39731e001..eb03186aa 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -1090,4 +1090,5 @@ Accepter les invitations aux conversations de groupes provenant d\'inconnus Grande police Augmenter la taille de la police dans les bulles de message + Autoriser les messages privés \ No newline at end of file From a2f78b34361a9b25eb023743563ea3e98c8b8006 Mon Sep 17 00:00:00 2001 From: random_r Date: Fri, 24 May 2024 10:08:21 +0000 Subject: [PATCH 22/49] Translated using Weblate (Italian) Currently translated at 100.0% (67 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/it/ --- fastlane/metadata/android/it-IT/changelogs/4211204.txt | 2 ++ fastlane/metadata/android/it-IT/changelogs/4211304.txt | 1 + 2 files changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/4211204.txt create mode 100644 fastlane/metadata/android/it-IT/changelogs/4211304.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/4211204.txt b/fastlane/metadata/android/it-IT/changelogs/4211204.txt new file mode 100644 index 000000000..2e101d8e0 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* Corrette le chiamate che venivano smutate cambiando dispositivi di output +* Esclusi tutti i dispositivi Umidigi dall'integrazione di chiamate diff --git a/fastlane/metadata/android/it-IT/changelogs/4211304.txt b/fastlane/metadata/android/it-IT/changelogs/4211304.txt new file mode 100644 index 000000000..5a83a3a69 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/4211304.txt @@ -0,0 +1 @@ +* Avvia backup come servizio in primo piano per impedire che il servizio si fermi dopo 10 minuti From 44b6fc66d5fd5e9f302141b274e99d7b7d1aab2b Mon Sep 17 00:00:00 2001 From: nautilusx Date: Fri, 24 May 2024 15:11:37 +0000 Subject: [PATCH 23/49] Translated using Weblate (German) Currently translated at 100.0% (67 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/de/ --- fastlane/metadata/android/de-DE/changelogs/4211104.txt | 2 +- fastlane/metadata/android/de-DE/changelogs/4211204.txt | 2 ++ fastlane/metadata/android/de-DE/changelogs/4211304.txt | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/de-DE/changelogs/4211204.txt create mode 100644 fastlane/metadata/android/de-DE/changelogs/4211304.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/4211104.txt b/fastlane/metadata/android/de-DE/changelogs/4211104.txt index 80db37817..fa21b66e5 100644 --- a/fastlane/metadata/android/de-DE/changelogs/4211104.txt +++ b/fastlane/metadata/android/de-DE/changelogs/4211104.txt @@ -1,3 +1,3 @@ * Wiederkehrende Sicherungen planen -* Realme Geräte von der Anrufintegration ausgenommen +* Realme-Geräte von der Anrufintegration ausgenommen * Kleine Designverbesserungen (Chatblasen) diff --git a/fastlane/metadata/android/de-DE/changelogs/4211204.txt b/fastlane/metadata/android/de-DE/changelogs/4211204.txt new file mode 100644 index 000000000..42a529393 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* Anruf wird nicht mehr stummgeschaltet, wenn das Ausgabegerät gewechselt wird +* Umidigi-Geräte von der Anrufintegration ausgenommen diff --git a/fastlane/metadata/android/de-DE/changelogs/4211304.txt b/fastlane/metadata/android/de-DE/changelogs/4211304.txt new file mode 100644 index 000000000..127af8565 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4211304.txt @@ -0,0 +1 @@ +* Sicherung als Vordergrunddienst ausführen, damit der Prozess nicht nach 10 Minuten gestoppt wird From 66a5ff6781ffe726b335e6f7d208ef0c35be3c0f Mon Sep 17 00:00:00 2001 From: Besnik_b Date: Fri, 24 May 2024 09:12:26 +0000 Subject: [PATCH 24/49] Translated using Weblate (Albanian) Currently translated at 100.0% (67 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/sq/ --- fastlane/metadata/android/sq/changelogs/4211304.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/sq/changelogs/4211304.txt diff --git a/fastlane/metadata/android/sq/changelogs/4211304.txt b/fastlane/metadata/android/sq/changelogs/4211304.txt new file mode 100644 index 000000000..9b0191f55 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/4211304.txt @@ -0,0 +1 @@ +* Xhirim Kopjeruajtje si shërbim në prapaskenë, për të parandaluar ndalimin e procesit pas 10 minutash From 2ca7b9eb6c2957be98404530800f0a8761d205e2 Mon Sep 17 00:00:00 2001 From: NameLessGO Date: Sun, 26 May 2024 21:45:28 +0000 Subject: [PATCH 25/49] Translated using Weblate (Portuguese (Brazil)) Currently translated at 89.0% (908 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/pt_BR/ --- src/main/res/values-pt-rBR/strings.xml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index 329dfd8c8..98f8386f3 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -40,7 +40,7 @@ Moderador Participante Visitante - Deseja remover %s da sua lista de contatos? As conversas associadas a esse contato não serão removidas. + Gostaria de remover %s da sua lista de contatos? O chat com este contato não será removido. Deseja bloquear o recebimento de mensagens de %s? Deseja desbloquear o recebimento de mensagens de %s? Bloquear todos os contatos de %s? @@ -78,19 +78,19 @@ Preparando para enviar as imagens Compartilhando arquivos. Por favor, aguarde... Limpar o histórico - Limpa o histórico de conversas + Limpar histórico do chat Deseja excluir todas as mensagens dessa conversa? \n \nAtenção: Isso não afetará mensagens armazenadas em outros dispositivos ou servidores. Excluir arquivo Deseja realmente excluir este arquivo?\n\nAtenção: Isso não excluirá cópias deste arquivo armazenadas em outros dispositivos ou servidores. Selecione o dispositivo - Enviar mensagem não criptografada + Enviar mensagem de texto não criptografada Enviar mensagem Enviar mensagem para %s Enviar mensagem criptografada via v\\OMEMO Novo apelido em uso - Enviar descriptografada + Enviar texto não criptografada Não foi possível descriptografar. Talvez você não tenha a chave privada apropriada. OpenKeychain OpenKeychain para criptografar e descriptografar as mensagens e gerenciar suas chaves públicas.

Ele está licenciado sob a GPLv3+ e está disponível no F-Droid e na Google Play.

(Por favor reinicie o %1$s em seguida).]]>
@@ -163,7 +163,7 @@ Cliente incompatível Erro de fluxo Erro na abertura do fluxo - Descriptografada + Texto não criptografado OTR OpenPGP OMEMO @@ -175,7 +175,7 @@ Tem certeza que deseja remover sua chave pública OpenPGP do seu anúncio de presença?\nSeus contatos não poderão mais enviar mensagens criptografadas com o OpenPGP para você. A chave pública do OpenPGP foi publicada. Habilitar a conta - Se você excluir a sua conta todo o seu histórico de conversas será apagado + Tem certeza de que deseja excluir sua conta? Excluir sua conta apaga todo o seu histórico do chat Gravar voz Endereço XMPP Bloquear endereço XMPP @@ -983,4 +983,13 @@ As chamadas estão desabilitadas ao usar Tor Mudar para vídeo Recusar requisição de mudança para vídeo + Enviar relatórios de erro + Gostaria de remover o favorito de %s e arquivar chat? + Exclua o chat depois + Compartilhar com… + Arquivar chat + Novo chat + Enviar mensagem criptografada + Desconectado + Gostaria de remover o favorito de %s? \ No newline at end of file From 242a2f36432244afa3528238bf6b13c62f78b26d Mon Sep 17 00:00:00 2001 From: NameLessGO Date: Sun, 26 May 2024 21:39:19 +0000 Subject: [PATCH 26/49] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (13 of 13 strings) Translation: Conversations/Android App (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-conversations/pt_BR/ --- src/conversations/res/values-pt-rBR/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/conversations/res/values-pt-rBR/strings.xml b/src/conversations/res/values-pt-rBR/strings.xml index 210d814e1..7e2b18910 100644 --- a/src/conversations/res/values-pt-rBR/strings.xml +++ b/src/conversations/res/values-pt-rBR/strings.xml @@ -4,8 +4,8 @@ Usar o conversations.im Criar uma nova conta Você já possui uma conta XMPP? Esse pode ser o seu caso caso já esteja usando um outro cliente XMPP ou tenha usado o Conversations antes. Caso contrário, você pode criar uma nova conta XMPP agora.\nDica: alguns provedores de e-mail também fornecem contas XMPP. - O XMPP é uma rede de mensageria instantânea independente de provedor. Você pode usar esse cliente com qualquer servidor XMPP que você escolher. -\nEntretanto, para sua conveniência, nós simplificamos o processo de criação de uma conta em conversations.im, um provedor especialmente configurado para se usar com o Conversations. + XMPP é uma rede de mensagens instantâneas independente de provedor. Você pode usar este aplicativo com qualquer servidor XMPP de sua escolha. +\nNo entanto, para sua comodidade, facilitamos criar uma conta em conversas.im; um provedor especificamente adequado para uso com Conversations. Você foi convidado para %1$s. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nAo escolher %1$s como um provedor você conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo. Você foi convidado para %1$s. Um nome de usuário já foi escolhido para você. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nVocê conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo. Seu convite do servidor From f19110e4d24244c89d9ae2f4cfff9e605ba976cf Mon Sep 17 00:00:00 2001 From: alextecplayz Date: Sun, 26 May 2024 14:31:38 +0000 Subject: [PATCH 27/49] Translated using Weblate (Romanian) Currently translated at 100.0% (9 of 9 strings) Translation: Conversations/Android App (Quicksy) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-quicksy/ro/ --- src/quicksy/res/values-ro-rRO/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quicksy/res/values-ro-rRO/strings.xml b/src/quicksy/res/values-ro-rRO/strings.xml index 7dd79ce4a..a2f4fbe04 100644 --- a/src/quicksy/res/values-ro-rRO/strings.xml +++ b/src/quicksy/res/values-ro-rRO/strings.xml @@ -4,9 +4,9 @@ Trimițând datele despre erori ajutați la continuarea dezvoltării aplicației Quicksy Contactele vă sunt anunțate atunci când folosiți Quicksy Pentru a continua să primiți notificări, chiar și când ecranul este oprit, trebuie să adăugați Quicksy în lista de aplicații protejate. - Poză profil Quicksy + Poză de profil Quicksy Quicksy nu este disponibilă în țara dumneavoastră. Nu s-a putut verifica identitatea serverului. Eroare de securitate necunoscută. A expirat timpul de așteptare conexiune server. - + \ No newline at end of file From 627bfae4ab8b9236dda945cd9797604113891b42 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 May 2024 16:31:59 +0200 Subject: [PATCH 28/49] use new xml api for bind 2 --- .../eu/siacs/conversations/AppSettings.java | 24 ++++++++ .../services/XmppConnectionService.java | 7 ++- .../conversations/utils/AccountUtils.java | 12 ++-- .../conversations/xmpp/XmppConnection.java | 28 +++++---- .../android/xmpp/model/bind2/Bind.java | 4 ++ .../android/xmpp/model/bind2/Tag.java | 17 ++++++ .../android/xmpp/model/sasl2/Device.java | 17 ++++++ .../android/xmpp/model/sasl2/Software.java | 17 ++++++ .../android/xmpp/model/sasl2/UserAgent.java | 25 ++++++++ .../services/QuickConversationsService.java | 60 ++++++++----------- 10 files changed, 160 insertions(+), 51 deletions(-) create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind2/Tag.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Device.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Software.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/UserAgent.java diff --git a/src/main/java/eu/siacs/conversations/AppSettings.java b/src/main/java/eu/siacs/conversations/AppSettings.java index 0916d1e1a..4d3931524 100644 --- a/src/main/java/eu/siacs/conversations/AppSettings.java +++ b/src/main/java/eu/siacs/conversations/AppSettings.java @@ -10,6 +10,8 @@ import com.google.common.base.Strings; +import java.security.SecureRandom; + public class AppSettings { public static final String KEEP_FOREGROUND_SERVICE = "enable_foreground_service"; @@ -45,6 +47,7 @@ public class AppSettings { public static final String LARGE_FONT = "large_font"; private static final String ACCEPT_INVITES_FROM_STRANGERS = "accept_invites_from_strangers"; + private static final String INSTALLATION_ID = "im.conversations.android.install_id"; private final Context context; @@ -140,4 +143,25 @@ public void setSendCrashReports(boolean value) { public boolean isRequireChannelBinding() { return getBooleanPreference(REQUIRE_CHANNEL_BINDING, R.bool.require_channel_binding); } + + public synchronized long getInstallationId() { + final var sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + final long existing = sharedPreferences.getLong(INSTALLATION_ID, 0); + if (existing != 0) { + return existing; + } + final var secureRandom = new SecureRandom(); + final var installationId = secureRandom.nextLong(); + sharedPreferences.edit().putLong(INSTALLATION_ID, installationId).apply(); + return installationId; + } + + public synchronized void resetInstallationId() { + final var secureRandom = new SecureRandom(); + final var installationId = secureRandom.nextLong(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putLong(INSTALLATION_ID, installationId) + .apply(); + } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 0cf6fbe24..b7935c3b5 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -229,6 +229,8 @@ public class XmppConnectionService extends Service { public DatabaseBackend databaseBackend; private final ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor("ContactMerger"); private long mLastActivity = 0; + + private final AppSettings appSettings = new AppSettings(this); private final FileBackend fileBackend = new FileBackend(this); private MemorizingTrustManager mMemorizingTrustManager; private final NotificationService mNotificationService = new NotificationService(this); @@ -483,6 +485,10 @@ public OpenPgpApi getOpenPgpApi() { } } + public AppSettings getAppSettings() { + return this.appSettings; + } + public FileBackend getFileBackend() { return this.fileBackend; } @@ -4633,7 +4639,6 @@ public void setMemorizingTrustManager(MemorizingTrustManager trustManager) { public void updateMemorizingTrustManager() { final MemorizingTrustManager trustManager; - final var appSettings = new AppSettings(this); if (appSettings.isTrustSystemCAStore()) { trustManager = new MemorizingTrustManager(getApplicationContext()); } else { diff --git a/src/main/java/eu/siacs/conversations/utils/AccountUtils.java b/src/main/java/eu/siacs/conversations/utils/AccountUtils.java index fe70241f9..4b2c2957f 100644 --- a/src/main/java/eu/siacs/conversations/utils/AccountUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/AccountUtils.java @@ -38,23 +38,27 @@ public static boolean hasEnabledAccounts(final XmppConnectionService service) { return false; } - public static String publicDeviceId(final Account account) { + public static String publicDeviceId(final Account account, final long installationId) { final UUID uuid; try { uuid = UUID.fromString(account.getUuid()); } catch (final IllegalArgumentException e) { return account.getUuid(); } + return createUuid4(uuid.getMostSignificantBits(), installationId).toString(); + } + + public static UUID createUuid4(long mostSigBits, long leastSigBits) { final byte[] bytes = Bytes.concat( - Longs.toByteArray(uuid.getLeastSignificantBits()), - Longs.toByteArray(uuid.getLeastSignificantBits())); + Longs.toByteArray(mostSigBits), + Longs.toByteArray(leastSigBits)); bytes[6] &= 0x0f; /* clear version */ bytes[6] |= 0x40; /* set to version 4 */ bytes[8] &= 0x3f; /* clear variant */ bytes[8] |= 0x80; /* set to IETF variant */ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); - return new UUID(byteBuffer.getLong(), byteBuffer.getLong()).toString(); + return new UUID(byteBuffer.getLong(), byteBuffer.getLong()); } public static List getEnabledAccounts(final XmppConnectionService service) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index a38266266..9a51f9e51 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableList; import eu.siacs.conversations.AppSettings; +import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.XmppDomainVerifier; @@ -78,6 +79,7 @@ import im.conversations.android.xmpp.model.sasl.Success; import im.conversations.android.xmpp.model.sasl2.Authenticate; import im.conversations.android.xmpp.model.sasl2.Authentication; +import im.conversations.android.xmpp.model.sasl2.UserAgent; import im.conversations.android.xmpp.model.sm.Ack; import im.conversations.android.xmpp.model.sm.Enable; import im.conversations.android.xmpp.model.sm.Enabled; @@ -195,7 +197,7 @@ public class XmppConnection implements Runnable { public XmppConnection(final Account account, final XmppConnectionService service) { this.account = account; this.mXmppConnectionService = service; - this.appSettings = new AppSettings(mXmppConnectionService.getApplicationContext()); + this.appSettings = mXmppConnectionService.getAppSettings(); this.presenceListener = new PresenceParser(service, account); this.unregisteredIqListener = new IqParser(service, account); this.messageListener = new MessageParser(service, account); @@ -1664,15 +1666,15 @@ private Authenticate generateAuthenticationRequest( if (!Strings.isNullOrEmpty(firstMessage)) { authenticate.addChild("initial-response").setContent(firstMessage); } - final Element userAgent = authenticate.addChild("user-agent"); - userAgent.setAttribute("id", AccountUtils.publicDeviceId(account)); - userAgent - .addChild("software") - .setContent(mXmppConnectionService.getString(R.string.app_name)); + final var userAgent = + authenticate.addExtension( + new UserAgent( + AccountUtils.publicDeviceId( + account, appSettings.getInstallationId()))); + userAgent.setSoftware( + String.format("%s %s", BuildConfig.APP_NAME, BuildConfig.VERSION_NAME)); if (!PhoneHelper.isEmulator()) { - userAgent - .addChild("device") - .setContent(String.format("%s %s", Build.MANUFACTURER, Build.MODEL)); + userAgent.setDevice(String.format("%s %s", Build.MANUFACTURER, Build.MODEL)); } // do not include bind if 'inlineStreamManagement' is missing and we have a streamId // (because we would rather just do a normal SM/resume) @@ -1698,7 +1700,7 @@ private Authenticate generateAuthenticationRequest( private Bind generateBindRequest(final Collection bindFeatures) { Log.d(Config.LOGTAG, "inline bind features: " + bindFeatures); final var bind = new Bind(); - bind.addChild("tag").setContent(mXmppConnectionService.getString(R.string.app_name)); + bind.setTag(BuildConfig.APP_NAME); if (bindFeatures.contains(Namespace.CARBONS)) { bind.addExtension(new im.conversations.android.xmpp.model.carbons.Enable()); } @@ -2292,6 +2294,10 @@ private void processStreamError(final Tag currentTag) throws IOException { return; } if (streamError.hasChild("conflict")) { + final var loginInfo = this.loginInfo; + if (loginInfo != null && loginInfo.saslVersion == SaslMechanism.Version.SASL_2) { + this.appSettings.resetInstallationId(); + } account.setResource(createNewResource()); Log.d( Config.LOGTAG, @@ -2299,7 +2305,7 @@ private void processStreamError(final Tag currentTag) throws IOException { + ": switching resource due to conflict (" + account.getResource() + ")"); - throw new IOException(); + throw new IOException("Closed stream due to resource conflict"); } else if (streamError.hasChild("host-unknown")) { throw new StateChangingException(Account.State.HOST_UNKNOWN); } else if (streamError.hasChild("policy-violation")) { diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java index bff72a06b..3af144105 100644 --- a/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java @@ -21,4 +21,8 @@ public Collection getInlineFeatures() { final var inline = getInline(); return inline == null ? Collections.emptyList() : inline.getExtensions(Feature.class); } + + public void setTag(final String tag) { + this.addExtension(new Tag(tag)); + } } diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Tag.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Tag.java new file mode 100644 index 000000000..5fac1d9e3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Tag.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.bind2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Tag extends Extension { + + public Tag() { + super(Tag.class); + } + + public Tag(final String tag) { + this(); + setContent(tag); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Device.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Device.java new file mode 100644 index 000000000..2594f5874 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Device.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.sasl2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Device extends Extension { + + public Device() { + super(Device.class); + } + + public Device(final String device) { + this(); + this.setContent(device); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Software.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Software.java new file mode 100644 index 000000000..8685ed3ff --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Software.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.sasl2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Software extends Extension { + + public Software() { + super(Software.class); + } + + public Software(final String software) { + this(); + this.setContent(software); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/UserAgent.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/UserAgent.java new file mode 100644 index 000000000..bb2a0c68c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/UserAgent.java @@ -0,0 +1,25 @@ +package im.conversations.android.xmpp.model.sasl2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class UserAgent extends Extension { + + public UserAgent() { + super(UserAgent.class); + } + + public UserAgent(final String userAgentId) { + this(); + this.setAttribute("id", userAgentId); + } + + public void setSoftware(final String software) { + this.addExtension(new Software(software)); + } + + public void setDevice(final String device) { + this.addExtension(new Device(device)); + } +} diff --git a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java index 8cf8fe202..d2af65f83 100644 --- a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java +++ b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java @@ -5,16 +5,36 @@ import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.SystemClock; -import android.preference.PreferenceManager; import android.util.Log; import com.google.common.collect.ImmutableMap; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.android.PhoneNumberContact; +import eu.siacs.conversations.crypto.TrustManagers; +import eu.siacs.conversations.crypto.sasl.Plain; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Entry; +import eu.siacs.conversations.http.HttpConnectionManager; +import eu.siacs.conversations.utils.AccountUtils; +import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.PhoneNumberUtilWrapper; +import eu.siacs.conversations.utils.SerialSingleThreadExecutor; +import eu.siacs.conversations.utils.SmsRetrieverWrapper; +import eu.siacs.conversations.utils.TLSSocketFactory; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.Jid; + +import im.conversations.android.xmpp.model.stanza.Iq; + +import io.michaelrocks.libphonenumber.android.Phonenumber; + import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; @@ -38,7 +58,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.WeakHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -52,26 +71,6 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.android.PhoneNumberContact; -import eu.siacs.conversations.crypto.TrustManagers; -import eu.siacs.conversations.crypto.sasl.Plain; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Contact; -import eu.siacs.conversations.entities.Entry; -import eu.siacs.conversations.http.HttpConnectionManager; -import eu.siacs.conversations.utils.AccountUtils; -import eu.siacs.conversations.utils.CryptoHelper; -import eu.siacs.conversations.utils.PhoneNumberUtilWrapper; -import eu.siacs.conversations.utils.SerialSingleThreadExecutor; -import eu.siacs.conversations.utils.SmsRetrieverWrapper; -import eu.siacs.conversations.utils.TLSSocketFactory; -import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.Jid; -import im.conversations.android.xmpp.model.stanza.Iq; -import io.michaelrocks.libphonenumber.android.Phonenumber; - public class QuickConversationsService extends AbstractQuickConversationsService { @@ -88,8 +87,6 @@ public class QuickConversationsService extends AbstractQuickConversationsService private static final String BASE_URL = "https://" + API_DOMAIN; - private static final String INSTALLATION_ID = "eu.siacs.conversations.installation-id"; - private final Set mOnVerificationRequested = Collections.newSetFromMap(new WeakHashMap<>()); private final Set mOnVerification = Collections.newSetFromMap(new WeakHashMap<>()); @@ -308,16 +305,9 @@ private void setHeader(HttpURLConnection connection) { } private String getInstallationId() { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(service); - String id = preferences.getString(INSTALLATION_ID, null); - if (id != null) { - return id; - } else { - id = UUID.randomUUID().toString(); - preferences.edit().putString(INSTALLATION_ID, id).apply(); - return id; - } - + final var appSettings = service.getAppSettings(); + final long installationId = appSettings.getInstallationId(); + return AccountUtils.createUuid4(installationId, installationId).toString(); } private int getApiErrorCode(final Exception e) { From 1e3930a8267a530bc25c01acb6cba3b0a5c966fc Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 May 2024 16:35:09 +0200 Subject: [PATCH 29/49] use proper theme colors for new key icon --- src/main/res/drawable/ic_new_releases_24dp.xml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/res/drawable/ic_new_releases_24dp.xml b/src/main/res/drawable/ic_new_releases_24dp.xml index dd9a4b95d..b0ffc3f42 100644 --- a/src/main/res/drawable/ic_new_releases_24dp.xml +++ b/src/main/res/drawable/ic_new_releases_24dp.xml @@ -1,5 +1,10 @@ - - + + From 69fc07db631567a3ba5da735c5ec6301b8246719 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 May 2024 16:43:19 +0200 Subject: [PATCH 30/49] exclude early oppo devices from call integration --- .../eu/siacs/conversations/services/CallIntegration.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index 121c2cae6..15bc3a9b2 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegration.java @@ -531,6 +531,12 @@ private static boolean isDeviceModelSupported() { && Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { return false; } + // we are relatively sure that old Oppo devices are broken too. We get reports of 'number + // not sent' from Oppo R15x (Android 10) + if ("OPPO".equalsIgnoreCase(Build.MANUFACTURER) + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + return false; + } // we only know of one Umidigi device (BISON_GT2_5G) that doesn't work (audio is not being // routed properly) However with those devices being extremely rare it's impossible to gauge // how many might be effected and no Naomi Wu around to clarify with the company directly From 21c5da33080d2fe2440813cfffcfceafce56a6c4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 May 2024 16:53:42 +0200 Subject: [PATCH 31/49] catch rare audio source exception (when permissions are lacking) in recorder --- .../java/eu/siacs/conversations/ui/RecordingActivity.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java index 64446e930..231a8600d 100644 --- a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java @@ -105,7 +105,12 @@ protected void onStop() { private boolean startRecording() { mRecorder = new MediaRecorder(); - mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + try { + mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + } catch (final RuntimeException e) { + Log.e(Config.LOGTAG,"could not set audio source", e); + return false; + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { mRecorder.setPrivacySensitive(true); } From bcf5e1916a9d8a235e9b44085340fa6592b90dda Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 May 2024 17:02:18 +0200 Subject: [PATCH 32/49] version bump to 2.16.3 + changelog --- CHANGELOG.md | 5 +++++ build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/4211404.txt | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/4211404.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index cdcc3b23f..80e79abe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Version 2.16.3 + +* exclude older Oppo devices from call integration +* various bug fixes + ### Version 2.16.2 * Run Backup as foreground service to prevent process being stopped after 10 minutes diff --git a/build.gradle b/build.gradle index 5443d33a9..04da16f85 100644 --- a/build.gradle +++ b/build.gradle @@ -104,8 +104,8 @@ android { defaultConfig { minSdkVersion 23 targetSdkVersion 34 - versionCode 42113 - versionName "2.16.2" + versionCode 42114 + versionName "2.16.3" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/4211404.txt b/fastlane/metadata/android/en-US/changelogs/4211404.txt new file mode 100644 index 000000000..8c3bfc7eb --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* exclude older Oppo devices from call integration +* various bug fixes From f0ec9006f0257fcfa1998cec6e8dd2cb12e1abec Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 2 Jun 2024 17:01:17 +0200 Subject: [PATCH 33/49] fix rare NPE race condition --- .../conversations/xmpp/XmppConnection.java | 42 +++++++++---------- .../xmpp/model/AuthenticationRequest.java | 13 ++++++ .../android/xmpp/model/sasl/Auth.java | 9 +++- .../xmpp/model/sasl2/Authenticate.java | 10 ++++- 4 files changed, 49 insertions(+), 25 deletions(-) create mode 100644 src/main/java/im/conversations/android/xmpp/model/AuthenticationRequest.java diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 9a51f9e51..38194d5cd 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -63,6 +63,7 @@ import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived; +import im.conversations.android.xmpp.model.AuthenticationRequest; import im.conversations.android.xmpp.model.AuthenticationStreamFeature; import im.conversations.android.xmpp.model.StreamElement; import im.conversations.android.xmpp.model.bind2.Bind; @@ -1514,14 +1515,15 @@ private void authenticate(final SaslMechanism.Version version) throws IOExceptio final String firstMessage = saslMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)); final boolean usingFast = SaslMechanism.hashedToken(saslMechanism); - final StreamElement authenticate; + final AuthenticationRequest authenticate; + final LoginInfo loginInfo; if (version == SaslMechanism.Version.SASL) { authenticate = new Auth(); if (!Strings.isNullOrEmpty(firstMessage)) { authenticate.setContent(firstMessage); } quickStartAvailable = false; - this.loginInfo = new LoginInfo(saslMechanism, version, Collections.emptyList()); + loginInfo = new LoginInfo(saslMechanism, version, Collections.emptyList()); } else if (version == SaslMechanism.Version.SASL_2) { final Authentication authentication = (Authentication) authElement; final var inline = authentication.getInline(); @@ -1552,7 +1554,7 @@ private void authenticate(final SaslMechanism.Version version) throws IOExceptio return; } } - this.loginInfo = new LoginInfo(saslMechanism, version, bindFeatures); + loginInfo = new LoginInfo(saslMechanism, version, bindFeatures); this.hashTokenRequest = hashTokenRequest; authenticate = generateAuthenticationRequest( @@ -1560,7 +1562,7 @@ private void authenticate(final SaslMechanism.Version version) throws IOExceptio } else { throw new AssertionError("Missing implementation for " + version); } - + this.loginInfo = loginInfo; if (account.setOption(Account.OPTION_QUICKSTART_AVAILABLE, quickStartAvailable)) { mXmppConnectionService.databaseBackend.updateAccount(account); } @@ -1571,8 +1573,8 @@ private void authenticate(final SaslMechanism.Version version) throws IOExceptio + ": Authenticating with " + version + "/" - + LoginInfo.mechanism(this.loginInfo).getMechanism()); - authenticate.setAttribute("mechanism", LoginInfo.mechanism(this.loginInfo).getMechanism()); + + LoginInfo.mechanism(loginInfo).getMechanism()); + authenticate.setMechanism(LoginInfo.mechanism(loginInfo)); synchronized (this.mStanzaQueue) { this.stanzasSentBeforeAuthentication = this.stanzasSent; tagWriter.writeElement(authenticate); @@ -1650,13 +1652,13 @@ private void checkAssignedDomain(final Jid jid) { } } - private Authenticate generateAuthenticationRequest( + private AuthenticationRequest generateAuthenticationRequest( final String firstMessage, final boolean usingFast) { return generateAuthenticationRequest( firstMessage, usingFast, null, Bind2.QUICKSTART_FEATURES, true); } - private Authenticate generateAuthenticationRequest( + private AuthenticationRequest generateAuthenticationRequest( final String firstMessage, final boolean usingFast, final HashedToken.Mechanism hashedTokenRequest, @@ -1909,13 +1911,15 @@ private void sendBindRequest() { } clearIqCallbacks(); if (account.getJid().isBareJid()) { - account.setResource(this.createNewResource()); + account.setResource(createNewResource()); } else { fixResource(mXmppConnectionService, account); } final Iq iq = new Iq(Iq.Type.SET); final String resource = - Config.USE_RANDOM_RESOURCE_ON_EVERY_BIND ? nextRandomId() : account.getResource(); + Config.USE_RANDOM_RESOURCE_ON_EVERY_BIND + ? CryptoHelper.random(9) + : account.getResource(); iq.addExtension(new im.conversations.android.xmpp.model.bind.Bind()).setResource(resource); this.sendUnmodifiedIqPacket( iq, @@ -2381,11 +2385,11 @@ private boolean establishStream(final SSLSockets.Version sslVersion) SaslMechanism.Version.SASL_2, Bind2.QUICKSTART_FEATURES); final boolean usingFast = quickStartMechanism instanceof HashedToken; - final StreamElement authenticate = + final AuthenticationRequest authenticate = generateAuthenticationRequest( quickStartMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)), usingFast); - authenticate.setAttribute("mechanism", quickStartMechanism.getMechanism()); + authenticate.setMechanism(quickStartMechanism); sendStartStream(true, false); synchronized (this.mStanzaQueue) { this.stanzasSentBeforeAuthentication = this.stanzasSent; @@ -2416,16 +2420,8 @@ private void sendStartStream(final boolean from, final boolean flush) throws IOE tagWriter.writeTag(stream, flush); } - private String createNewResource() { - return mXmppConnectionService.getString(R.string.app_name) + '.' + nextRandomId(true); - } - - private String nextRandomId() { - return nextRandomId(false); - } - - private String nextRandomId(final boolean s) { - return CryptoHelper.random(s ? 3 : 9); + private static String createNewResource() { + return String.format("%s.%s", BuildConfig.APP_NAME, CryptoHelper.random(3)); } public String sendIqPacket(final Iq packet, final Consumer callback) { @@ -2437,7 +2433,7 @@ public synchronized String sendUnmodifiedIqPacket( final Iq packet, final Consumer callback, boolean force) { // TODO if callback != null verify that type is get or set if (packet.getId() == null) { - packet.setAttribute("id", nextRandomId()); + packet.setId(CryptoHelper.random(9)); } if (callback != null) { synchronized (this.packetCallbacks) { diff --git a/src/main/java/im/conversations/android/xmpp/model/AuthenticationRequest.java b/src/main/java/im/conversations/android/xmpp/model/AuthenticationRequest.java new file mode 100644 index 000000000..b2122ab86 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/AuthenticationRequest.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model; + +import eu.siacs.conversations.crypto.sasl.SaslMechanism; + +public abstract class AuthenticationRequest extends StreamElement{ + + + protected AuthenticationRequest(Class clazz) { + super(clazz); + } + + public abstract void setMechanism(final SaslMechanism mechanism); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java index 668d10d80..e9dd801f2 100644 --- a/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java @@ -1,12 +1,19 @@ package im.conversations.android.xmpp.model.sasl; +import eu.siacs.conversations.crypto.sasl.SaslMechanism; import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.AuthenticationRequest; import im.conversations.android.xmpp.model.StreamElement; @XmlElement -public class Auth extends StreamElement { +public class Auth extends AuthenticationRequest { public Auth() { super(Auth.class); } + + @Override + public void setMechanism(final SaslMechanism mechanism) { + this.setAttribute("mechanism", mechanism.getMechanism()); + } } diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java index 7869bf010..c709a779d 100644 --- a/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java @@ -1,12 +1,20 @@ package im.conversations.android.xmpp.model.sasl2; +import eu.siacs.conversations.crypto.sasl.SaslMechanism; +import eu.siacs.conversations.xmpp.XmppConnection; import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.AuthenticationRequest; import im.conversations.android.xmpp.model.StreamElement; @XmlElement -public class Authenticate extends StreamElement { +public class Authenticate extends AuthenticationRequest { public Authenticate() { super(Authenticate.class); } + + @Override + public void setMechanism(final SaslMechanism mechanism) { + this.setAttribute("mechanism", mechanism.getMechanism()); + } } From 4e09d5c44137033c64a56189b2931d9130e0f0dd Mon Sep 17 00:00:00 2001 From: codimp Date: Sun, 2 Jun 2024 15:29:05 +0000 Subject: [PATCH 34/49] Translated using Weblate (French) Currently translated at 100.0% (1020 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/fr/ --- src/main/res/values-fr/strings.xml | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index eb03186aa..f2b15b3ad 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -5,7 +5,7 @@ Gérer le compte Détails du contact Détails du groupe - Détails du canal + Détails du salon Ajouter un compte Modifier le nom Ajouter au carnet d\'adresses @@ -14,8 +14,8 @@ Débloquer le contact Bloquer le domaine Débloquer le domaine - Bloquer le participant - Débloquer le participant + Bloquer le·a participant·e + Débloquer le·a participant·e Gestion des comptes Paramètres Choisir un contact @@ -35,11 +35,11 @@ Message chiffré avec OpenPGP Cet identifiant est déjà utilisé Identifiant non valide - Administrateur + Administrateur·ice Propriétaire - Modérateur - Participant - Visiteur + Modérateur·ice + Participant·e + Visiteur·ice Voulez-vous supprimer %s de votre liste de contacts ? Les conversations associées à ce contact ne seront pas supprimées. Voulez-vous bloquer %s pour l\'empêcher de vous envoyer des messages ? Voulez-vous débloquer %s et lui permettre de vous envoyer des messages ? @@ -95,15 +95,15 @@ Envoyer en clair Échec du déchiffrement. Avez-vous la bonne clé privée ? OpenKeychain - OpenKeychain pour chiffrer et déchiffrer les messages et pour gérer vos clés publiques.\n\nOpenKeychain est sous licence GPLv3 et est disponible sur F-Droid et Google Play.\n\n(Veuillez redémarrer %1$s après l\'installation de l\'application.)]]> + %1$s utilise <b>OpenKeychain</b> pour chiffrer et déchiffrer les messages et pour gérer vos clés publiques.<br><br>OpenKeychain est sous licence GPLv3 et est disponible sur F-Droid et Google Play.<br><br><small>(Veuillez redémarrer %1$s après l\'installation de l\'application.)</small> Redémarrer Installer Veuillez installer OpenKeychain Proposition… Patientez… - Aucune clé OpenPGP trouvée. + Aucune clé OpenPGP trouvée Impossible de chiffrer vos messages car votre contact n\'a pas communiqué sa clé publique.\n\nDemandez-lui de configurer OpenPGP. - Aucune clé OpenPGP n\'a été trouvée. + Aucune clé OpenPGP n\'a été trouvée Impossible de chiffrer votre message car vos contacts ne communiquent pas leur clé publique.\n\nDemandez-leur de configurer OpenPGP. Général Accepter les fichiers @@ -137,7 +137,7 @@ Demander les màj de disponibilité Choisir une image Prendre une photo - Accepter par avance les demandes de publication. + Accepter par avance les demandes de publication Le fichier choisi n\'est pas une image Impossible de convertir l\'image Impossible de trouver le fichier @@ -171,7 +171,7 @@ OMEMO Supprimer Désactiver temporairement - Publier un avatar + Publier une image de profil Publier la clé publique OpenPGP Supprimer la clé publique OpenPGP Êtes-vous sûr de vouloir supprimer votre clé publique OpenPGP de votre annonce de présence ?\nVos contacts ne pourront plus vous envoyer de message chiffrés avec OpenPGP. @@ -181,10 +181,10 @@ Enregistrer un son Adresse XMPP Bloquer l\'adresse XMPP - nom@exemple.com + nom@example.com Mot de passe Ce n\'est pas une adresse XMPP valide - Plus de mémoire disponible. L\'image est trop volumineuse. + Plus de mémoire disponible. L\'image est trop volumineuse Voulez-vous ajouter %s à votre carnet d\'adresses ? Infos sur le serveur XEP-0313 : MAM @@ -208,7 +208,7 @@ en ligne hier en ligne il y a %d jours Message chiffré. Veuillez installer OpenKeychain pour le déchiffrer. - Nouveaux messages chiffrés avec OpenPGP détectés. + Nouveaux messages chiffrés avec OpenPGP détectés ID de clé OpenPGP Empreinte OMEMO v\\Empreinte OMEMO @@ -228,12 +228,12 @@ Sélectionner Le contact existe déjà Rejoindre - canal@conference.example.com/surnom - canal@conference.example.com + salon@conference.example.com/surnom + salon@conference.example.com Enregistrer comme favori Supprimer le favori Détruire le groupe - Détruire le canal + Détruire le salon Voulez-vous vraiment détruire ce groupe ?\n\nAvertissement : le groupe sera complètement supprimé du serveur. Êtes-vous sûr de vouloir détruire ce canal public \? \n @@ -1062,7 +1062,7 @@ Couleurs systèmes (Material You) La découverte de canaux utilise un service de tierce partie appelé <a href=https://search.jabber.network>search.jabber.network</a>.<br><br> Utiliser cette fonctionnalité va transmettre votre adresse IP et votre recherche à ce service. Voir leur <a href=https://search.jabber.network/privacy>Politique de confidentialité</a> pour plus d\'informations. Archiver la conversation - Archiver cette conversation + Supprimer cette conversation ensuite Envoyer un message chiffré Conversation archivée Se rendre à la conversation From 17a65af9c85ab6c26a518f2998d7ee71f7b685b1 Mon Sep 17 00:00:00 2001 From: codimp Date: Sat, 1 Jun 2024 16:41:36 +0000 Subject: [PATCH 35/49] Translated using Weblate (French) Currently translated at 100.0% (13 of 13 strings) Translation: Conversations/Android App (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-conversations/fr/ --- src/conversations/res/values-fr/strings.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/conversations/res/values-fr/strings.xml b/src/conversations/res/values-fr/strings.xml index 68de25b54..730443ae6 100644 --- a/src/conversations/res/values-fr/strings.xml +++ b/src/conversations/res/values-fr/strings.xml @@ -3,7 +3,8 @@ Choisissez votre fournisseur XMPP Utiliser conversations.im Créer un nouveau compte - Avez-vous déjà un compte XMPP ? Cela peut être le cas si vous utilisez déjà un autre client XMPP ou si vous avez déjà utilisé Conversations auparavant. Sinon, vous pouvez créer un nouveau compte XMPP dès maintenant.\nRemarque : Certains fournisseurs de messagerie proposent également des comptes XMPP. + Avez-vous déjà un compte XMPP ? Cela peut être le cas si vous utilisez déjà un autre client XMPP ou si vous avez déjà utilisé Conversations auparavant. Sinon, vous pouvez créer un nouveau compte XMPP dès maintenant. +\nRemarque : Certains fournisseurs mail proposent également des comptes XMPP. XMPP est un réseau de messagerie instantanée indépendant du fournisseur. Vous pouvez utiliser cette application avec n’importe quel serveur XMPP de votre choix. \nToutefois, pour votre commodité, nous avons facilité la création d’un compte sur conversations.im ; un fournisseur spécialement conçu pour Conversations. Vous avez été invité à %1$s. Nous allons vous guider à travers le processus de création d’un compte.\nEn choisissant %1$s comme fournisseur, vous pourrez communiquer avec les utilisateurs des autres fournisseurs en leur donnant votre adresse XMPP complète. @@ -11,7 +12,7 @@ Votre invitation au serveur 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. + Si votre contact se trouve près de vous, il peut aussi scanner le code ci-dessous pour accepter votre invitation. Rejoignez %1$s et discutez avec moi : %2$s Partager une invitation avec … \ No newline at end of file From dc859e8b5fbb96ee852becc6e8bb4fdde2c135bc Mon Sep 17 00:00:00 2001 From: codimp Date: Sat, 1 Jun 2024 16:06:00 +0000 Subject: [PATCH 36/49] Translated using Weblate (French) Currently translated at 100.0% (2 of 2 strings) Translation: Conversations/App Store Metadata (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata-conversations/fr/ --- .../android/fr-FR/full_description.txt | 21 ++++++++++--------- .../android/fr-FR/short_description.txt | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/conversations/fastlane/metadata/android/fr-FR/full_description.txt b/src/conversations/fastlane/metadata/android/fr-FR/full_description.txt index 653bdec10..12cf31963 100644 --- a/src/conversations/fastlane/metadata/android/fr-FR/full_description.txt +++ b/src/conversations/fastlane/metadata/android/fr-FR/full_description.txt @@ -1,4 +1,4 @@ -Facile à utiliser, fiable, respectueux de votre batterie. Prend en charge les images, les conversations de groupe et le chiffrement de bout-en-bout. +Facile à utiliser, fiable, respectueux de votre batterie. Prend en charge les images, les conversations de groupe et le chiffrement de bout en bout. Principes de conception : @@ -8,6 +8,7 @@ Principes de conception : * Nécessiter le moins de permissions possible Fonctionnalités : + * Chiffrement de bout-en-bout avec au choix, OMEMO ou OpenPGP * Envoi et réception d'images * Appels audio et vidéo chiffrés (DTLS-SRTP) @@ -27,12 +28,12 @@ Conversations fonctionne avec n'importe quel serveur XMPP. Cependant XMPP est un Ces XEP sont actuellement : -* XEP-0065: SOCKS5 Bytestreams (ou mod_proxy65). Sera utilisé pour transférer des fichiers si les deux correspondants sont derrière un pare-feu (NAT). -* XEP-0163: Personal Eventing Protocol pour les avatars -* XEP-0191: Blocking Command vous permet de mettre des spammeurs sur liste noire ou bloquer des contacts sans les retirer de vos contacts. -* XEP-0198: Stream Management permet à XMPP de survivre à des petites pannes de réseau et aux changements de la connexion TCP sous-jacente. -* XEP-0280: Message Carbons qui synchronise automatiquement les messages que vous envoyez à votre client de bureau et vous permet ainsi de passer sans heurt de votre client mobile à votre client de bureau et inversement dans une conversation. -* XEP-0237: Roster Versioning principalement pour économiser de la bande passante sur les connexions mobiles de mauvaise qualité. -* XEP-0313: Message Archive Management synchronise l'historique des messages avec le serveur. Retrouvez des messages qui ont été envoyés pendant que Conversations était hors ligne. -* XEP-0352: Client State Indication fait savoir au serveur si Conversations est ou n'est pas en arrière-plan. Permet au serveur d'économiser de la bande passante en différant des paquets non importants. -* XEP-0363: HTTP File Upload vous permet de partager des fichiers dans les conférences et avec des contacts hors-ligne. Nécessite un composant supplémentaire sur votre serveur. +* XEP-0065 : SOCKS5 Bytestreams (ou mod_proxy65). Sera utilisé pour transférer des fichiers si les deux correspondants sont derrière un pare-feu (NAT). +* XEP-0163 : Personal Eventing Protocol pour les avatars +* XEP-0191 : Blocking Command vous permet de mettre des spammeurs sur liste noire ou bloquer des contacts sans les retirer de vos contacts. +* XEP-0198 : Stream Management permet à XMPP de survivre à des petites pannes de réseau et aux changements de la connexion TCP sous-jacente. +* XEP-0280 : Message Carbons qui synchronise automatiquement les messages que vous envoyez à votre client de bureau et vous permet ainsi de passer sans heurt de votre client mobile à votre client de bureau et inversement dans une conversation. +* XEP-0237 : Roster Versioning principalement pour économiser de la bande passante sur les connexions mobiles de mauvaise qualité. +* XEP-0313 : Message Archive Management synchronise l'historique des messages avec le serveur. Retrouvez des messages qui ont été envoyés pendant que Conversations était hors ligne. +* XEP-0352 : Client State Indication fait savoir au serveur si Conversations est ou n'est pas en arrière-plan. Permet au serveur d'économiser de la bande passante en différant des paquets non importants. +* XEP-0363 : HTTP File Upload vous permet de partager des fichiers dans les conférences et avec des contacts hors-ligne. Nécessite un composant supplémentaire sur votre serveur. diff --git a/src/conversations/fastlane/metadata/android/fr-FR/short_description.txt b/src/conversations/fastlane/metadata/android/fr-FR/short_description.txt index b4ae66d63..072c6ed1a 100644 --- a/src/conversations/fastlane/metadata/android/fr-FR/short_description.txt +++ b/src/conversations/fastlane/metadata/android/fr-FR/short_description.txt @@ -1 +1 @@ -Messagerie instantanée XMPP chiffrée, facile à utiliser avec votre appareil mobile +Messagerie instantanée XMPP chiffrée et facile avec votre appareil mobile From ec49979e275337da7586e77d85976c26b2747d75 Mon Sep 17 00:00:00 2001 From: codimp Date: Sat, 1 Jun 2024 16:21:56 +0000 Subject: [PATCH 37/49] Translated using Weblate (French) Currently translated at 100.0% (2 of 2 strings) Translation: Conversations/App Store Metadata (Quicksy) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata-quicksy/fr/ --- .../metadata/android/fr-FR/full_description.txt | 14 ++++++++++++++ .../metadata/android/fr-FR/short_description.txt | 1 + 2 files changed, 15 insertions(+) create mode 100644 src/quicksy/fastlane/metadata/android/fr-FR/full_description.txt create mode 100644 src/quicksy/fastlane/metadata/android/fr-FR/short_description.txt diff --git a/src/quicksy/fastlane/metadata/android/fr-FR/full_description.txt b/src/quicksy/fastlane/metadata/android/fr-FR/full_description.txt new file mode 100644 index 000000000..c3ba436ff --- /dev/null +++ b/src/quicksy/fastlane/metadata/android/fr-FR/full_description.txt @@ -0,0 +1,14 @@ +Quicksy est un spin-off du client populaire Jabber/XMPP Conversations avec découverte automatique des contacts. + +Vous vous inscrivez avec votre numéro de téléphone et Quicksy va automatiquement — en se basant sur les numéros de votre carnet de contacts — vous suggérer des contacts. + +Sous le capot Quicksy est un client Jabber à part entière qui vous permet de communiquer avec n'importe quel utilisateur·ice sur n'importe quel serveur public fédéré. De même, les utilisateur·ices de Quicksy peuvent être contactés de l'extérieur simplement en ajoutant +numéro-de-téléphone@quicksy.im à votre liste de contacts. + +Outre la synchronisation des contacts, l'interface utilisateur·ice est délibérément aussi proche que possible de Conversations. Cela permet aux utilisateur·ices de migrer éventuellement de Quicksy vers Conversations sans avoir à réapprendre le fonctionnement de l'application. + +Les contacts suggérés inclus d'autres utilisateur·ices Quicksy et des utilisateur·ices Jabber/XMPP classiques qui ont entré leur identifiant Jabber dans le répertoire Quicksy (https://quicksy.im/#get-listed). + +NOTE : Pour ajouter (https://quicksy.im/enter/) votre identifiant Jabber dans le répertoire +Quicksy des frais d'inscription uniques sont requis. + +Lisez la politique de confidentialité (https://quicksy.im/#privacy) pour plus d'information. diff --git a/src/quicksy/fastlane/metadata/android/fr-FR/short_description.txt b/src/quicksy/fastlane/metadata/android/fr-FR/short_description.txt new file mode 100644 index 000000000..8cd359341 --- /dev/null +++ b/src/quicksy/fastlane/metadata/android/fr-FR/short_description.txt @@ -0,0 +1 @@ +Entrée et découverte facile de Jabber / XMPP From 17423e270eb21fe10aa32fd8cabd66d8a2e49653 Mon Sep 17 00:00:00 2001 From: codimp Date: Sat, 1 Jun 2024 16:52:41 +0000 Subject: [PATCH 38/49] Translated using Weblate (French) Currently translated at 1.4% (1 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/fr/ --- fastlane/metadata/android/fr-FR/changelogs/349.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/fr-FR/changelogs/349.txt diff --git a/fastlane/metadata/android/fr-FR/changelogs/349.txt b/fastlane/metadata/android/fr-FR/changelogs/349.txt new file mode 100644 index 000000000..fb657c547 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/349.txt @@ -0,0 +1,4 @@ +* Introduction d'un paramètre expert pour faire la découverte de salons sur le serveur local au lieu de search.jabber.network +* Active les coches de délivrance par défaut et supprimer le paramètre +* Active ‘Le bouton Envoyer indique l'état’ par défaut et supprimer le paramètre +* Déplacer les paramètres du service de sauvegarde et de premier plan vers l'écran principal From feebe197a4743afedbaee9a851969b92e2b65d12 Mon Sep 17 00:00:00 2001 From: codimp Date: Sun, 2 Jun 2024 23:00:29 +0000 Subject: [PATCH 39/49] Translated using Weblate (French) Currently translated at 100.0% (1020 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/fr/ --- src/main/res/values-fr/strings.xml | 178 ++++++++++++++--------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index f2b15b3ad..0e9394621 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -235,11 +235,11 @@ Détruire le groupe Détruire le salon Voulez-vous vraiment détruire ce groupe ?\n\nAvertissement : le groupe sera complètement supprimé du serveur. - Êtes-vous sûr de vouloir détruire ce canal public \? -\n -\nAttention : Le canal sera totalement supprimé du serveur. + Êtes-vous sûr de vouloir détruire ce salon public ? +\n +\nAttention : Le salon sera totalement supprimé du serveur. Impossible de détruire le groupe - Impossible de détruire le canal + Impossible de détruire le salon Modifier le sujet du groupe Sujet Rejoindre le groupe… @@ -251,13 +251,13 @@ %1$s+%2$d autres ont tout lu jusqu\'ici Tout le monde a lu jusqu\'ici Publier - Appuyer sur l\'avatar pour choisir une image depuis la galerie + Appuyer sur l\'image de profil pour choisir une image depuis la galerie Mise à jour… Le serveur a rejeté votre publication Impossible de convertir votre image - Impossible de stocker l\'avatar sur le disque + Impossible de stocker l\'image de profil sur le disque (Un appui long réinitialise le paramètre) - Votre serveur ne supporte pas la publication d\'avatars + Votre serveur ne supporte pas la publication d\'image de profil chuchoté pour %s Envoyer un message privé à %s @@ -280,13 +280,13 @@ Correction des messages Permet à vos contacts d\'éditer leurs messages rétroactivement Paramètres avancés - À utiliser avec précaution. + À utiliser avec précaution À propos de %s Heures tranquilles Heure de début Heure de fin Activer les heures tranquilles - Les notifications seront muettes pendant les heures tranquilles. + Les notifications seront muettes pendant les heures tranquilles Autres Empreinte OMEMO copiée dans le presse-papier Vous êtes bannis de ce groupe @@ -298,7 +298,7 @@ avec le compte %s hébergé sur %s Vérification de %s sur l\'hôte HTTP - Vous n\'êtes pas connecté. Essayez plus tard. + Vous n\'êtes pas connecté. Essayez plus tard Vérification de la taille de %s Vérification de la taille de %1$s sur %2$s Options du message @@ -310,7 +310,7 @@ URL copiée dans le presse-papier Adresse XMPP copiée dans le presse-papiers Message d\'erreur copié dans le presse-papier - adresse internet + adresse web Scanner le QR code Montrer le QR code Afficher la liste des contacts bloqués @@ -318,7 +318,7 @@ Confirmer Réessayer Service au premier plan - Évite que le système ne ferme votre connexion. + Évite que le système ne ferme votre connexion Créer une sauvegarde La sauvegarde sera stockée dans %s Création des fichiers de sauvegarde @@ -344,13 +344,13 @@ Aucune application disponible pour ouvrir le lien Aucune application disponible pour afficher le contact Tags dynamiques - Afficher des tags en lecture seule en dessous des contacts. + Afficher des tags en lecture seule en dessous des contacts Activer les notifications Serveur de groupe non trouvé Impossible de créer le groupe - Avatar du compte + Image de profil du compte Copier l\'empreinte OMEMO dans le presse-papier - Régénérer l\'empreinte OMEMO + Régénérer la clé OMEMO Supprimer les appareils Êtes-vous sûr de vouloir supprimer les autres appareils de l\'annonce OMEMO ? Ils s\'annonceront de nouveau à leur prochaine connexion, mais ils peuvent ne pas recevoir les messages envoyés entre temps. Aucune clé utilisable n\'est disponible pour ce contact. \nImpossible d\'obtenir de nouvelles clés depuis le serveur. Pourrait-il y avoir un problème avec le serveur de votre contact ? @@ -372,26 +372,26 @@ Hors ligne Banni Membre - Mode expert + Mode avancé Accorder des privilèges aux membres Révoquer les privilèges des membres - Accorder des privilèges d\'administrateur - Révoquer des privilèges d\'administrateur + Accorder des privilèges d\'administrateur·ice + Révoquer des privilèges d\'administrateur·ice Accorder des privilèges de propriétaire Révoquer les privilèges du propriétaire Supprimer du groupe - Supprimer du canal + Supprimer du salon Impossible de changer l\'affiliation de %s Bannir du groupe - Bannir du canal - Vous essayez de supprimer %s d\'un canal public. La seule façon de le faire est de bannir cet utilisateur pour toujours. + Bannir du salon + Vous essayez de supprimer %s d\'un salon public. La seule façon de le faire est de bannir cet·te utilisateur·ice pour toujours. Bannir maintenant Impossible de changer le rôle de %s - Configuration du groupe - Configuration du canal public + Configuration du groupe privé + Configuration du salon public Privé, membres uniquement Rendre les adresses XMPP visibles à tout le monde - Rendre le canal modéré + Rendre le salon modéré Vous ne participez pas Options du groupe modifiées ! Impossible de modifier les options du groupe @@ -404,14 +404,14 @@ Touche Entrée pour envoyer Utilisez la touche Entrée pour envoyer un message. Vous pourrez toujours utiliser la combinaison Ctrl+Entrée pour envoyer un message, même si cette option est désactivée. Afficher la touche Entrée - Remplacer la touche Émoticônes par la touche Entrée. + Remplacer la touche Émoticônes par la touche Entrée audio vidéo image document PDF Application Android Contact - L\'avatar a été publié ! + L\'image de profil a été publiée ! %s en cours d\'envoi En train de proposer un(e) %s Cacher contacts hors-ligne @@ -426,11 +426,11 @@ Aucune application trouvée pour afficher le lieu Position Quitter le groupe privé - Quitte le canal public + Quitte le salon public Ne pas utiliser les CAs système - Tous les certificats doivent être approuvés manuellement. + Tous les certificats doivent être approuvés manuellement Retirer les certificats - Supprimer les certificats approuvés manuellement. + Supprimer les certificats approuvés manuellement Aucun certificat approuvé manuellement Retirer les certificats Supprimer la sélection @@ -485,7 +485,7 @@ Votre appareil ne supporte pas la sélection de certificats client ! Connexion Connexion via Tor - Rediriger toutes les connexions vers le réseau Tor. Nécessite Orbot. + Rediriger toutes les connexions vers le réseau Tor. Nécessite Orbot Nom d\'hôte Port Adresse du serveur (ou .onion) @@ -524,18 +524,18 @@ Ce champ est requis Corriger le message Envoyer le message corrigé - Vous avez déjà fait confiance à l\'empreinte de cette personne pour accorder votre confiance. En sélectionnant « Terminé », vous confirmez simplement que %s fait partie de ce groupe. + Vous avez déjà fait confiance à l\'empreinte de cette personne. En sélectionnant « Terminé », vous confirmez simplement que %s fait partie de ce groupe. Vous avez désactivé ce compte Erreur de sécurité : accès invalide au fichier ! Aucune application disponible pour partager l\'URI Partager l\'URI avec… Accepter et continuer Nous vous guiderons tout au long du processus de création d\'un compte sur conversations.im. -\nLorsque vous sélectionnerez conversations.im en tant que fournisseur, vous pourrez communiquer avec les utilisateurs d\'autres fournisseurs en leur fournissant votre adresse XMPP complète. +\nLorsque vous sélectionnerez conversations.im en tant que fournisseur, vous pourrez communiquer avec les utilisateur·ices d\'autres fournisseurs en leur fournissant votre adresse XMPP complète. Votre adresse XMPP complète sera : %s Créer un compte Utiliser votre propre fournisseur - Choisissez votre nom d\'utilisateur + Choisissez votre nom d\'utilisateur·ice Gérer l\'état de disponibilité manuellement Définir votre disponibilité lors de l\'édition de votre statut. Message de statut @@ -548,7 +548,7 @@ Les optimisations de batterie ne peuvent pas être désactivées sur votre appareil Échec de l\'inscription : Réessayer plus tard Échec de l\'inscription : le mot de passe n\'est pas assez fort - Choisir les participants + Choisir les participant·es Création d\'un groupe… Inviter à nouveau Désactiver @@ -568,7 +568,7 @@ Ordinateur Smartphone Tablette - Navigateur Internet + Navigateur web Console Paiement requis Autoriser à accéder à internet @@ -583,7 +583,7 @@ Effacer les identités OMEMO Régénérer vos clés OMEMO. Tous vos contacts devront vous vérifier à nouveau. À n\'utiliser qu\'en dernier recours. Supprimer les clés sélectionnées - Vous devez être connecté pour publier votre avatar. + Vous devez être connecté pour publier votre image de profil. Afficher le message d\'erreur Message d\'erreur Économiseur de données activé @@ -645,16 +645,16 @@ %d mois %d mois - Suppression messages auto + Suppression automatique des messages Efface automatiquement de cet appareil les messages plus anciens que l\'intervalle choisi. Chiffrement du message en cours Aucun message récupéré, en raison de la configuration de rétention de l\'appareil. Compression de la vidéo en cours Contact bloqué. - Notifications d\'inconnus - Notifier les messages et appels reçus d\'inconnus. - Message d\'un inconnu reçu - Bloquer l\'inconnu + Notifications d\'inconnu·es + Notifier les messages et appels reçus d\'inconnu·es. + Message d\'un·e inconnu·e reçu + Bloquer l\'inconnu·e Bloquer le domaine entier En ligne actuellement Nouvelle tentative de déchiffrement @@ -686,7 +686,7 @@ La lecture d\'un QR code nécessite l\'accès à l\'appareil photo Faire défiler l\'écran jusqu\'en bas Faire défiler l\'écran jusqu\'en bas après avoir envoyé un message - Modifier le message de l\'état + Modifier le message d\'état Modifier le message de statut Désactiver le chiffrement %1$s n\'est pas capable d\'envoyer un message chiffré à %2$s. Ceci peut être lié au fait que votre contact utilise un serveur obsolète ou un client qui ne gère par OMEMO. @@ -707,7 +707,7 @@ Le message n\'était pas chiffré pour cet appareil. Échec de déchiffrement du message OMEMO. annuler - Le partage de positionnement est désactivé. + Le partage de positionnement est désactivé Figer la position Débloquer la position Copier la position @@ -724,14 +724,14 @@ Voir la conversation Plugin de partage de localisation Utilisez le plugin Share Location à la place de la carte intégrée - Copier l\'adresse internet + Copier l\'adresse web Copier l\'adresse XMPP Partage de fichier HTTP pour S3 Recherche directe Lors de l\'ajout de conversations, afficher le clavier et placer le curseur sur le champ de recherche - Avatar du groupe - Le serveur ne prend pas en charge les avatars pour les groupes - Seul le propriétaire peut changer l\'avatar d\'un groupe + Image de profil du groupe + Le serveur ne prend pas en charge les images de profil pour les groupes + Seul le propriétaire peut changer l\'image de profil d\'un groupe Nom du contact Surnom Nom @@ -757,7 +757,7 @@ Importance, son, vibration Compression vidéo Voir les média - Participants + Participant·es Navigateur de média Fichier omis en raison d\'une violation de la sécurité. Qualité des vidéos @@ -799,10 +799,10 @@ Impossible d\'établir une connexion sécurisée. Impossible de trouver le serveur. Une erreur est survenue pendant le traitement de votre requête. - Entrée utilisateur incorrecte + Entrée utilisateur·ice incorrecte Temporairement indisponible. Réessayez plus tard. Pas de connexion réseau. - Veuillez réessayer dans%s + Veuillez réessayer dans %s Vous êtes à taux limité Trop de tentatives Vous utilisez une version obsolète de cette application. @@ -815,8 +815,8 @@ Rejeter la demande Installer Orbot Démarrer Orbot - Aucune application de marché installée. - Ce canal rendra votre adresse XMPP publique + Aucun magasin d\'applications installée. + Ce salon rendra votre adresse XMPP publique e-book Original (non compressé) Ouvrir avec… @@ -825,48 +825,48 @@ Restaurer la sauvegarde Restaurer Entrez votre mot de passe pour que le compte %s restaure la sauvegarde. - N\'utilisez pas la fonctionnalité de sauvegarde de la restauration pour tenter de cloner (exécuter simultanément) une installation. La restauration d’une sauvegarde ne concerne que les migrations ou en cas de perte du périphérique d’origine. + N\'utilisez pas la fonctionnalité de sauvegarde de la restauration pour tenter de cloner (exécuter simultanément) une installation. La restauration d’une sauvegarde ne concerne que les migrations ou en cas de perte de l\'appareil d’origine. Impossible de restaurer la sauvegarde. Impossible de déchiffrer la sauvegarde. Le mot de passe est-il correct ? Sauvegarde & restauration Entrez l\'adresse XMPP Créer un groupe - Rejoindre le canal public + Rejoindre le salon public Créer un groupe privé - Créer un canal public - Nom du canal + Créer un salon public + Nom du salon Adresse XMPP - Veuillez donner un nom au canal + Veuillez donner un nom au salon Veuillez renseigner une adresse XMPP Ceci est une adresse XMPP. Veuillez renseigner un nom. - Création d\'un canal public… - Ce canal existe déjà - Vous avez rejoint un canal existant - Impossible de sauvegarder la configuration du canal + Création d\'un salon public… + Ce salon existe déjà + Vous avez rejoint un salon existant + Impossible de sauvegarder la configuration du salon Autoriser quiconque à éditer le sujet Permettre à quiconque d\'inviter d\'autres personnes N\'importe qui peut éditer le sujet. Les propriétaires peuvent éditer le sujet. - Les administrateurs peuvent modifier le sujet. + Les administrateur·ices peuvent modifier le sujet. Les propriétaires peuvent inviter d\'autres personnes. N\'importe qui peut inviter d\'autres personnes. - Les adresses XMPP sont visibles par les administrateurs. + Les adresses XMPP sont visibles par les administrateur·ices. Les adresses XMPP sont visibles par tous. - Ce canal public n\'a pas de participants. Invitez vos contacts ou utilisez le bouton de partage pour distribuer son adresse XMPP. - Ce groupe privé n\'a aucun participant. + Ce salon public n\'a pas de participant·es. Invitez vos contacts ou utilisez le bouton de partage pour distribuer son adresse XMPP. + Ce groupe privé n\'a aucun·e participant·e. Gérer les privilèges - Rechercher des participants + Rechercher des participant·es Fichier trop volumineux Joindre - Découverte des canaux - Recherche des canaux + Découverte des salons + Recherche des salons Violation possible de la confidentialité ! J\'ai déjà un compte Ajouter un compte existant Enregistrer un nouveau compte Ceci ressemble à une adresse de domaine Ajouter quand même - Ceci ressemble à une adresse de canal + Ceci ressemble à une adresse de salon Partager les fichiers de sauvegardes Sauvegarder les conversations Événement @@ -875,13 +875,13 @@ Ce compte a déjà été configuré Veuillez saisir le mot de passe pour ce compte Impossible de réaliser cette action - Rejoindre le canal public… + Rejoindre le salon public… L\'application de partage n\'a pas accordé la permission d\'accéder à ce fichier. - + Salons et groupes de discussion jabber.network Serveur local - La plupart des utilisateurs devraient choisir « jabber.network » pour de meilleures suggestions provenant de l’écosystème public entier de XMPP. - Méthode de découverte des canaux + La plupart des utilisateur·ices devraient choisir « jabber.network » pour de meilleures suggestions provenant de l’écosystème public entier de XMPP. + Méthode de découverte des salons Sauvegarde À propos Veuillez activer votre compte @@ -926,8 +926,8 @@ Impossible de corriger le message Toutes les conversations Cette conversation - Votre avatar - Avatar pour %s + Votre image de profil + Image de profil pour %s Chiffré avec OMEMO Chiffré avec OpenPGP Non chiffré @@ -949,7 +949,7 @@ Échec lors de la livraison Plus d\'options Aucune application trouvée - Inviter à Conversations + Inviter sur Conversations Impossible de lire l\'invitation Le serveur ne prend pas en charge la génération d\'invitations Aucun compte actif ne prend en charge cette fonctionalité @@ -993,7 +993,7 @@ Synchroniser les favoris Activer \"Rejoindre automatiquement\" en entrant ou sortant d\'un groupe et réagir aux modifications apportées par d\'autres clients. graphique vectoriel - Appel entrant (%s) - %s + Appel entrant (%s) · %s Appel sortant · %s %1$d appel manqué de %2$s @@ -1006,7 +1006,7 @@ Impossible de supprimer le compte du serveur Document texte Échec temporaire de l\'authentification - Supprimer l\'avatar + Supprimer l\'image de profil Vous essayez d\'importer un format de fichier de sauvegarde obsolète Livre audio Distributeur UnifiedPush @@ -1014,7 +1014,7 @@ Signaler un spam Politique de confidentialité Quicksy vous demande votre consentement pour utiliser vos données - Signaler un spam et bloquer son auteur + Signaler un spam et bloquer son auteur·ice Déconnecté S\'identifier Vous vous êtes déconnecté⸱e de ce compte @@ -1032,11 +1032,11 @@ Thème clair/sombre Autoriser les captures d\'écran Chiffrement de bout en bout - Afficher le contenu de l\'application dans le sélecteur d\'applications et autorise les captures d\'écran + Afficher le contenu de l\'application dans le sélecteur d\'applications et autoriser les captures d\'écran Autorités de certification Faire confiance aux certificats de l\'autorité de certification du système - Oblige la liaison du canal - La liaison du canal peut permettre de détecter les attaques de l\'homme du milieu + Oblige la liaison du salon + La liaison du salon peut permettre de détecter les attaques de l\'homme du milieu Clavier Créer une sauvegarde ponctuelle Créer une sauvegarde récurrente @@ -1049,7 +1049,7 @@ Rejoignez la Conversation Chiffrement de bout en bout, confiance aveugle avant vérification, détection des attaques de l\'homme du milieu Système d\'exploitation - Opération pas encore supportée + Opération non supportée Notifications d\'utilisation En agissant en tant que distributeur UnifiedPush, la connexion XMPP persistante, fiable et économique en batterie sera utilisée pour communiquer avec d\'autres applications compatibles comme Tusky, Ltt.rs, FluffyChat et d\'autres encore. Notifications en plein écran @@ -1060,18 +1060,18 @@ Afficher un fond différent pour distinguer les messages envoyés et reçus Couleurs dynamiques Couleurs systèmes (Material You) - La découverte de canaux utilise un service de tierce partie appelé <a href=https://search.jabber.network>search.jabber.network</a>.<br><br> Utiliser cette fonctionnalité va transmettre votre adresse IP et votre recherche à ce service. Voir leur <a href=https://search.jabber.network/privacy>Politique de confidentialité</a> pour plus d\'informations. + La découverte de salons utilise un service tier appelé <a href=https://search.jabber.network>search.jabber.network</a>.<br><br> Utiliser cette fonctionnalité va transmettre votre adresse IP et votre recherche à ce service. Voir leur <a href=https://search.jabber.network/privacy>Politique de confidentialité</a> pour plus d\'informations. Archiver la conversation Supprimer cette conversation ensuite Envoyer un message chiffré Conversation archivée Se rendre à la conversation Votre contact utilise un appareil qui n\'est pas encore vérifié. Scannez son QR code pour effectuer une vérification et éviter une attaque de l\'homme du milieu. - Discuter + Lancer une discussion Aucun certificat client sélectionné ! Thème, couleurs, captures d\'écran, saisie Sécurité - Relai de notifications pour les applications de tierce partie compatibles avec UnifiedPush + Relai de notifications pour les applications tierces compatibles avec UnifiedPush Notifications Taille de fichier, compression des images, qualité des vidéos Période sans notifications, sonnerie, vibration, inconnus @@ -1082,12 +1082,12 @@ Connection au serveur Notifications d\'écriture, dernière connexion, disponibilité Nom d\'hôte et port, Tor - Nom d\'hôte et port, Tor et découverte des canaux + Nom d\'hôte et port, Tor et découverte des salons Application Interaction Sur l\'appareil - Invitations d\'inconnus - Accepter les invitations aux conversations de groupes provenant d\'inconnus + Invitations d\'inconnu·es + Accepter les invitations aux conversations de groupes provenant d\'inconnu·es Grande police Augmenter la taille de la police dans les bulles de message Autoriser les messages privés From 35049b6379c4583b9b867c5b72583156d08d514b Mon Sep 17 00:00:00 2001 From: nautilusx Date: Mon, 3 Jun 2024 16:57:55 +0000 Subject: [PATCH 40/49] Translated using Weblate (German) Currently translated at 100.0% (68 of 68 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/de/ --- fastlane/metadata/android/de-DE/changelogs/4211404.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/de-DE/changelogs/4211404.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/4211404.txt b/fastlane/metadata/android/de-DE/changelogs/4211404.txt new file mode 100644 index 000000000..ecd13e402 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* Ältere Oppo-Geräte von der Anrufintegration ausgenommen +* Verschiedene Fehlerbehebungen From e1aa2d3d6d0bd580e68e1d65e6a3b39ceff3c957 Mon Sep 17 00:00:00 2001 From: rex07 Date: Mon, 3 Jun 2024 19:47:20 +0000 Subject: [PATCH 41/49] Translated using Weblate (Arabic) Currently translated at 100.0% (9 of 9 strings) Translation: Conversations/Android App (Quicksy) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-quicksy/ar/ --- src/quicksy/res/values-ar/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/quicksy/res/values-ar/strings.xml b/src/quicksy/res/values-ar/strings.xml index ddf1d8788..cf44a653b 100644 --- a/src/quicksy/res/values-ar/strings.xml +++ b/src/quicksy/res/values-ar/strings.xml @@ -1,10 +1,10 @@ - مدى الوقت الذي يظل فيه Quicksy هادئًا بعد رؤية نشاط على جهاز آخر - عبر إرسال الأخطاء انت تقوم بالمساعدة في تطوير برمجة Quicksy + طول الفترة الزمنية التي يظل فيها Quicksy هادئًا بعد رؤية النشاط على جهاز آخر + بإرسال تتبعات المكدس، فإنك تساعد في التطوير المستمر لـ Quicksy إجعل كلّ جهات إتصالك تعلم أنك تستعمل كويكسي للمواصلة في إستقبال التنبيهات، حتى والشاشة مغلقة، يجب عليك أن تضيف Quicksy إلى قائمة التطبيقات المحميّة. - صورة حساب Quicksy + صورة الملف الشخصي بسرعة Quicksy إن كويكسي Quicksy غير متوفر في بلدكم. لا يمكن التأكد من خادم الهويّة. خطأ أمني مجهول. From f67aeadf10ec4264b2b311c93ac7db3f6591ac80 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Mon, 3 Jun 2024 19:52:04 +0000 Subject: [PATCH 42/49] Translated using Weblate (Ukrainian) Currently translated at 100.0% (68 of 68 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/uk/ --- fastlane/metadata/android/uk/changelogs/4211404.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/4211404.txt diff --git a/fastlane/metadata/android/uk/changelogs/4211404.txt b/fastlane/metadata/android/uk/changelogs/4211404.txt new file mode 100644 index 000000000..ff1007cbf --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* Виключення старих пристроїв Oppo з інтеграції викликів +* Виправлення різноманітних помилок From a901341f813e54b84f37eda36d9087ba5ff32b2b Mon Sep 17 00:00:00 2001 From: ariasuni Date: Mon, 3 Jun 2024 23:05:53 +0000 Subject: [PATCH 43/49] Translated using Weblate (French) Currently translated at 100.0% (1020 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/fr/ --- src/main/res/values-fr/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 0e9394621..6747989e6 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -21,7 +21,7 @@ Choisir un contact Choisir les contacts Partager via le compte - Bloquer la liste + Comptes bloqués À l\'instant Il y a 1 minute Il y a %d minutes @@ -313,7 +313,7 @@ adresse web Scanner le QR code Montrer le QR code - Afficher la liste des contacts bloqués + Afficher la liste des comptes bloqués Détails du compte Confirmer Réessayer From d2c36895265836f08b4e410fa3e8b054c1bab659 Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Tue, 4 Jun 2024 02:52:26 +0000 Subject: [PATCH 44/49] Translated using Weblate (Polish) Currently translated at 17.6% (12 of 68 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/pl/ --- fastlane/metadata/android/pl-PL/changelogs/4211404.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/pl-PL/changelogs/4211404.txt diff --git a/fastlane/metadata/android/pl-PL/changelogs/4211404.txt b/fastlane/metadata/android/pl-PL/changelogs/4211404.txt new file mode 100644 index 000000000..916a4c5fe --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* Wyłączenie starszych urządzeń Umidigi z integracji rozmów +* Różne poprawki błędów From b80361f1be43a230c7975a8810b79e60eafda539 Mon Sep 17 00:00:00 2001 From: ghose Date: Tue, 4 Jun 2024 07:18:37 +0000 Subject: [PATCH 45/49] Translated using Weblate (Galician) Currently translated at 58.8% (40 of 68 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/gl/ --- fastlane/metadata/android/gl-ES/changelogs/4211404.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/gl-ES/changelogs/4211404.txt diff --git a/fastlane/metadata/android/gl-ES/changelogs/4211404.txt b/fastlane/metadata/android/gl-ES/changelogs/4211404.txt new file mode 100644 index 000000000..bae283ec9 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* excluír da integración de chamadas aos dispositivos Oppo antigos +* arranxos varios From a8cd904ee44536862c4cf5116f1406a886973976 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Tue, 4 Jun 2024 00:20:39 +0000 Subject: [PATCH 46/49] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (68 of 68 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/zh_Hans/ --- fastlane/metadata/android/zh-CN/changelogs/4211404.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/zh-CN/changelogs/4211404.txt diff --git a/fastlane/metadata/android/zh-CN/changelogs/4211404.txt b/fastlane/metadata/android/zh-CN/changelogs/4211404.txt new file mode 100644 index 000000000..aa59d1f2e --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* 从呼叫集成中排除较旧的 OPPO 设备 +* 各种错误修复 From 7160028ecf77722caa546dac355d322a0167cdc3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 4 Jun 2024 12:51:35 +0200 Subject: [PATCH 47/49] bump dependencies --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 04da16f85..c1a22a733 100644 --- a/build.gradle +++ b/build.gradle @@ -45,10 +45,10 @@ dependencies { exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' } conversationsPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2") - quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.0.2' + quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.1.0' implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1' implementation("com.github.CanHub:Android-Image-Cropper:2.0.0") - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.exifinterface:exifinterface:1.3.7' implementation 'androidx.cardview:cardview:1.0.0' implementation "androidx.preference:preference:1.2.1" From a1746a8e0660b252aa939d54b2940e8ee052fc66 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 4 Jun 2024 16:50:43 +0200 Subject: [PATCH 48/49] fix regression in handling own omemo devices --- .../conversations/crypto/axolotl/AxolotlService.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index b1ecda3c2..ceb7ab48e 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -392,11 +392,11 @@ private void publishOwnDeviceIdIfNeeded() { } Iq packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid()); mXmppConnectionService.sendIqPacket(account, packet, response -> { - if (packet.getType() == Iq.Type.TIMEOUT) { + if (response.getType() == Iq.Type.TIMEOUT) { Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids."); } else { //TODO consider calling registerDevices only after item-not-found to account for broken PEPs - final Element item = IqParser.getItem(packet); + final Element item = IqParser.getItem(response); final Set deviceIds = IqParser.deviceIds(item); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds); registerDevices(account.getJid().asBareJid(), deviceIds); @@ -744,7 +744,7 @@ private ListenableFuture verifySessionWithPEP(final XmppAxol } final SettableFuture future = SettableFuture.create(); final Iq packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId()); - mXmppConnectionService.sendIqPacket(account, packet, (response) -> { + mXmppConnectionService.sendIqPacket(account, packet, response -> { Pair verification = IqParser.verification(response); if (verification != null) { try { @@ -850,7 +850,7 @@ private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) { } } if (packet != null) { - mXmppConnectionService.sendIqPacket(account, packet, (response) -> { + mXmppConnectionService.sendIqPacket(account, packet, response -> { if (response.getType() == Iq.Type.RESULT) { fetchDeviceListStatus.put(jid, true); final Element item = IqParser.getItem(response); @@ -914,7 +914,7 @@ private ListenableFuture buildSessionFromPEP(final SignalPro final Jid jid = Jid.of(address.getName()); final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid()); final Iq bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId()); - mXmppConnectionService.sendIqPacket(account, bundlesPacket, (packet) -> { + mXmppConnectionService.sendIqPacket(account, bundlesPacket, packet -> { if (packet.getType() == Iq.Type.TIMEOUT) { fetchStatusMap.put(address, FetchStatus.TIMEOUT); sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout")); From 6275d0c536301e167b90fe1d0305534503c316c3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 4 Jun 2024 16:54:43 +0200 Subject: [PATCH 49/49] version bump to 2.16.4 + changelog --- CHANGELOG.md | 4 ++++ build.gradle | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80e79abe5..5a1ab749d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### Version 2.16.4 + +* Fix minor regression introduced in 2.16.4 + ### Version 2.16.3 * exclude older Oppo devices from call integration diff --git a/build.gradle b/build.gradle index c1a22a733..7168b7486 100644 --- a/build.gradle +++ b/build.gradle @@ -104,8 +104,8 @@ android { defaultConfig { minSdkVersion 23 targetSdkVersion 34 - versionCode 42114 - versionName "2.16.3" + versionCode 42115 + versionName "2.16.4" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId