From 40b54faaa4dd8019afb857c976515a744e959634 Mon Sep 17 00:00:00 2001 From: yurtpage Date: Sat, 19 Apr 2025 16:45:59 +0000 Subject: [PATCH 01/15] Translated using Weblate (Russian) Currently translated at 100.0% (1076 of 1076 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/ru/ --- src/main/res/values-ru/strings.xml | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 3be0abeb349ac952711c12f5213f994109c39fbe..81b6422d2bbf2799afc5082bbc8fa68b7f087c05 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -184,14 +184,14 @@ \nВаши собеседники не смогут больше отправлять вам зашифрованные OpenPGP сообщения. Публичный ключ OpenPGP опубликован. Включить аккаунт - Удалить свой аккаунт? Удаление аккаунта также сотрёт все беседы. + Удалить свой аккаунт? Удаление аккаунта также сотрёт все историю бесед Записать голос XMPP-адрес Заблокировать XMPP-адрес username@example.com Пароль Недопустимый XMPP-адрес - Нехватка памяти. Изображение слишком большое. + Нехватка памяти. Изображение слишком большое Добавить %s в вашу адресную книгу? Информация о сервере XEP-0313: архив сообщений @@ -310,7 +310,7 @@ используется аккаунт %s размещено на %s Проверка %s на сервере HTTP - Вы не подключены. Попробуйте позже. + Вы не подключены. Попробуйте позже Проверить размер (%s) Проверить размер на %2$s (%1$s) Параметры сообщения @@ -499,7 +499,7 @@ Ваше устройство не поддерживает выбор клиентских сертификатов! Подключение Соединение через Tor - Направить все соединения через сеть Tor. Требуется Orbot. + Направить все соединения через сеть Tor. Требуется Orbot Имя сервера Порт Сервер или .onion-адрес @@ -556,7 +556,7 @@ Использовать своего провайдера Выберите имя пользователя Управлять доступностью вручную - Устанавливать свою доступность при редактировании статусного сообщения + Устанавливать свою доступность при редактировании статусного сообщения. Статусное сообщение Свободен для общения В сети @@ -620,7 +620,7 @@ Отправить XMPP URI Отправить HTTP-ссылку Слепое доверие до подтверждения - Автоматически доверять всем новым устройствам контактов, которые не были подтверждены ранее, но запрашивать ручное подтверждение каждый раз, когда подтверждённый контакт добавляет новое устройство + Автоматически доверять всем новым устройствам контактов, которые не были подтверждены ранее, но запрашивать ручное подтверждение каждый раз, когда подтверждённый контакт добавляет новое устройство. Принятие OMEMO-ключей вслепую. Это означает, что собеседник может оказаться недоверенным лицом. Недоверенный Некорректный QR-код @@ -679,7 +679,7 @@ Сжатие видео Контакт заблокирован. Уведомления от неизвестных контактов - Уведомлять о сообщениях и звонках от неизвестных контактов + Уведомлять о сообщениях и звонках от неизвестных контактов. Получено сообщение от неизвестного контакта Заблокировать неизвестный контакт Заблокировать весь домен @@ -725,9 +725,9 @@ Отключить сейчас Черновик: OMEMO-шифрование - OMEMO будет всегда использоваться для одиночных бесед и приватных конференций - OMEMO будет использоваться по умолчанию для новых бесед - OMEMO нужно будет явно включать для новых бесед + OMEMO будет всегда использоваться для одиночных бесед и приватных конференций. + OMEMO будет использоваться по умолчанию для новых бесед. + OMEMO нужно будет явно включать для новых бесед. Создать ярлык Включено по умолчанию Выключено по умолчанию @@ -1011,7 +1011,7 @@ Невозможно удалить аккаунт на сервере Удалить аккаунт на сервере Синхронизировать закладки - Устанавливать флаг \"автоприсоединение\" при входе и выходе из MUC, и реагировать на изменения от других клиентов + Устанавливать флаг \"автоприсоединение\" при входе и выходе из группового чата, и реагировать на изменения от других клиентов. Поиск конференций Загрузка не выполнена: файл испорчен Перейти на видеовызов\? @@ -1019,10 +1019,10 @@ Входящий вызов (%s) · %s Регистрации аккаунтов не поддерживаются Звонки отключены при использовании Tor - Аккаунт для получения push-уведомлений + Аккаунт для получения push-уведомлений. Нет (неактивно) Вы собираетесь проверить ключи OMEMO своего аккаунта. Это безопасно только в том случае, если вы перешли по этой ссылке из надёжного источника, где только вы могли опубликовать эту ссылку. - Восстанавливайте только те резервные копии, которые были созданы лично вами! + Восстанавливайте только те резервные копии, которые были созданы лично вами. %1$d пропущенный вызов от %2$s %1$d пропущенных вызова от %2$s @@ -1107,7 +1107,7 @@ Операция не поддерживается Полноэкранные уведомления Регулярные резервные копии - Разрешить приложению показывать экраны входящих звонков при заблокированном экране + Разрешить приложению показывать экраны входящих звонков при заблокированном экране. Conversations, будучи распределителем UnifiedPush, будет использовать стойкое, надёжное и малопотребляющее подключение XMPP для пробуждения других совместимых с UnifiedPush приложений, таких как Tusky, Ltt.rs, FluffyChat и др. Требовать привязку канала Привязка канала может обнаружить некоторые атаки посредником @@ -1119,7 +1119,7 @@ Звонок через динамик. Нажмите для переключения на наушники. Видео не активно. Нажмите для включения. Звонок через наушники. - Звонок через проводные наушники. + Звонок через проводные наушники Звонок через динамик. Звонок через Bluetooth. Сменить камеру @@ -1146,12 +1146,12 @@ Показывать аватары Сообщения в пузырях Пузыри сообщений - Показывать аватары в своих сообщениях и приватных беседах в дополнение к конференциям + Показывать аватары в своих сообщениях и приватных беседах в дополнение к конференциям. Интеграция звонков Выравнивать сообщения слева - Показывать все сообщения, включая отправленные, выровненными по левому краю для единообразия вида беседы + Показывать все сообщения, включая отправленные, выровненными по левому краю для единообразия вида беседы. Настраиваемые уведомления - Звонки из этого приложения взаимодействуют с обычными телефонными звонками, например, завершение одного звонка при начале другого + Звонки из этого приложения взаимодействуют с обычными телефонными звонками, например, завершение одного звонка при начале другого. Использовать индивидуальные настройки уведомлений (важность, звук, вибрация) для этой беседы? Хотите удалить свой аватар? Некоторые клиенты могут продолжать отображать кэшированную копию вашего аватара. Показывать только контактам From 3c9dbebed3c7be4a3197be45ce5a8613d8598bfc Mon Sep 17 00:00:00 2001 From: user11 Date: Sat, 19 Apr 2025 22:06:05 +0000 Subject: [PATCH 02/15] Translated using Weblate (Serbian) Currently translated at 100.0% (1076 of 1076 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/sr/ --- src/main/res/values-sr/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml index d579bbff5063baf9079340f0471636f0a3800c59..649ecd4b035aef62bd241e3e0ed964a740bb57d6 100644 --- a/src/main/res/values-sr/strings.xml +++ b/src/main/res/values-sr/strings.xml @@ -1157,4 +1157,7 @@ Копирај број телефона Копиран број телефона у клипборд Копирај URI + Планирана недоступност + Сервис недоступан (познат проблем) + Опоравак сервиса предвиђен у %s From f599c216f05ca9b304216c3a3c3c891813d974f2 Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Sat, 19 Apr 2025 12:31:30 +0000 Subject: [PATCH 03/15] Translated using Weblate (Polish) Currently translated at 56.3% (49 of 87 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/4214204.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/pl-PL/changelogs/4214204.txt diff --git a/fastlane/metadata/android/pl-PL/changelogs/4214204.txt b/fastlane/metadata/android/pl-PL/changelogs/4214204.txt new file mode 100644 index 0000000000000000000000000000000000000000..d10e24b966d13858cb9efe93c0ce8b30b4c5c2d4 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4214204.txt @@ -0,0 +1,2 @@ +* Obsługa statusu niedostępności usługi +* Drobne poprawki bezpieczeństwa parsowania wielu elementów treści, identyfikatorów uczestników i stanz From 57ae125c48c5908828cb9bb10bfd942d3b0f05f3 Mon Sep 17 00:00:00 2001 From: solokot Date: Sun, 20 Apr 2025 06:15:53 +0000 Subject: [PATCH 04/15] Translated using Weblate (Russian) Currently translated at 100.0% (1076 of 1076 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/ru/ --- src/main/res/values-ru/strings.xml | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 81b6422d2bbf2799afc5082bbc8fa68b7f087c05..847ebbcb6a3d5056295da630d33ac058177c61ee 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -191,7 +191,7 @@ username@example.com Пароль Недопустимый XMPP-адрес - Нехватка памяти. Изображение слишком большое + Нехватка памяти. Изображение слишком большое. Добавить %s в вашу адресную книгу? Информация о сервере XEP-0313: архив сообщений @@ -310,7 +310,7 @@ используется аккаунт %s размещено на %s Проверка %s на сервере HTTP - Вы не подключены. Попробуйте позже + Вы не подключены. Попробуйте позже. Проверить размер (%s) Проверить размер на %2$s (%1$s) Параметры сообщения @@ -499,7 +499,7 @@ Ваше устройство не поддерживает выбор клиентских сертификатов! Подключение Соединение через Tor - Направить все соединения через сеть Tor. Требуется Orbot + Направить все соединения через сеть Tor. Требуется Orbot. Имя сервера Порт Сервер или .onion-адрес @@ -556,7 +556,7 @@ Использовать своего провайдера Выберите имя пользователя Управлять доступностью вручную - Устанавливать свою доступность при редактировании статусного сообщения. + Устанавливать свою доступность при редактировании статусного сообщения Статусное сообщение Свободен для общения В сети @@ -620,7 +620,7 @@ Отправить XMPP URI Отправить HTTP-ссылку Слепое доверие до подтверждения - Автоматически доверять всем новым устройствам контактов, которые не были подтверждены ранее, но запрашивать ручное подтверждение каждый раз, когда подтверждённый контакт добавляет новое устройство. + Автоматически доверять всем новым устройствам контактов, которые не были подтверждены ранее, но запрашивать ручное подтверждение каждый раз, когда подтверждённый контакт добавляет новое устройство Принятие OMEMO-ключей вслепую. Это означает, что собеседник может оказаться недоверенным лицом. Недоверенный Некорректный QR-код @@ -679,7 +679,7 @@ Сжатие видео Контакт заблокирован. Уведомления от неизвестных контактов - Уведомлять о сообщениях и звонках от неизвестных контактов. + Уведомлять о сообщениях и звонках от неизвестных контактов Получено сообщение от неизвестного контакта Заблокировать неизвестный контакт Заблокировать весь домен @@ -725,9 +725,9 @@ Отключить сейчас Черновик: OMEMO-шифрование - OMEMO будет всегда использоваться для одиночных бесед и приватных конференций. - OMEMO будет использоваться по умолчанию для новых бесед. - OMEMO нужно будет явно включать для новых бесед. + OMEMO будет всегда использоваться для одиночных бесед и приватных конференций + OMEMO будет использоваться по умолчанию для новых бесед + OMEMO нужно будет явно включать для новых бесед Создать ярлык Включено по умолчанию Выключено по умолчанию @@ -1011,7 +1011,7 @@ Невозможно удалить аккаунт на сервере Удалить аккаунт на сервере Синхронизировать закладки - Устанавливать флаг \"автоприсоединение\" при входе и выходе из группового чата, и реагировать на изменения от других клиентов. + Устанавливать флаг \"автоприсоединение\" при входе и выходе из группового чата, и реагировать на изменения от других клиентов Поиск конференций Загрузка не выполнена: файл испорчен Перейти на видеовызов\? @@ -1019,10 +1019,10 @@ Входящий вызов (%s) · %s Регистрации аккаунтов не поддерживаются Звонки отключены при использовании Tor - Аккаунт для получения push-уведомлений. + Аккаунт для получения push-уведомлений Нет (неактивно) Вы собираетесь проверить ключи OMEMO своего аккаунта. Это безопасно только в том случае, если вы перешли по этой ссылке из надёжного источника, где только вы могли опубликовать эту ссылку. - Восстанавливайте только те резервные копии, которые были созданы лично вами. + Восстанавливайте только те резервные копии, которые были созданы лично вами! %1$d пропущенный вызов от %2$s %1$d пропущенных вызова от %2$s @@ -1107,7 +1107,7 @@ Операция не поддерживается Полноэкранные уведомления Регулярные резервные копии - Разрешить приложению показывать экраны входящих звонков при заблокированном экране. + Разрешить приложению показывать экраны входящих звонков при заблокированном экране Conversations, будучи распределителем UnifiedPush, будет использовать стойкое, надёжное и малопотребляющее подключение XMPP для пробуждения других совместимых с UnifiedPush приложений, таких как Tusky, Ltt.rs, FluffyChat и др. Требовать привязку канала Привязка канала может обнаружить некоторые атаки посредником @@ -1119,7 +1119,7 @@ Звонок через динамик. Нажмите для переключения на наушники. Видео не активно. Нажмите для включения. Звонок через наушники. - Звонок через проводные наушники + Звонок через проводные наушники. Звонок через динамик. Звонок через Bluetooth. Сменить камеру @@ -1146,12 +1146,12 @@ Показывать аватары Сообщения в пузырях Пузыри сообщений - Показывать аватары в своих сообщениях и приватных беседах в дополнение к конференциям. + Показывать аватары в своих сообщениях и приватных беседах в дополнение к конференциям Интеграция звонков Выравнивать сообщения слева - Показывать все сообщения, включая отправленные, выровненными по левому краю для единообразия вида беседы. + Показывать все сообщения, включая отправленные, выровненными по левому краю для единообразия вида беседы Настраиваемые уведомления - Звонки из этого приложения взаимодействуют с обычными телефонными звонками, например, завершение одного звонка при начале другого. + Звонки из этого приложения взаимодействуют с обычными телефонными звонками, например, завершение одного звонка при начале другого Использовать индивидуальные настройки уведомлений (важность, звук, вибрация) для этой беседы? Хотите удалить свой аватар? Некоторые клиенты могут продолжать отображать кэшированную копию вашего аватара. Показывать только контактам From 09b33432bbeb26f94522269c04df7219d52a2981 Mon Sep 17 00:00:00 2001 From: solokot Date: Sun, 20 Apr 2025 06:13:14 +0000 Subject: [PATCH 05/15] Translated using Weblate (Russian) 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/ru/ --- .../fastlane/metadata/android/ru-RU/full_description.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/quicksy/fastlane/metadata/android/ru-RU/full_description.txt b/src/quicksy/fastlane/metadata/android/ru-RU/full_description.txt index 9bf22d59b24ca2f92a454ffad2bd44e4c526b155..93cfda2aa020db40cd01be002a1b88fecc335987 100644 --- a/src/quicksy/fastlane/metadata/android/ru-RU/full_description.txt +++ b/src/quicksy/fastlane/metadata/android/ru-RU/full_description.txt @@ -1,14 +1,14 @@ Quicksy — это ответвление популярного Jabber/XMPP клиента Conversations с автоматическим нахождением контактов. -Вы регистрируетесь используя свой номер телефона и Quicksy автоматически на основе телефонных номеров в вашей адресной книге предложит вам возможные контакты. +Вы регистрируетесь, используя свой номер телефона, и Quicksy автоматически на основе телефонных номеров в вашей адресной книге предложит вам возможные контакты. -По сути Quicksy — это полноценный XMPP-клиент, который позволяет вам общаться с любым пользователем на любом общедоступном федеративном сервере. Аналогично, с пользователями Quicksy можно связаться извне просто добавив +phonenumber@quicksy.im в свой список контактов. +По сути Quicksy — это полноценный XMPP-клиент, который позволяет вам общаться с любым пользователем на любом общедоступном федеративном сервере. Аналогично, с пользователями Quicksy можно связаться извне, просто добавив +phonenumber@quicksy.im в свой список контактов. Помимо синхронизации контактов, пользовательский интерфейс намеренно максимально приближен к Conversations. Это позволяет пользователям в конечном итоге перейти с Quicksy на Conversations без необходимости заново изучать принципы работы приложения. -Предлагаемые контакты включают других пользователей Quicksy и обычных пользователей XMPP которые ввели свой идентификатор XMPP в каталог Quicksy (https://quicksy.im/#get-listed). +Предлагаемые контакты включают других пользователей Quicksy и обычных пользователей XMPP, которые внесли свой идентификатор XMPP в каталог Quicksy (https://quicksy.im/#get-listed). -ПРИМЕЧАНИЕ: Чтобы ввести (https://quicksy.im/enter/) свой идентификатор XMPP в каталог Quicksy +ПРИМЕЧАНИЕ: чтобы ввести (https://quicksy.im/enter/) свой идентификатор XMPP в каталог Quicksy, необходимо внести единовременный регистрационный взнос. Ознакомьтесь с Политикой конфиденциальности (https://quicksy.im/#privacy) для дополнительной информации. From d1eb3afb51cdc2a7fd9d63b800b1f99a1940438b Mon Sep 17 00:00:00 2001 From: solokot Date: Sun, 20 Apr 2025 06:19:59 +0000 Subject: [PATCH 06/15] Translated using Weblate (Russian) Currently translated at 100.0% (1076 of 1076 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/ru/ --- src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 847ebbcb6a3d5056295da630d33ac058177c61ee..c1e5cbe638676d352d409d738a794bec58d576a7 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -184,7 +184,7 @@ \nВаши собеседники не смогут больше отправлять вам зашифрованные OpenPGP сообщения. Публичный ключ OpenPGP опубликован. Включить аккаунт - Удалить свой аккаунт? Удаление аккаунта также сотрёт все историю бесед + Удалить свой аккаунт? Удаление аккаунта также сотрёт все историю бесед. Записать голос XMPP-адрес Заблокировать XMPP-адрес From c7ff399885332f5d03eb1abb453ebb55cbcca142 Mon Sep 17 00:00:00 2001 From: mccode Date: Mon, 21 Apr 2025 10:40:18 +0000 Subject: [PATCH 07/15] Added translation using Weblate (Irish) --- src/conversations/res/values-ga/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/conversations/res/values-ga/strings.xml diff --git a/src/conversations/res/values-ga/strings.xml b/src/conversations/res/values-ga/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..a6b3daec9354f9ae75cdf8d94a67446c6227dd96 --- /dev/null +++ b/src/conversations/res/values-ga/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 36707964a861ee771078a1a62bd9cd30926813d2 Mon Sep 17 00:00:00 2001 From: mccode Date: Mon, 21 Apr 2025 11:03:49 +0000 Subject: [PATCH 08/15] Translated using Weblate (Irish) Currently translated at 23.0% (3 of 13 strings) Translation: Conversations/Android App (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-conversations/ga/ --- src/conversations/res/values-ga/strings.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/conversations/res/values-ga/strings.xml b/src/conversations/res/values-ga/strings.xml index a6b3daec9354f9ae75cdf8d94a67446c6227dd96..ee21d93fd943365566a149cdeee1e54576395699 100644 --- a/src/conversations/res/values-ga/strings.xml +++ b/src/conversations/res/values-ga/strings.xml @@ -1,2 +1,6 @@ - \ No newline at end of file + + Roghnaigh do freastalaí XMPP + Bain úsáid as conversations.im + Oscail cuntas nua + From 08decc8f7e4351aba0214004656fd51abd7b4ad4 Mon Sep 17 00:00:00 2001 From: mccode Date: Mon, 21 Apr 2025 11:10:28 +0000 Subject: [PATCH 09/15] Added translation using Weblate (Irish) --- src/main/res/values-ga/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/main/res/values-ga/strings.xml diff --git a/src/main/res/values-ga/strings.xml b/src/main/res/values-ga/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..a6b3daec9354f9ae75cdf8d94a67446c6227dd96 --- /dev/null +++ b/src/main/res/values-ga/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From c2026c182250f72b8f2a6ec7f2611dfba33927ba Mon Sep 17 00:00:00 2001 From: random_r Date: Tue, 22 Apr 2025 09:25:34 +0000 Subject: [PATCH 10/15] Translated using Weblate (Italian) Currently translated at 100.0% (1076 of 1076 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/it/ --- src/main/res/values-it/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 88b1dc86cfb5973ae8095f1fa030056e691d1ac4..d08acec8155c9c41df7c23d883b30f21b7969d2d 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -1137,4 +1137,7 @@ Copia numero di telefono Copia URI URI copiato negli appunti + Manutenzione programmata + Servizio in manutenzione (problema noto) + Il servizio è programmato per tornare il %s From cd595cf8340c4566d4c972f2105a4698003fc108 Mon Sep 17 00:00:00 2001 From: "lucasmz.dev" Date: Tue, 22 Apr 2025 02:06:31 +0000 Subject: [PATCH 11/15] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (1076 of 1076 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 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index 484b1d650ac0f04a691d019fb76f9e817bdcea26..4e59b3ee8ebf9db90de91a218ba1da9ea29f1f10 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -1141,4 +1141,7 @@ Endereço de e-mail copiado para a área de transferência URI copiada para a área de transferência Número de telefone copiado para a área de transferência + Tempo de inatividade planejado + Serviço caído (problema conhecido) + Se espera que o serviço volte às %s From 4264391f7006fd42d30f6ab9c32c54735540c0c6 Mon Sep 17 00:00:00 2001 From: random_r Date: Tue, 22 Apr 2025 09:29:30 +0000 Subject: [PATCH 12/15] Translated using Weblate (Italian) Currently translated at 100.0% (87 of 87 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/4214204.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/4214204.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/4214204.txt b/fastlane/metadata/android/it-IT/changelogs/4214204.txt new file mode 100644 index 0000000000000000000000000000000000000000..906a67ca8dc814e81241e3998fa8fa9474956a3d --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/4214204.txt @@ -0,0 +1,2 @@ +* Supporto a 'Stato di interruzione del servizio' +* Correzioni di sicurezza minori nella lettura di corpi multipli, id-occupanti e id-stanze From 1b9d74168a650b5e417afeeb07a964fce508457d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 24 Apr 2025 11:28:05 +0200 Subject: [PATCH 13/15] better wording for no channel binding error --- src/main/res/layout/activity_edit_account.xml | 1 + src/main/res/values/strings.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml index f6aff8a4dade446a93df31912b3a47156c656e79..5ef5a469b2e5896c0b1ee264ae39f1e27001f2fe 100644 --- a/src/main/res/layout/activity_edit_account.xml +++ b/src/main/res/layout/activity_edit_account.xml @@ -100,6 +100,7 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="8sp" + android:baselineAligned="false" android:orientation="horizontal" android:weightSum="1"> diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index c61c01a87cbc090d30ee7ce49e320419f9318baf..bf2050357d9e0f571b01e0fad5730e53fad8f014 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -171,7 +171,7 @@ Incompatible client Stream error Stream opening error - Channel binding unavailable + No channel binding Clear text OTR OpenPGP From a82181c70f1ca160dbef3e58b433ef6ce425e1d1 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 24 Apr 2025 13:14:18 +0200 Subject: [PATCH 14/15] refactor HTTP slot requester make use of future api; get rid of http upload legacy --- .../conversations/generator/IqGenerator.java | 21 +-- .../http/HttpConnectionManager.java | 6 +- .../http/HttpUploadConnection.java | 171 +++++++++--------- .../eu/siacs/conversations/http/Method.java | 51 ------ .../conversations/http/SlotRequester.java | 128 +++++-------- .../services/XmppConnectionService.java | 10 + .../eu/siacs/conversations/xml/Namespace.java | 1 - .../xmpp/IqErrorResponseException.java | 33 ++++ .../xmpp/IqResponseException.java | 8 - .../conversations/xmpp/XmppConnection.java | 120 ++++++------ .../android/xmpp/model/upload/Slot.java | 10 + 11 files changed, 269 insertions(+), 290 deletions(-) delete mode 100644 src/main/java/eu/siacs/conversations/http/Method.java create mode 100644 src/main/java/eu/siacs/conversations/xmpp/IqErrorResponseException.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/IqResponseException.java diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 9a4dad2a5af411ae4e916a1349199d2fc5dcdd90..60da01c49a82514d04274e69dd5e25966b246e9c 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -18,6 +18,7 @@ import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.pep.Avatar; import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.upload.Request; import java.nio.ByteBuffer; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; @@ -438,23 +439,13 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public Iq requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) { + public Iq requestHttpUploadSlot( + final Jid host, final DownloadableFile file, final 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())); - request.setAttribute("size", file.getExpectedSize()); - request.setAttribute("content-type", mime); - return packet; - } - - 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())); - request.addChild("size").setContent(String.valueOf(file.getExpectedSize())); - request.addChild("content-type").setContent(mime); + final var request = packet.addExtension(new Request()); + request.setFilename(convertFilename(file.getName())); + request.setSize(file.getExpectedSize()); return packet; } diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java index b1138ba1a1323787cf91ff6d92d48bdf2a0923b0..1af4ba48bd2fe4e17a889e563c9868c86294464d 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java @@ -114,11 +114,7 @@ public class HttpConnectionManager extends AbstractConnectionManager { return; } } - HttpUploadConnection connection = - new HttpUploadConnection( - message, - Method.determine(message.getConversation().getAccount()), - this); + HttpUploadConnection connection = new HttpUploadConnection(message, this); connection.init(delay); this.uploadConnections.add(connection); } diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index ef51b9af43db25f82df4c30514512a07c1dec9fd..9154c2c422783ed65d10cec2451db1c91220d94c 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -3,20 +3,12 @@ package eu.siacs.conversations.http; import static eu.siacs.conversations.utils.Random.SECURE_RANDOM; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.Future; - import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.DownloadableFile; @@ -25,6 +17,10 @@ import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Future; import okhttp3.Call; import okhttp3.Callback; import okhttp3.OkHttpClient; @@ -32,17 +28,14 @@ import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; -public class HttpUploadConnection implements Transferable, AbstractConnectionManager.ProgressListener { +public class HttpUploadConnection + implements Transferable, AbstractConnectionManager.ProgressListener { - static final List WHITE_LISTED_HEADERS = Arrays.asList( - "Authorization", - "Cookie", - "Expires" - ); + static final List WHITE_LISTED_HEADERS = + Arrays.asList("Authorization", "Cookie", "Expires"); private final HttpConnectionManager mHttpConnectionManager; private final XmppConnectionService mXmppConnectionService; - private final Method method; private boolean delayed = false; private DownloadableFile file; private final Message message; @@ -53,9 +46,9 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan private Call mostRecentCall; private ListenableFuture slotFuture; - public HttpUploadConnection(Message message, Method method, HttpConnectionManager httpConnectionManager) { + public HttpUploadConnection( + final Message message, final HttpConnectionManager httpConnectionManager) { this.message = message; - this.method = method; this.mHttpConnectionManager = httpConnectionManager; this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService(); } @@ -88,13 +81,13 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan final ListenableFuture slotFuture = this.slotFuture; if (slotFuture != null && !slotFuture.isDone()) { if (slotFuture.cancel(true)) { - Log.d(Config.LOGTAG,"cancelled slot requester"); + Log.d(Config.LOGTAG, "cancelled slot requester"); } } final Call call = this.mostRecentCall; if (call != null && !call.isCanceled()) { call.cancel(); - Log.d(Config.LOGTAG,"cancelled HTTP request"); + Log.d(Config.LOGTAG, "cancelled HTTP request"); } } @@ -102,8 +95,13 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan finish(); final Call call = this.mostRecentCall; final Future slotFuture = this.slotFuture; - final boolean cancelled = (call != null && call.isCanceled()) || (slotFuture != null && slotFuture.isCancelled()); - mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED, cancelled ? Message.ERROR_MESSAGE_CANCELLED : errorMessage); + final boolean cancelled = + (call != null && call.isCanceled()) + || (slotFuture != null && slotFuture.isCancelled()); + mXmppConnectionService.markMessage( + message, + Message.STATUS_SEND_FAILED, + cancelled ? Message.ERROR_MESSAGE_CANCELLED : errorMessage); } private void finish() { @@ -115,7 +113,8 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan final Account account = message.getConversation().getAccount(); this.file = mXmppConnectionService.getFileBackend().getFile(message, false); final String mime; - if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + if (message.getEncryption() == Message.ENCRYPTION_PGP + || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { mime = "application/pgp-encrypted"; } else { mime = this.file.getMimeType(); @@ -130,75 +129,85 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan } this.file.setExpectedSize(originalFileSize + (file.getKey() != null ? 16 : 0)); message.resetFileParams(); - this.slotFuture = new SlotRequester(mXmppConnectionService).request(method, account, file, mime); - Futures.addCallback(this.slotFuture, new FutureCallback() { - @Override - public void onSuccess(@Nullable SlotRequester.Slot result) { - HttpUploadConnection.this.slot = result; - try { - HttpUploadConnection.this.upload(); - } catch (final Exception e) { - fail(e.getMessage()); - } - } + this.slotFuture = new SlotRequester(mXmppConnectionService).request(account, file, mime); + Futures.addCallback( + this.slotFuture, + new FutureCallback<>() { + @Override + public void onSuccess(@Nullable SlotRequester.Slot result) { + HttpUploadConnection.this.slot = result; + try { + HttpUploadConnection.this.upload(); + } catch (final Exception e) { + fail(e.getMessage()); + } + } - @Override - public void onFailure(@NonNull final Throwable throwable) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to request slot", throwable); - // TODO consider fall back to jingle in 1-on-1 chats with exactly one online presence - fail(throwable.getMessage()); - } - }, MoreExecutors.directExecutor()); + @Override + public void onFailure(@NonNull final Throwable throwable) { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + ": unable to request slot", + throwable); + // TODO consider fall back to jingle in 1-on-1 chats with exactly one online + // presence + fail(throwable.getMessage()); + } + }, + MoreExecutors.directExecutor()); message.setTransferable(this); mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); } private void upload() { - final OkHttpClient client = mHttpConnectionManager.buildHttpClient( - slot.put, - message.getConversation().getAccount(), - 0, - true - ); + final OkHttpClient client = + mHttpConnectionManager.buildHttpClient( + slot.put, message.getConversation().getAccount(), 0, true); final RequestBody requestBody = AbstractConnectionManager.requestBody(file, this); - final Request request = new Request.Builder() - .url(slot.put) - .put(requestBody) - .headers(slot.headers) - .build(); + final Request request = + new Request.Builder().url(slot.put).put(requestBody).headers(slot.headers).build(); Log.d(Config.LOGTAG, "uploading file to " + slot.put); this.mostRecentCall = client.newCall(request); - this.mostRecentCall.enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, IOException e) { - Log.d(Config.LOGTAG, "http upload failed", e); - fail(e.getMessage()); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - final int code = response.code(); - if (code == 200 || code == 201) { - Log.d(Config.LOGTAG, "finished uploading file"); - final String get; - if (key != null) { - get = AesGcmURL.toAesGcmUrl(slot.get.newBuilder().fragment(CryptoHelper.bytesToHex(key)).build()); - } else { - get = slot.get.toString(); + this.mostRecentCall.enqueue( + new Callback() { + @Override + public void onFailure(@NonNull Call call, IOException e) { + Log.d(Config.LOGTAG, "http upload failed", e); + fail(e.getMessage()); } - mXmppConnectionService.getFileBackend().updateFileParams(message, get); - mXmppConnectionService.getFileBackend().updateMediaScanner(file); - finish(); - if (!message.isPrivateMessage()) { - message.setCounterpart(message.getConversation().getJid().asBareJid()); + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + final int code = response.code(); + if (code == 200 || code == 201) { + Log.d(Config.LOGTAG, "finished uploading file"); + final String get; + if (key != null) { + get = + AesGcmURL.toAesGcmUrl( + slot.get + .newBuilder() + .fragment(CryptoHelper.bytesToHex(key)) + .build()); + } else { + get = slot.get.toString(); + } + mXmppConnectionService.getFileBackend().updateFileParams(message, get); + mXmppConnectionService.getFileBackend().updateMediaScanner(file); + finish(); + if (!message.isPrivateMessage()) { + message.setCounterpart( + message.getConversation().getJid().asBareJid()); + } + mXmppConnectionService.resendMessage(message, delayed); + } else { + Log.d( + Config.LOGTAG, + "http upload failed because response code was " + code); + fail("http upload failed because response code was " + code); + } } - mXmppConnectionService.resendMessage(message, delayed); - } else { - Log.d(Config.LOGTAG, "http upload failed because response code was " + code); - fail("http upload failed because response code was " + code); - } - } - }); + }); } public Message getMessage() { @@ -210,4 +219,4 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan this.transmitted = progress; mHttpConnectionManager.updateConversationUi(false); } -} \ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/http/Method.java b/src/main/java/eu/siacs/conversations/http/Method.java deleted file mode 100644 index 47dae2b30cea010df7ba1f6a5c4f004c21fcf96d..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/http/Method.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2018, Daniel Gultsch All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package eu.siacs.conversations.http; - -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.XmppConnection; - -public enum Method { - HTTP_UPLOAD, HTTP_UPLOAD_LEGACY; - - public static Method determine(Account account) { - XmppConnection.Features features = account.getXmppConnection() == null ? null : account.getXmppConnection().getFeatures(); - if (features == null) { - return HTTP_UPLOAD; - } - if (features.useLegacyHttpUpload()) { - return HTTP_UPLOAD_LEGACY; - } else if (features.httpUpload(0)) { - return HTTP_UPLOAD; - } else { - return HTTP_UPLOAD; - } - } -} diff --git a/src/main/java/eu/siacs/conversations/http/SlotRequester.java b/src/main/java/eu/siacs/conversations/http/SlotRequester.java index d76a99fda85eabf39864583d3c2fbc95f105da47..cc8e5ec2b091590bdc9d686987a90f6b64c3ed87 100644 --- a/src/main/java/eu/siacs/conversations/http/SlotRequester.java +++ b/src/main/java/eu/siacs/conversations/http/SlotRequester.java @@ -29,21 +29,22 @@ package eu.siacs.conversations.http; +import android.util.Log; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; - -import java.util.Map; - +import com.google.common.util.concurrent.MoreExecutors; +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.DownloadableFile; -import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.IqResponseException; import eu.siacs.conversations.xmpp.Jid; import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.upload.Header; +import im.conversations.android.xmpp.model.upload.Slot; +import java.util.Map; import okhttp3.Headers; import okhttp3.HttpUrl; @@ -55,83 +56,54 @@ public class SlotRequester { this.service = service; } - public ListenableFuture request(Method method, Account account, DownloadableFile file, String mime) { - if (method == Method.HTTP_UPLOAD_LEGACY) { - final Jid host = account.getXmppConnection().findDiscoItemByFeature(Namespace.HTTP_UPLOAD_LEGACY); - return requestHttpUploadLegacy(account, host, file, mime); - } else { - final Jid host = account.getXmppConnection().findDiscoItemByFeature(Namespace.HTTP_UPLOAD); - return requestHttpUpload(account, host, file, mime); + public ListenableFuture request( + final Account account, final DownloadableFile file, final String mime) { + final var result = + account.getXmppConnection() + .getServiceDiscoveryResultByFeature(Namespace.HTTP_UPLOAD); + if (result == null) { + return Futures.immediateFailedFuture( + new IllegalStateException("No HTTP upload host found")); } + return requestHttpUpload(account, result.getKey(), file, mime); } - private ListenableFuture requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) { - final SettableFuture future = SettableFuture.create(); - 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 { - final String putUrl = slotElement.findChildContent("put"); - final String getUrl = slotElement.findChildContent("get"); - if (getUrl != null && putUrl != null) { - final Slot slot = new Slot( - HttpUrl.get(putUrl), - HttpUrl.get(getUrl), - Headers.of("Content-Type", mime == null ? "application/octet-stream" : mime) - ); - future.set(slot); - return; - } - } catch (final IllegalArgumentException e) { - future.setException(e); - return; - } - } - } - future.setException(new IqResponseException(IqParser.extractErrorMessage(packet))); - }); - return future; - } - - private ListenableFuture requestHttpUpload(Account account, Jid host, DownloadableFile file, String mime) { - final SettableFuture future = SettableFuture.create(); + private ListenableFuture requestHttpUpload( + final Account account, final Jid host, final DownloadableFile file, final String mime) { 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 { - final Element put = slotElement.findChild("put"); - final Element get = slotElement.findChild("get"); - final String putUrl = put == null ? null : put.getAttribute("url"); - final String getUrl = get == null ? null : get.getAttribute("url"); - if (getUrl != null && putUrl != null) { - final ImmutableMap.Builder headers = new ImmutableMap.Builder<>(); - for (final Element child : put.getChildren()) { - if ("header".equals(child.getName())) { - final String name = child.getAttribute("name"); - final String value = child.getContent(); - if (HttpUploadConnection.WHITE_LISTED_HEADERS.contains(name) && value != null && !value.trim().contains("\n")) { - headers.put(name, value.trim()); - } - } - } - headers.put("Content-Type", mime == null ? "application/octet-stream" : mime); - final Slot slot = new Slot(HttpUrl.get(putUrl), HttpUrl.get(getUrl), headers.build()); - future.set(slot); - return; + final var iqFuture = service.sendIqPacket(account, request); + return Futures.transform( + iqFuture, + response -> { + final var slot = + response.getExtension( + im.conversations.android.xmpp.model.upload.Slot.class); + if (slot == null) { + Log.d(Config.LOGTAG, "-->" + response.toString()); + throw new IllegalStateException("Slot not found in IQ response"); + } + final var getUrl = slot.getGetUrl(); + final var put = slot.getPut(); + if (getUrl == null || put == null) { + throw new IllegalStateException("Missing get or put in slot response"); + } + final var putUrl = put.getUrl(); + if (putUrl == null) { + throw new IllegalStateException("Missing put url"); + } + final var headers = new ImmutableMap.Builder(); + for (final Header header : put.getHeaders()) { + final String name = header.getHeaderName(); + final String value = header.getContent(); + if (Strings.isNullOrEmpty(value) || value.contains("\n")) { + continue; } - } catch (final IllegalArgumentException e) { - future.setException(e); - return; + headers.put(name, value.trim()); } - } - } - future.setException(new IqResponseException(IqParser.extractErrorMessage(packet))); - }); - return future; + headers.put("Content-Type", mime == null ? "application/octet-stream" : mime); + return new Slot(putUrl, getUrl, headers.buildKeepingLast()); + }, + MoreExecutors.directExecutor()); } public static class Slot { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 7c627f755d3f0a150b29714aaa1f54b35433b49d..e8fb3ddee314146616b56f84a3a22c388ecf85eb 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -60,6 +60,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; @@ -169,6 +170,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -5958,6 +5960,14 @@ public class XmppConnectionService extends Service { connection.sendCreateAccountWithCaptchaPacket(id, data); } + public ListenableFuture sendIqPacket(final Account account, final Iq request) { + final XmppConnection connection = account.getXmppConnection(); + if (connection == null) { + return Futures.immediateFailedFuture(new TimeoutException()); + } + return connection.sendIqPacket(request); + } + public void sendIqPacket(final Account account, final Iq packet, final Consumer callback) { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 15d88f75aaa6a0800774a945a78b1101754602f6..353898c56b72e48c5b80c6b02869afd4e0ee7872 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -33,7 +33,6 @@ public final class Namespace { public static final String REGISTER_STREAM_FEATURE = "http://jabber.org/features/iq-register"; public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams"; public static final String HTTP_UPLOAD = "urn:xmpp:http:upload:0"; - public static final String HTTP_UPLOAD_LEGACY = "urn:xmpp:http:upload"; public static final String STANZA_IDS = "urn:xmpp:sid:0"; public static final String IDLE = "urn:xmpp:idle:1"; public static final String DATA = "jabber:x:data"; diff --git a/src/main/java/eu/siacs/conversations/xmpp/IqErrorResponseException.java b/src/main/java/eu/siacs/conversations/xmpp/IqErrorResponseException.java new file mode 100644 index 0000000000000000000000000000000000000000..fb8d8e730cd9d4e4b37d3321a084e9d4abc25f93 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/IqErrorResponseException.java @@ -0,0 +1,33 @@ +package eu.siacs.conversations.xmpp; + +import im.conversations.android.xmpp.model.stanza.Iq; + +public class IqErrorResponseException extends Exception { + + private final Iq response; + + public IqErrorResponseException(final Iq response) { + super(message(response)); + this.response = response; + } + + public Iq getResponse() { + return this.response; + } + + public static String message(final Iq iq) { + final var error = iq.getError(); + if (error == null) { + return "missing error element in response"; + } + final var text = error.getTextAsString(); + if (text != null) { + return text; + } + final var condition = error.getCondition(); + if (condition != null) { + return condition.getName(); + } + return "no condition attached to error"; + } +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/IqResponseException.java b/src/main/java/eu/siacs/conversations/xmpp/IqResponseException.java deleted file mode 100644 index 84357eaa3183fde4fac4c513afca6a4e479ad615..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/IqResponseException.java +++ /dev/null @@ -1,8 +0,0 @@ -package eu.siacs.conversations.xmpp; - -public class IqResponseException extends Exception { - - public IqResponseException(final String message) { - super(message); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 11c6df3bbb0995af4311d2c09c9cb98d8444b9f1..e8826c96aa46558a283b2e81adac42076427c1d8 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -21,6 +21,8 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.primitives.Ints; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; import de.gultsch.common.Patterns; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.BuildConfig; @@ -128,6 +130,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -2533,6 +2536,21 @@ public class XmppConnection implements Runnable { return String.format("%s.%s", BuildConfig.APP_NAME, CryptoHelper.random(3)); } + public ListenableFuture sendIqPacket(final Iq request) { + final SettableFuture settable = SettableFuture.create(); + this.sendIqPacket( + request, + response -> { + final var type = response.getType(); + switch (type) { + case RESULT -> settable.set(response); + case TIMEOUT -> settable.setException(new TimeoutException()); + default -> settable.setException(new IqErrorResponseException(response)); + } + }); + return settable; + } + public String sendIqPacket(final Iq packet, final Consumer callback) { packet.setFrom(account.getJid()); return this.sendUnmodifiedIqPacket(packet, callback, false); @@ -2720,6 +2738,18 @@ public class XmppConnection implements Runnable { } } + public Entry getServiceDiscoveryResultByFeature( + final String feature) { + synchronized (this.disco) { + for (final var cursor : this.disco.entrySet()) { + if (cursor.getValue().getFeatures().contains(feature)) { + return cursor; + } + } + return null; + } + } + public Jid findDiscoItemByFeature(final String feature) { final var items = findDiscoItemsByFeature(feature); if (items.isEmpty()) { @@ -3152,66 +3182,54 @@ public class XmppConnection implements Runnable { return HttpUrl.parse(address); } - public boolean httpUpload(long filesize) { + public boolean httpUpload(long fileSize) { if (Config.DISABLE_HTTP_UPLOAD) { return false; + } + final var result = getServiceDiscoveryResultByFeature(Namespace.HTTP_UPLOAD); + if (result == null) { + return false; + } + final long maxSize; + try { + maxSize = + Long.parseLong( + result.getValue() + .getExtendedDiscoInformation( + Namespace.HTTP_UPLOAD, "max-file-size")); + } catch (final Exception e) { + return true; + } + if (fileSize <= maxSize) { + return true; } else { - for (String namespace : - new String[] {Namespace.HTTP_UPLOAD, Namespace.HTTP_UPLOAD_LEGACY}) { - List> items = - findDiscoItemsByFeature(namespace); - if (!items.isEmpty()) { - try { - long maxsize = - Long.parseLong( - items.get(0) - .getValue() - .getExtendedDiscoInformation( - namespace, "max-file-size")); - if (filesize <= maxsize) { - return true; - } else { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": http upload is not available for files with" - + " size " - + filesize - + " (max is " - + maxsize - + ")"); - return false; - } - } catch (Exception e) { - return true; - } - } - } + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": http upload is not available for files with" + + " size " + + fileSize + + " (max is " + + maxSize + + ")"); return false; } } - public boolean useLegacyHttpUpload() { - return findDiscoItemByFeature(Namespace.HTTP_UPLOAD) == null - && findDiscoItemByFeature(Namespace.HTTP_UPLOAD_LEGACY) != null; - } - public long getMaxHttpUploadSize() { - for (String namespace : - new String[] {Namespace.HTTP_UPLOAD, Namespace.HTTP_UPLOAD_LEGACY}) { - List> items = findDiscoItemsByFeature(namespace); - if (!items.isEmpty()) { - try { - return Long.parseLong( - items.get(0) - .getValue() - .getExtendedDiscoInformation(namespace, "max-file-size")); - } catch (Exception e) { - // ignored - } - } + final var result = getServiceDiscoveryResultByFeature(Namespace.HTTP_UPLOAD); + if (result == null) { + return -1; + } + try { + return Long.parseLong( + result.getValue() + .getExtendedDiscoInformation( + Namespace.HTTP_UPLOAD, "max-file-size")); + } catch (final Exception e) { + return -1; + // ignored } - return -1; } public boolean stanzaIds() { 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 index df90157812be59d01d54ceca65ee47b7d9e764ff..9bac8abcab47f4d76cbdc31c8da6c72c7392706e 100644 --- a/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java @@ -2,6 +2,7 @@ package im.conversations.android.xmpp.model.upload; import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.Extension; +import okhttp3.HttpUrl; @XmlElement public class Slot extends Extension { @@ -9,4 +10,13 @@ public class Slot extends Extension { public Slot() { super(Slot.class); } + + public HttpUrl getGetUrl() { + final var get = getExtension(Get.class); + return get == null ? null : get.getUrl(); + } + + public Put getPut() { + return getExtension(Put.class); + } } From 086a72731b75df4735cb4f18f00b66f938997918 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 26 Apr 2025 16:57:42 +0200 Subject: [PATCH 15/15] add mime type and icon for pcap files --- .../siacs/conversations/crypto/PgpEngine.java | 352 ++++++++++-------- .../crypto/axolotl/AxolotlService.java | 8 +- .../crypto/axolotl/XmppAxolotlMessage.java | 80 ++-- .../eu/siacs/conversations/entities/Edit.java | 14 +- .../conversations/entities/MucOptions.java | 6 +- .../entities/PresenceTemplate.java | 143 ++++--- .../conversations/entities/ReadByMarker.java | 320 ++++++++-------- .../conversations/http/SlotRequester.java | 2 +- .../siacs/conversations/parser/IqParser.java | 4 +- .../conversations/parser/MessageParser.java | 1 - .../persistance/DatabaseBackend.java | 4 +- .../persistance/FileBackend.java | 8 +- .../services/CallIntegration.java | 5 +- .../services/MessageArchiveService.java | 236 +++++++----- .../services/XmppConnectionService.java | 6 +- .../ui/ConversationFragment.java | 3 +- .../ui/CreatePrivateGroupChatDialog.java | 44 ++- .../ui/CreatePublicChannelDialog.java | 2 +- .../ui/JoinConferenceDialog.java | 218 ++++++----- .../conversations/ui/ShortcutActivity.java | 54 +-- .../siacs/conversations/ui/XmppActivity.java | 3 +- .../ui/adapter/KnownHostsAdapter.java | 2 +- .../ui/adapter/MediaAdapter.java | 11 +- .../ui/adapter/MessageAdapter.java | 2 +- .../ui/service/CameraManager.java | 144 +++---- .../ui/util/AvatarWorkerTask.java | 42 ++- .../ui/util/MucDetailsContextMenuHelper.java | 133 ++++--- .../conversations/ui/widget/ScannerView.java | 24 +- .../conversations/utils/CursorUtils.java | 4 +- .../siacs/conversations/utils/GeoHelper.java | 9 +- .../conversations/utils/ImStyleParser.java | 28 +- .../siacs/conversations/utils/MimeUtils.java | 8 +- .../siacs/conversations/xmpp/pep/Avatar.java | 3 +- .../android/xmpp/model/muc/Affiliation.java | 2 +- .../android/xmpp/model/stanza/Stanza.java | 5 +- src/main/res/drawable/ic_help_center_48dp.xml | 16 +- src/main/res/drawable/ic_lan_24dp.xml | 12 + 37 files changed, 1105 insertions(+), 853 deletions(-) create mode 100644 src/main/res/drawable/ic_lan_24dp.xml diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java index d3588a9952cc22cb0e5cb795181c7d09dda08f5f..e0ba84c73a08a9e84dd704279675514e34ecfba8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java @@ -3,27 +3,10 @@ package eu.siacs.conversations.crypto; import android.app.PendingIntent; import android.content.Intent; import android.util.Log; - import androidx.annotation.StringRes; - import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.base.Strings; - -import org.openintents.openpgp.OpenPgpError; -import org.openintents.openpgp.OpenPgpSignatureResult; -import org.openintents.openpgp.util.OpenPgpApi; -import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -35,6 +18,18 @@ import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.UiCallback; import eu.siacs.conversations.utils.AsciiArmor; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import org.openintents.openpgp.OpenPgpError; +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.openintents.openpgp.util.OpenPgpApi; +import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; public class PgpEngine { private final OpenPgpApi api; @@ -48,9 +43,20 @@ public class PgpEngine { private static void logError(Account account, OpenPgpError error) { if (error != null) { error.describeContents(); - Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": OpenKeychain error '" + error.getMessage() + "' code=" + error.getErrorId() + " class=" + error.getClass().getName()); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid().toString() + + ": OpenKeychain error '" + + error.getMessage() + + "' code=" + + error.getErrorId() + + " class=" + + error.getClass().getName()); } else { - Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": OpenKeychain error with no message"); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid().toString() + + ": OpenKeychain error with no message"); } } @@ -60,8 +66,7 @@ public class PgpEngine { final Conversation conversation = (Conversation) message.getConversation(); if (conversation.getMode() == Conversation.MODE_SINGLE) { long[] keys = { - conversation.getContact().getPgpKeyId(), - conversation.getAccount().getPgpId() + conversation.getContact().getPgpKeyId(), conversation.getAccount().getPgpId() }; params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys); } else { @@ -78,75 +83,95 @@ public class PgpEngine { } InputStream is = new ByteArrayInputStream(body.getBytes()); final OutputStream os = new ByteArrayOutputStream(); - api.executeApiAsync(params, is, os, result -> { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - try { - os.flush(); - final ArrayList encryptedMessageBody = new ArrayList<>(); - final String[] lines = os.toString().split("\n"); - for (int i = 2; i < lines.length - 1; ++i) { - if (!lines[i].contains("Version")) { - encryptedMessageBody.add(lines[i].trim()); + api.executeApiAsync( + params, + is, + os, + result -> { + switch (result.getIntExtra( + OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + try { + os.flush(); + final ArrayList encryptedMessageBody = + new ArrayList<>(); + final String[] lines = os.toString().split("\n"); + for (int i = 2; i < lines.length - 1; ++i) { + if (!lines[i].contains("Version")) { + encryptedMessageBody.add(lines[i].trim()); + } + } + message.setEncryptedBody( + Joiner.on('\n').join(encryptedMessageBody)); + message.setEncryption(Message.ENCRYPTION_DECRYPTED); + mXmppConnectionService.sendMessage(message); + callback.success(message); + } catch (IOException e) { + callback.error(R.string.openpgp_error, message); } - } - message.setEncryptedBody(Joiner.on('\n').join(encryptedMessageBody)); - message.setEncryption(Message.ENCRYPTION_DECRYPTED); - mXmppConnectionService.sendMessage(message); - callback.success(message); - } catch (IOException e) { - callback.error(R.string.openpgp_error, message); - } - break; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message); - break; - case OpenPgpApi.RESULT_CODE_ERROR: - OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); - String errorMessage = error != null ? error.getMessage() : null; - @StringRes final int res; - if (errorMessage != null && errorMessage.startsWith("Bad key for encryption")) { - res = R.string.bad_key_for_encryption; - } else { - res = R.string.openpgp_error; + break; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequired( + result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), + message); + break; + case OpenPgpApi.RESULT_CODE_ERROR: + OpenPgpError error = + result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); + String errorMessage = error != null ? error.getMessage() : null; + @StringRes final int res; + if (errorMessage != null + && errorMessage.startsWith("Bad key for encryption")) { + res = R.string.bad_key_for_encryption; + } else { + res = R.string.openpgp_error; + } + logError(conversation.getAccount(), error); + callback.error(res, message); + break; } - logError(conversation.getAccount(), error); - callback.error(res, message); - break; - } - }); + }); } else { try { - DownloadableFile inputFile = this.mXmppConnectionService - .getFileBackend().getFile(message, true); - DownloadableFile outputFile = this.mXmppConnectionService - .getFileBackend().getFile(message, false); + DownloadableFile inputFile = + this.mXmppConnectionService.getFileBackend().getFile(message, true); + DownloadableFile outputFile = + this.mXmppConnectionService.getFileBackend().getFile(message, false); outputFile.getParentFile().mkdirs(); outputFile.createNewFile(); final InputStream is = new FileInputStream(inputFile); final OutputStream os = new FileOutputStream(outputFile); - api.executeApiAsync(params, is, os, result -> { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - try { - os.flush(); - } catch (IOException ignored) { - //ignored + api.executeApiAsync( + params, + is, + os, + result -> { + switch (result.getIntExtra( + OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + try { + os.flush(); + } catch (IOException ignored) { + // ignored + } + FileBackend.close(os); + mXmppConnectionService.sendMessage(message); + callback.success(message); + break; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequired( + result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), + message); + break; + case OpenPgpApi.RESULT_CODE_ERROR: + logError( + conversation.getAccount(), + result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); + callback.error(R.string.openpgp_error, message); + break; } - FileBackend.close(os); - mXmppConnectionService.sendMessage(message); - callback.success(message); - break; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message); - break; - case OpenPgpApi.RESULT_CODE_ERROR: - logError(conversation.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); - callback.error(R.string.openpgp_error, message); - break; - } - }); + }); } catch (final IOException e) { callback.error(R.string.openpgp_error, message); } @@ -168,11 +193,11 @@ public class PgpEngine { final InputStream is = new ByteArrayInputStream(Strings.nullToEmpty(status).getBytes()); final ByteArrayOutputStream os = new ByteArrayOutputStream(); final Intent result = api.executeApi(params, is, os); - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, - OpenPgpApi.RESULT_CODE_ERROR)) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: - final OpenPgpSignatureResult sigResult = result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE); - //TODO unsure that sigResult.getResult() is either 1, 2 or 3 + final OpenPgpSignatureResult sigResult = + result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE); + // TODO unsure that sigResult.getResult() is either 1, 2 or 3 if (sigResult != null) { return sigResult.getKeyId(); } else { @@ -190,22 +215,31 @@ public class PgpEngine { public void chooseKey(final Account account, final UiCallback callback) { Intent p = new Intent(); p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID); - api.executeApiAsync(p, null, null, result -> { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - callback.success(account); - return; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account); - return; - case OpenPgpApi.RESULT_CODE_ERROR: - logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); - callback.error(R.string.openpgp_error, account); - } - }); + api.executeApiAsync( + p, + null, + null, + result -> { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + callback.success(account); + return; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequired( + result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account); + return; + case OpenPgpApi.RESULT_CODE_ERROR: + logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); + callback.error(R.string.openpgp_error, account); + } + }); } - public void generateSignature(Intent intent, final Account account, String status, final UiCallback callback) { + public void generateSignature( + Intent intent, + final Account account, + String status, + final UiCallback callback) { if (account.getPgpId() == 0) { return; } @@ -215,70 +249,86 @@ public class PgpEngine { params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId()); InputStream is = new ByteArrayInputStream(status.getBytes()); final OutputStream os = new ByteArrayOutputStream(); - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": signing status message \"" + status + "\""); - api.executeApiAsync(params, is, os, result -> { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - final ArrayList signature = new ArrayList<>(); - try { - os.flush(); - boolean sig = false; - for (final String line : Splitter.on('\n').split(os.toString())) { - if (sig) { - if (line.contains("END PGP SIGNATURE")) { - sig = false; - } else { - if (!line.contains("Version")) { - signature.add(line.trim()); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + ": signing status message \"" + status + "\""); + api.executeApiAsync( + params, + is, + os, + result -> { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + final ArrayList signature = new ArrayList<>(); + try { + os.flush(); + boolean sig = false; + for (final String line : Splitter.on('\n').split(os.toString())) { + if (sig) { + if (line.contains("END PGP SIGNATURE")) { + sig = false; + } else { + if (!line.contains("Version")) { + signature.add(line.trim()); + } + } + } + if (line.contains("BEGIN PGP SIGNATURE")) { + sig = true; } } + } catch (IOException e) { + callback.error(R.string.openpgp_error, null); + return; } - if (line.contains("BEGIN PGP SIGNATURE")) { - sig = true; + callback.success(Joiner.on('\n').join(signature)); + return; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequired( + result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status); + return; + case OpenPgpApi.RESULT_CODE_ERROR: + OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); + if (error != null + && "signing subkey not found!".equals(error.getMessage())) { + callback.error(0, null); + } else { + logError(account, error); + callback.error(R.string.unable_to_connect_to_keychain, null); } - } - } catch (IOException e) { - callback.error(R.string.openpgp_error, null); - return; } - callback.success(Joiner.on('\n').join(signature)); - return; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status); - return; - case OpenPgpApi.RESULT_CODE_ERROR: - OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); - if (error != null && "signing subkey not found!".equals(error.getMessage())) { - callback.error(0, null); - } else { - logError(account, error); - callback.error(R.string.unable_to_connect_to_keychain, null); - } - } - }); + }); } public void hasKey(final Contact contact, final UiCallback callback) { Intent params = new Intent(); params.setAction(OpenPgpApi.ACTION_GET_KEY); params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId()); - api.executeApiAsync(params, null, null, new IOpenPgpCallback() { + api.executeApiAsync( + params, + null, + null, + new IOpenPgpCallback() { - @Override - public void onReturn(Intent result) { - switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { - case OpenPgpApi.RESULT_CODE_SUCCESS: - callback.success(contact); - return; - case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: - callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), contact); - return; - case OpenPgpApi.RESULT_CODE_ERROR: - logError(contact.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); - callback.error(R.string.openpgp_error, contact); - } - } - }); + @Override + public void onReturn(Intent result) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + callback.success(contact); + return; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + callback.userInputRequired( + result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), + contact); + return; + case OpenPgpApi.RESULT_CODE_ERROR: + logError( + contact.getAccount(), + result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); + callback.error(R.string.openpgp_error, contact); + } + } + }); } public PendingIntent getIntentForKey(long pgpKeyId) { @@ -288,6 +338,6 @@ public class PgpEngine { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[0]); Intent result = api.executeApi(params, inputStream, outputStream); - return (PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); + return result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); } } 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 53757f939072473856f5f8ea8cfbf39dabb66c0d..85f5695965b4578923c4cc646d772f5cbf08fdee 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -951,7 +951,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " - + response.toString()); + + response); } pepBroken = true; } @@ -1416,7 +1416,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " - + address.toString() + + address + ", adding to cache..."); XmppAxolotlSession session = new XmppAxolotlSession( @@ -1462,7 +1462,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " - + address.toString() + + address + ", adding to cache..."); XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey); @@ -1538,7 +1538,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " - + address.toString()); + + address); } } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index bba24d90b5a5c8899e7441135b66e29144b12925..8bad6a2fc409c578dc4a98c1ff269b2519c56a78 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -2,7 +2,10 @@ package eu.siacs.conversations.crypto.axolotl; import android.util.Base64; import android.util.Log; - +import eu.siacs.conversations.Config; +import eu.siacs.conversations.utils.Compatibility; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.Jid; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -10,7 +13,6 @@ import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; - import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; @@ -20,11 +22,6 @@ import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.utils.Compatibility; -import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.Jid; - public class XmppAxolotlMessage { public static final String CONTAINERTAG = "encrypted"; private static final String HEADER = "header"; @@ -45,7 +42,8 @@ public class XmppAxolotlMessage { private byte[] authtagPlusInnerKey = null; private byte[] iv = null; - private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException { + private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) + throws IllegalArgumentException { this.from = from; Element header = axolotlMessage.findChild(HEADER); try { @@ -62,7 +60,8 @@ public class XmppAxolotlMessage { int recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); byte[] key = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT); boolean isPreKey = keyElement.getAttributeAsBoolean("prekey"); - this.keys.add(new XmppAxolotlSession.AxolotlKey(recipientId, key, isPreKey)); + this.keys.add( + new XmppAxolotlSession.AxolotlKey(recipientId, key, isPreKey)); } catch (NumberFormatException e) { throw new IllegalArgumentException("invalid remote id"); } @@ -74,11 +73,12 @@ public class XmppAxolotlMessage { iv = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT); break; default: - Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement.toString()); + Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement); break; } } - final Element payloadElement = axolotlMessage.findChildEnsureSingle(PAYLOAD, AxolotlService.PEP_PREFIX); + final Element payloadElement = + axolotlMessage.findChildEnsureSingle(PAYLOAD, AxolotlService.PEP_PREFIX); if (payloadElement != null) { ciphertext = Base64.decode(payloadElement.getContent().trim(), Base64.DEFAULT); } @@ -149,9 +149,16 @@ public class XmppAxolotlMessage { try { SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); - Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); + Cipher cipher = + Compatibility.twentyEight() + ? Cipher.getInstance(CIPHERMODE) + : Cipher.getInstance(CIPHERMODE, PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); - this.ciphertext = cipher.doFinal(Config.OMEMO_PADDING ? getPaddedBytes(plaintext) : plaintext.getBytes()); + this.ciphertext = + cipher.doFinal( + Config.OMEMO_PADDING + ? getPaddedBytes(plaintext) + : plaintext.getBytes()); if (Config.PUT_AUTH_TAG_INTO_KEY && this.ciphertext != null) { this.authtagPlusInnerKey = new byte[16 + 16]; byte[] ciphertext = new byte[this.ciphertext.length - 16]; @@ -160,8 +167,12 @@ public class XmppAxolotlMessage { System.arraycopy(this.innerKey, 0, authtagPlusInnerKey, 0, this.innerKey.length); this.ciphertext = ciphertext; } - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException + } catch (NoSuchAlgorithmException + | NoSuchPaddingException + | InvalidKeyException + | IllegalBlockSizeException + | BadPaddingException + | NoSuchProviderException | InvalidAlgorithmParameterException e) { throw new CryptoFailedException(e); } @@ -220,7 +231,8 @@ public class XmppAxolotlMessage { return encryptionElement; } - private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { + private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) + throws CryptoFailedException { ArrayList possibleKeys = new ArrayList<>(); for (XmppAxolotlSession.AxolotlKey key : keys) { if (key.deviceId == sourceDeviceId) { @@ -233,17 +245,22 @@ public class XmppAxolotlMessage { return session.processReceiving(possibleKeys); } - XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { - return new XmppAxolotlKeyTransportMessage(session.getFingerprint(), unpackKey(session, sourceDeviceId), getIV()); + XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) + throws CryptoFailedException { + return new XmppAxolotlKeyTransportMessage( + session.getFingerprint(), unpackKey(session, sourceDeviceId), getIV()); } - public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { + public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) + throws CryptoFailedException { XmppAxolotlPlaintextMessage plaintextMessage = null; byte[] key = unpackKey(session, sourceDeviceId); if (key != null) { try { if (key.length < 32) { - throw new OutdatedSenderException("Key did not contain auth tag. Sender needs to update their OMEMO client"); + throw new OutdatedSenderException( + "Key did not contain auth tag. Sender needs to update their OMEMO" + + " client"); } final int authTagLength = key.length - 16; byte[] newCipherText = new byte[key.length - 16 + ciphertext.length]; @@ -254,18 +271,28 @@ public class XmppAxolotlMessage { ciphertext = newCipherText; key = newKey; - final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); + final Cipher cipher = + Compatibility.twentyEight() + ? Cipher.getInstance(CIPHERMODE) + : Cipher.getInstance(CIPHERMODE, PROVIDER); SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); String plaintext = new String(cipher.doFinal(ciphertext)); - plaintextMessage = new XmppAxolotlPlaintextMessage(Config.OMEMO_PADDING ? plaintext.trim() : plaintext, session.getFingerprint()); - - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | InvalidAlgorithmParameterException | IllegalBlockSizeException - | BadPaddingException | NoSuchProviderException e) { + plaintextMessage = + new XmppAxolotlPlaintextMessage( + Config.OMEMO_PADDING ? plaintext.trim() : plaintext, + session.getFingerprint()); + + } catch (NoSuchAlgorithmException + | NoSuchPaddingException + | InvalidKeyException + | InvalidAlgorithmParameterException + | IllegalBlockSizeException + | BadPaddingException + | NoSuchProviderException e) { throw new CryptoFailedException(e); } } @@ -285,7 +312,6 @@ public class XmppAxolotlMessage { return plaintext; } - public String getFingerprint() { return fingerprint; } diff --git a/src/main/java/eu/siacs/conversations/entities/Edit.java b/src/main/java/eu/siacs/conversations/entities/Edit.java index a3865bdd08c808b10bd0d10968f6e724a15092d3..2357dc0105db72f4156da8f13c6e55fe9b4079f6 100644 --- a/src/main/java/eu/siacs/conversations/entities/Edit.java +++ b/src/main/java/eu/siacs/conversations/entities/Edit.java @@ -1,12 +1,12 @@ package eu.siacs.conversations.entities; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.util.ArrayList; -import java.util.List; - public class Edit { private final String editedId; @@ -45,7 +45,8 @@ public class Edit { private static Edit fromJson(JSONObject jsonObject) throws JSONException { String edited = jsonObject.has("edited_id") ? jsonObject.getString("edited_id") : null; - String serverMsgId = jsonObject.has("server_msg_id") ? jsonObject.getString("server_msg_id") : null; + String serverMsgId = + jsonObject.has("server_msg_id") ? jsonObject.getString("server_msg_id") : null; return new Edit(edited, serverMsgId); } @@ -83,9 +84,8 @@ public class Edit { Edit edit = (Edit) o; - if (editedId != null ? !editedId.equals(edit.editedId) : edit.editedId != null) - return false; - return serverMsgId != null ? serverMsgId.equals(edit.serverMsgId) : edit.serverMsgId == null; + if (!Objects.equals(editedId, edit.editedId)) return false; + return Objects.equals(serverMsgId, edit.serverMsgId); } @Override diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 1b68acf0fceb5810d9e0df31ea333a80f242a9d0..7ba6f8a23276a8878c87e7735d2c10ff7871dd8b 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Set; public class MucOptions { @@ -919,9 +920,8 @@ public class MucOptions { if (role != user.role) return false; if (affiliation != user.affiliation) return false; - if (realJid != null ? !realJid.equals(user.realJid) : user.realJid != null) - return false; - return fullJid != null ? fullJid.equals(user.fullJid) : user.fullJid == null; + if (!Objects.equals(realJid, user.realJid)) return false; + return Objects.equals(fullJid, user.fullJid); } public boolean isDomain() { diff --git a/src/main/java/eu/siacs/conversations/entities/PresenceTemplate.java b/src/main/java/eu/siacs/conversations/entities/PresenceTemplate.java index 91741e674aaf93f1219ac4056f93e19adb6a754c..958891d34d4b8915fdc3309a4827c37b41001347 100644 --- a/src/main/java/eu/siacs/conversations/entities/PresenceTemplate.java +++ b/src/main/java/eu/siacs/conversations/entities/PresenceTemplate.java @@ -2,80 +2,77 @@ package eu.siacs.conversations.entities; import android.content.ContentValues; import android.database.Cursor; - +import java.util.Objects; public class PresenceTemplate extends AbstractEntity { - public static final String TABELNAME = "presence_templates"; - public static final String LAST_USED = "last_used"; - public static final String MESSAGE = "message"; - public static final String STATUS = "status"; - - private long lastUsed = 0; - private String statusMessage; - private Presence.Status status = Presence.Status.ONLINE; - - public PresenceTemplate(Presence.Status status, String statusMessage) { - this.status = status; - this.statusMessage = statusMessage; - this.lastUsed = System.currentTimeMillis(); - this.uuid = java.util.UUID.randomUUID().toString(); - } - - private PresenceTemplate() { - - } - - @Override - public ContentValues getContentValues() { - final String show = status.toShowString(); - ContentValues values = new ContentValues(); - values.put(LAST_USED, lastUsed); - values.put(MESSAGE, statusMessage); - values.put(STATUS, show == null ? "" : show); - values.put(UUID, uuid); - return values; - } - - public static PresenceTemplate fromCursor(Cursor cursor) { - PresenceTemplate template = new PresenceTemplate(); - template.uuid = cursor.getString(cursor.getColumnIndex(UUID)); - template.lastUsed = cursor.getLong(cursor.getColumnIndex(LAST_USED)); - template.statusMessage = cursor.getString(cursor.getColumnIndex(MESSAGE)); - template.status = Presence.Status.fromShowString(cursor.getString(cursor.getColumnIndex(STATUS))); - return template; - } - - public Presence.Status getStatus() { - return status; - } - - public String getStatusMessage() { - return statusMessage; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - PresenceTemplate template = (PresenceTemplate) o; - - if (statusMessage != null ? !statusMessage.equals(template.statusMessage) : template.statusMessage != null) - return false; - return status == template.status; - - } - - @Override - public int hashCode() { - int result = statusMessage != null ? statusMessage.hashCode() : 0; - result = 31 * result + status.hashCode(); - return result; - } - - @Override - public String toString() { - return statusMessage; - } + public static final String TABELNAME = "presence_templates"; + public static final String LAST_USED = "last_used"; + public static final String MESSAGE = "message"; + public static final String STATUS = "status"; + + private long lastUsed = 0; + private String statusMessage; + private Presence.Status status = Presence.Status.ONLINE; + + public PresenceTemplate(Presence.Status status, String statusMessage) { + this.status = status; + this.statusMessage = statusMessage; + this.lastUsed = System.currentTimeMillis(); + this.uuid = java.util.UUID.randomUUID().toString(); + } + + private PresenceTemplate() {} + + @Override + public ContentValues getContentValues() { + final String show = status.toShowString(); + ContentValues values = new ContentValues(); + values.put(LAST_USED, lastUsed); + values.put(MESSAGE, statusMessage); + values.put(STATUS, show == null ? "" : show); + values.put(UUID, uuid); + return values; + } + + public static PresenceTemplate fromCursor(Cursor cursor) { + PresenceTemplate template = new PresenceTemplate(); + template.uuid = cursor.getString(cursor.getColumnIndex(UUID)); + template.lastUsed = cursor.getLong(cursor.getColumnIndex(LAST_USED)); + template.statusMessage = cursor.getString(cursor.getColumnIndex(MESSAGE)); + template.status = + Presence.Status.fromShowString(cursor.getString(cursor.getColumnIndex(STATUS))); + return template; + } + + public Presence.Status getStatus() { + return status; + } + + public String getStatusMessage() { + return statusMessage; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PresenceTemplate template = (PresenceTemplate) o; + + if (!Objects.equals(statusMessage, template.statusMessage)) return false; + return status == template.status; + } + + @Override + public int hashCode() { + int result = statusMessage != null ? statusMessage.hashCode() : 0; + result = 31 * result + status.hashCode(); + return result; + } + + @Override + public String toString() { + return statusMessage; + } } diff --git a/src/main/java/eu/siacs/conversations/entities/ReadByMarker.java b/src/main/java/eu/siacs/conversations/entities/ReadByMarker.java index 4b1254f0761bee318809cd90f0d5ded8b5a71260..f8e0ace1a1f90c4a587349d1e4ebd2a72eb1bfe4 100644 --- a/src/main/java/eu/siacs/conversations/entities/ReadByMarker.java +++ b/src/main/java/eu/siacs/conversations/entities/ReadByMarker.java @@ -1,171 +1,167 @@ package eu.siacs.conversations.entities; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - +import eu.siacs.conversations.xmpp.Jid; import java.util.Collection; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; - -import eu.siacs.conversations.xmpp.Jid; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; public class ReadByMarker { - private ReadByMarker() { - - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ReadByMarker marker = (ReadByMarker) o; - - if (fullJid != null ? !fullJid.equals(marker.fullJid) : marker.fullJid != null) - return false; - return realJid != null ? realJid.equals(marker.realJid) : marker.realJid == null; - - } - - @Override - public int hashCode() { - int result = fullJid != null ? fullJid.hashCode() : 0; - result = 31 * result + (realJid != null ? realJid.hashCode() : 0); - return result; - } - - private Jid fullJid; - private Jid realJid; - - public Jid getFullJid() { - return fullJid; - } - - public Jid getRealJid() { - return realJid; - } - - public JSONObject toJson() { - JSONObject jsonObject = new JSONObject(); - if (fullJid != null) { - try { - jsonObject.put("fullJid", fullJid.toString()); - } catch (JSONException e) { - //ignore - } - } - if (realJid != null) { - try { - jsonObject.put("realJid", realJid.toString()); - } catch (JSONException e) { - //ignore - } - } - return jsonObject; - } - - public static Set fromJson(final JSONArray jsonArray) { - final Set readByMarkers = new CopyOnWriteArraySet<>(); - for(int i = 0; i < jsonArray.length(); ++i) { - try { - readByMarkers.add(fromJson(jsonArray.getJSONObject(i))); - } catch (JSONException e) { - //ignored - } - } - return readByMarkers; - } - - public static ReadByMarker from(Jid fullJid, Jid realJid) { - final ReadByMarker marker = new ReadByMarker(); - marker.fullJid = fullJid; - marker.realJid = realJid == null ? null : realJid.asBareJid(); - return marker; - } - - public static ReadByMarker from(Message message) { - final ReadByMarker marker = new ReadByMarker(); - marker.fullJid = message.getCounterpart(); - marker.realJid = message.getTrueCounterpart(); - return marker; - } - - public static ReadByMarker from(MucOptions.User user) { - final ReadByMarker marker = new ReadByMarker(); - marker.fullJid = user.getFullJid(); - marker.realJid = user.getRealJid(); - return marker; - } - - public static Set from(Collection users) { - final Set markers = new CopyOnWriteArraySet<>(); - for(MucOptions.User user : users) { - markers.add(from(user)); - } - return markers; - } - - public static ReadByMarker fromJson(JSONObject jsonObject) { - ReadByMarker marker = new ReadByMarker(); - try { - marker.fullJid = Jid.of(jsonObject.getString("fullJid")); - } catch (JSONException | IllegalArgumentException e) { - marker.fullJid = null; - } - try { - marker.realJid = Jid.of(jsonObject.getString("realJid")); - } catch (JSONException | IllegalArgumentException e) { - marker.realJid = null; - } - return marker; - } - - public static Set fromJsonString(String json) { - try { - return fromJson(new JSONArray(json)); - } catch (final JSONException | NullPointerException e) { - return new CopyOnWriteArraySet<>(); - } - } - - public static JSONArray toJson(final Set readByMarkers) { - final JSONArray jsonArray = new JSONArray(); - for(final ReadByMarker marker : readByMarkers) { - jsonArray.put(marker.toJson()); - } - return jsonArray; - } - - public static boolean contains(ReadByMarker needle, final Set readByMarkers) { - for(final ReadByMarker marker : readByMarkers) { - if (marker.realJid != null && needle.realJid != null) { - if (marker.realJid.asBareJid().equals(needle.realJid.asBareJid())) { - return true; - } - } else if (marker.fullJid != null && needle.fullJid != null) { - if (marker.fullJid.equals(needle.fullJid)) { - return true; - } - } - } - return false; - } - - public static boolean allUsersRepresented(Collection users, Set markers) { - for(MucOptions.User user : users) { - if (!contains(from(user),markers)) { - return false; - } - } - return true; - } - - public static boolean allUsersRepresented(Collection users, Set markers, ReadByMarker marker) { - final Set markersCopy = new CopyOnWriteArraySet<>(markers); - markersCopy.add(marker); - return allUsersRepresented(users, markersCopy); - } - + private ReadByMarker() {} + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ReadByMarker marker = (ReadByMarker) o; + + if (!Objects.equals(fullJid, marker.fullJid)) return false; + return Objects.equals(realJid, marker.realJid); + } + + @Override + public int hashCode() { + int result = fullJid != null ? fullJid.hashCode() : 0; + result = 31 * result + (realJid != null ? realJid.hashCode() : 0); + return result; + } + + private Jid fullJid; + private Jid realJid; + + public Jid getFullJid() { + return fullJid; + } + + public Jid getRealJid() { + return realJid; + } + + public JSONObject toJson() { + JSONObject jsonObject = new JSONObject(); + if (fullJid != null) { + try { + jsonObject.put("fullJid", fullJid.toString()); + } catch (JSONException e) { + // ignore + } + } + if (realJid != null) { + try { + jsonObject.put("realJid", realJid.toString()); + } catch (JSONException e) { + // ignore + } + } + return jsonObject; + } + + public static Set fromJson(final JSONArray jsonArray) { + final Set readByMarkers = new CopyOnWriteArraySet<>(); + for (int i = 0; i < jsonArray.length(); ++i) { + try { + readByMarkers.add(fromJson(jsonArray.getJSONObject(i))); + } catch (JSONException e) { + // ignored + } + } + return readByMarkers; + } + + public static ReadByMarker from(Jid fullJid, Jid realJid) { + final ReadByMarker marker = new ReadByMarker(); + marker.fullJid = fullJid; + marker.realJid = realJid == null ? null : realJid.asBareJid(); + return marker; + } + + public static ReadByMarker from(Message message) { + final ReadByMarker marker = new ReadByMarker(); + marker.fullJid = message.getCounterpart(); + marker.realJid = message.getTrueCounterpart(); + return marker; + } + + public static ReadByMarker from(MucOptions.User user) { + final ReadByMarker marker = new ReadByMarker(); + marker.fullJid = user.getFullJid(); + marker.realJid = user.getRealJid(); + return marker; + } + + public static Set from(Collection users) { + final Set markers = new CopyOnWriteArraySet<>(); + for (MucOptions.User user : users) { + markers.add(from(user)); + } + return markers; + } + + public static ReadByMarker fromJson(JSONObject jsonObject) { + ReadByMarker marker = new ReadByMarker(); + try { + marker.fullJid = Jid.of(jsonObject.getString("fullJid")); + } catch (JSONException | IllegalArgumentException e) { + marker.fullJid = null; + } + try { + marker.realJid = Jid.of(jsonObject.getString("realJid")); + } catch (JSONException | IllegalArgumentException e) { + marker.realJid = null; + } + return marker; + } + + public static Set fromJsonString(String json) { + try { + return fromJson(new JSONArray(json)); + } catch (final JSONException | NullPointerException e) { + return new CopyOnWriteArraySet<>(); + } + } + + public static JSONArray toJson(final Set readByMarkers) { + final JSONArray jsonArray = new JSONArray(); + for (final ReadByMarker marker : readByMarkers) { + jsonArray.put(marker.toJson()); + } + return jsonArray; + } + + public static boolean contains(ReadByMarker needle, final Set readByMarkers) { + for (final ReadByMarker marker : readByMarkers) { + if (marker.realJid != null && needle.realJid != null) { + if (marker.realJid.asBareJid().equals(needle.realJid.asBareJid())) { + return true; + } + } else if (marker.fullJid != null && needle.fullJid != null) { + if (marker.fullJid.equals(needle.fullJid)) { + return true; + } + } + } + return false; + } + + public static boolean allUsersRepresented( + Collection users, Set markers) { + for (MucOptions.User user : users) { + if (!contains(from(user), markers)) { + return false; + } + } + return true; + } + + public static boolean allUsersRepresented( + Collection users, Set markers, ReadByMarker marker) { + final Set markersCopy = new CopyOnWriteArraySet<>(markers); + markersCopy.add(marker); + return allUsersRepresented(users, markersCopy); + } } diff --git a/src/main/java/eu/siacs/conversations/http/SlotRequester.java b/src/main/java/eu/siacs/conversations/http/SlotRequester.java index cc8e5ec2b091590bdc9d686987a90f6b64c3ed87..5bb5d7772b253857e7cc8eab81c60c4aedb9d1c2 100644 --- a/src/main/java/eu/siacs/conversations/http/SlotRequester.java +++ b/src/main/java/eu/siacs/conversations/http/SlotRequester.java @@ -79,7 +79,7 @@ public class SlotRequester { response.getExtension( im.conversations.android.xmpp.model.upload.Slot.class); if (slot == null) { - Log.d(Config.LOGTAG, "-->" + response.toString()); + Log.d(Config.LOGTAG, "-->" + response); throw new IllegalStateException("Slot not found in IQ response"); } final var getUrl = slot.getGetUrl(); diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 14efffd532f2846c388e364e9929a098bcf9a6db..7a7f9c3a0aa031e431991ef9398cde8b02e1d9c1 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -189,7 +189,7 @@ public class IqParser extends AbstractParser implements Consumer { + "Encountered invalid node in PEP (" + e.getMessage() + "):" - + device.toString() + + device + ", skipping..."); } } @@ -313,7 +313,7 @@ public class IqParser extends AbstractParser implements Consumer { AxolotlService.LOGPREFIX + " : " + "could not parse preKeyId from preKey " - + preKeyPublicElement.toString()); + + preKeyPublicElement); } catch (Throwable e) { Log.e( Config.LOGTAG, diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 83e817c2b5c95eaf42ab315d3b3291dc943bb0f4..ecf20048ce57c285c6be3b369369e7cce7dc0027 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -332,7 +332,6 @@ public class MessageParser extends AbstractParser } } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) { final var retractions = items.getRetractions(); - ; for (final var item : items.getItemMap(Conference.class).entrySet()) { final Bookmark bookmark = Bookmark.parseFromItem(item.getKey(), item.getValue(), account); diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index efbc1d7227ea3531b426b7405c4a42c1e6f325dc..e4787999c275e3fd7e546e70d3e0eccb081fd630 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -346,7 +346,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { final SQLiteDatabase db = getWritableDatabase(); final Stopwatch stopwatch = Stopwatch.createStarted(); db.execSQL(COPY_PREEXISTING_ENTRIES); - Log.d(Config.LOGTAG, "rebuilt message index in " + stopwatch.stop().toString()); + Log.d(Config.LOGTAG, "rebuilt message index in " + stopwatch.stop()); } public static synchronized DatabaseBackend getInstance(Context context) { @@ -1561,7 +1561,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + " is not null and conversationUuid=(select uuid from conversations where" + " accountUuid=? and (contactJid=? or contactJid like ?)) order by" + " timeSent desc"; - final String[] args = {account, jid.toString(), jid.toString() + "/%"}; + final String[] args = {account, jid.toString(), jid + "/%"}; Cursor cursor = db.rawQuery(SQL + (limit > 0 ? " limit " + limit : ""), args); List filesPaths = new ArrayList<>(); while (cursor.moveToNext()) { diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index c7f4e0d24555bf573bf7833b3cef04fd798f1f0c..2b95a1cf8de62a2661cfd72015f32eb8e65d4ac9 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -425,9 +425,7 @@ public class FileBackend { } public static void updateFileParams(Message message, String url, long size) { - final StringBuilder body = new StringBuilder(); - body.append(url).append('|').append(size); - message.setBody(body.toString()); + message.setBody(url + '|' + size); } public Bitmap getPreviewForUri(Attachment attachment, int size, boolean cacheOnly) { @@ -1244,7 +1242,7 @@ public class FileBackend { Log.d(Config.LOGTAG, "settled on char length " + chars + " with quality=" + quality); final Avatar avatar = new Avatar(); avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest()); - avatar.image = new String(mByteArrayOutputStream.toByteArray()); + avatar.image = mByteArrayOutputStream.toString(); if (format.equals(Bitmap.CompressFormat.WEBP)) { avatar.type = "image/webp"; } else if (format.equals(Bitmap.CompressFormat.JPEG)) { @@ -1289,7 +1287,7 @@ public class FileBackend { os.flush(); os.close(); avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest()); - avatar.image = new String(mByteArrayOutputStream.toByteArray()); + avatar.image = mByteArrayOutputStream.toString(); avatar.height = options.outHeight; avatar.width = options.outWidth; avatar.type = options.outMimeType; diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index 747f79fb31f2c14167ee9f6b00f6f5c0b57fec1f..3684e4f3ecd932005cb776452b8194ebc5da6848 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegration.java @@ -540,10 +540,7 @@ public class CallIntegration extends Connection { return false; } // SailfishOS's AppSupport do not support Call Integration - if (Build.MODEL.endsWith("(AppSupport)")) { - return false; - } - return true; + return !Build.MODEL.endsWith("(AppSupport)"); } public static boolean notSelfManaged(final Context context) { diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index 2c342e6728a985b12a1bb7d28b8a41ec63e132cc..11580e20fa4442cb5ffc9ec0c0c3ffa7d19d9abc 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -3,15 +3,7 @@ package eu.siacs.conversations.services; import static eu.siacs.conversations.utils.Random.SECURE_RANDOM; import android.util.Log; - import androidx.annotation.NonNull; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -25,6 +17,11 @@ import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.mam.MamReference; import im.conversations.android.xmpp.model.stanza.Iq; import im.conversations.android.xmpp.model.stanza.Message; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { @@ -90,7 +87,6 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } return null; } - } MessageArchiveService(final XmppConnectionService service) { @@ -106,11 +102,13 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } } } - MamReference mamReference = MamReference.max( - mXmppConnectionService.databaseBackend.getLastMessageReceived(account), - mXmppConnectionService.databaseBackend.getLastClearDate(account) - ); - mamReference = MamReference.max(mamReference, mXmppConnectionService.getAutomaticMessageDeletionDate()); + MamReference mamReference = + MamReference.max( + mXmppConnectionService.databaseBackend.getLastMessageReceived(account), + mXmppConnectionService.databaseBackend.getLastClearDate(account)); + mamReference = + MamReference.max( + mamReference, mXmppConnectionService.getAutomaticMessageDeletionDate()); long endCatchup = account.getXmppConnection().getLastSessionEstablished(); final Query query; if (mamReference.getTimestamp() == 0) { @@ -119,7 +117,9 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { long startCatchup = endCatchup - Config.MAM_MAX_CATCHUP; List conversations = mXmppConnectionService.getConversations(); for (Conversation conversation : conversations) { - if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account && startCatchup > conversation.getLastMessageTransmitted().getTimestamp()) { + if (conversation.getMode() == Conversation.MODE_SINGLE + && conversation.getAccount() == account + && startCatchup > conversation.getLastMessageTransmitted().getTimestamp()) { this.query(conversation, startCatchup, true); } } @@ -134,27 +134,21 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } void catchupMUC(final Conversation conversation) { - if (conversation.getLastMessageTransmitted().getTimestamp() < 0 && conversation.countMessages() == 0) { - query(conversation, - new MamReference(0), - 0, - true); + if (conversation.getLastMessageTransmitted().getTimestamp() < 0 + && conversation.countMessages() == 0) { + query(conversation, new MamReference(0), 0, true); } else { - query(conversation, - conversation.getLastMessageTransmitted(), - 0, - true); + query(conversation, conversation.getLastMessageTransmitted(), 0, true); } } public Query query(final Conversation conversation) { - if (conversation.getLastMessageTransmitted().getTimestamp() < 0 && conversation.countMessages() == 0) { - return query(conversation, - new MamReference(0), - System.currentTimeMillis(), - false); + if (conversation.getLastMessageTransmitted().getTimestamp() < 0 + && conversation.countMessages() == 0) { + return query(conversation, new MamReference(0), System.currentTimeMillis(), false); } else { - return query(conversation, + return query( + conversation, conversation.getLastMessageTransmitted(), conversation.getAccount().getXmppConnection().getLastSessionEstablished(), false); @@ -168,7 +162,11 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } else { synchronized (this.queries) { for (Query query : this.queries) { - if (query.getAccount() == account && query.isCatchup() && ((conversation.getMode() == Conversation.MODE_SINGLE && query.getWith() == null) || query.getConversation() == conversation)) { + if (query.getAccount() == account + && query.isCatchup() + && ((conversation.getMode() == Conversation.MODE_SINGLE + && query.getWith() == null) + || query.getConversation() == conversation)) { return true; } } @@ -178,21 +176,33 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } public Query query(final Conversation conversation, long end, boolean allowCatchup) { - return this.query(conversation, conversation.getLastMessageTransmitted(), end, allowCatchup); + return this.query( + conversation, conversation.getLastMessageTransmitted(), end, allowCatchup); } - public Query query(Conversation conversation, MamReference start, long end, boolean allowCatchup) { + public Query query( + Conversation conversation, MamReference start, long end, boolean allowCatchup) { synchronized (this.queries) { final Query query; - final MamReference startActual = MamReference.max(start, mXmppConnectionService.getAutomaticMessageDeletionDate()); + final MamReference startActual = + MamReference.max( + start, mXmppConnectionService.getAutomaticMessageDeletionDate()); if (start.getTimestamp() == 0) { query = new Query(conversation, startActual, end, false); query.reference = conversation.getFirstMamReference(); } else { if (allowCatchup) { - MamReference maxCatchup = MamReference.max(startActual, System.currentTimeMillis() - Config.MAM_MAX_CATCHUP); + MamReference maxCatchup = + MamReference.max( + startActual, + System.currentTimeMillis() - Config.MAM_MAX_CATCHUP); if (maxCatchup.greaterThan(startActual)) { - Query reverseCatchup = new Query(conversation, startActual, maxCatchup.getTimestamp(), false); + Query reverseCatchup = + new Query( + conversation, + startActual, + maxCatchup.getTimestamp(), + false); this.queries.add(reverseCatchup); this.execute(reverseCatchup); } @@ -231,40 +241,57 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { if (account.getStatus() == Account.State.ONLINE) { final Conversation conversation = query.getConversation(); if (conversation != null && conversation.getStatus() == Conversation.STATUS_ARCHIVED) { - 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 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() == Iq.Type.TIMEOUT) { - synchronized (this.queries) { - this.queries.remove(query); - if (query.hasCallback()) { - query.callback(false); + throw new IllegalStateException( + "Attempted to run MAM query for archived conversation"); + } + Log.d( + Config.LOGTAG, + account.getJid().asBareJid().toString() + ": running mam query " + query); + 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() == Iq.Type.TIMEOUT) { + synchronized (this.queries) { + this.queries.remove(query); + if (query.hasCallback()) { + query.callback(false); + } + } + } else if (p.getType() == Iq.Type.RESULT && fin != null) { + final boolean running; + synchronized (this.queries) { + running = this.queries.contains(query); + } + if (running) { + processFin(query, fin); + } else { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": ignoring MAM iq result because query had been" + + " killed"); + } + } else if (p.getType() == Iq.Type.RESULT && query.isLegacy()) { + // do nothing + } else { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid().toString() + + ": error executing mam: " + + p); + try { + finalizeQuery(query, true); + } catch (final IllegalStateException e) { + // ignored + } } - } - } else if (p.getType() == Iq.Type.RESULT && fin != null) { - final boolean running; - synchronized (this.queries) { - running = this.queries.contains(query); - } - if (running) { - processFin(query, fin); - } else { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring MAM iq result because query had been killed"); - } - } else if (p.getType() == Iq.Type.RESULT && query.isLegacy()) { - //do nothing - } else { - Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": error executing mam: " + p.toString()); - try { - finalizeQuery(query, true); - } catch (final IllegalStateException e) { - //ignored - } - } - }); + }); } else { synchronized (this.pendingQueries) { this.pendingQueries.add(query); @@ -320,7 +347,8 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { for (Query query : queries) { if (query.account == conversation.getAccount() && query.isCatchup()) { final Jid with = query.getWith() == null ? null : query.getWith().asBareJid(); - if ((conversation.getMode() == Conversational.MODE_SINGLE && with == null) || (conversation.getJid().asBareJid().equals(with))) { + if ((conversation.getMode() == Conversational.MODE_SINGLE && with == null) + || (conversation.getJid().asBareJid().equals(with))) { return true; } } @@ -329,7 +357,8 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { return false; } - boolean queryInProgress(Conversation conversation, XmppConnectionService.OnMoreMessagesLoaded callback) { + boolean queryInProgress( + Conversation conversation, XmppConnectionService.OnMoreMessagesLoaded callback) { synchronized (this.queries) { for (Query query : queries) { if (query.conversation == conversation) { @@ -361,12 +390,15 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { String count = set == null ? null : set.findChildContent("count"); Element first = set == null ? null : set.findChild("first"); Element relevant = query.getPagingOrder() == PagingOrder.NORMAL ? last : first; - boolean abort = (!query.isCatchup() && query.getTotalCount() >= Config.PAGE_SIZE) || query.getTotalCount() >= Config.MAM_MAX_MESSAGES; + boolean abort = + (!query.isCatchup() && query.getTotalCount() >= Config.PAGE_SIZE) + || query.getTotalCount() >= Config.MAM_MAX_MESSAGES; if (query.getConversation() != null) { query.getConversation().setFirstMamReference(first == null ? null : first.getContent()); } if (complete || relevant == null || abort) { - //TODO: FIX done logic to look at complete. using count is probably unreliable because it can be ommited and doesn’t work with paging. + // TODO: FIX done logic to look at complete. using count is probably unreliable because + // it can be ommited and doesn’t work with paging. boolean done; if (query.isCatchup()) { done = false; @@ -384,9 +416,21 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { done = done || (query.getActualMessageCount() == 0 && !query.isCatchup()); this.finalizeQuery(query, done); - Log.d(Config.LOGTAG, query.getAccount().getJid().asBareJid() + ": finished mam after " + query.getTotalCount() + "(" + query.getActualMessageCount() + ") messages. messages left=" + !done + " count=" + count); + Log.d( + Config.LOGTAG, + query.getAccount().getJid().asBareJid() + + ": finished mam after " + + query.getTotalCount() + + "(" + + query.getActualMessageCount() + + ") messages. messages left=" + + !done + + " count=" + + count); if (query.isCatchup() && query.getActualMessageCount() > 0) { - mXmppConnectionService.getNotificationService().finishBacklog(true, query.getAccount()); + mXmppConnectionService + .getNotificationService() + .finishBacklog(true, query.getAccount()); } processPostponed(query); } else { @@ -407,11 +451,15 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { void kill(final Conversation conversation) { final ArrayList toBeKilled = new ArrayList<>(); synchronized (this.pendingQueries) { - for (final Iterator iterator = this.pendingQueries.iterator(); iterator.hasNext(); ) { + for (final Iterator iterator = this.pendingQueries.iterator(); + iterator.hasNext(); ) { final Query query = iterator.next(); if (query.getConversation() == conversation) { iterator.remove(); - Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": killed pending MAM query for archived conversation"); + Log.d( + Config.LOGTAG, + conversation.getAccount().getJid().asBareJid() + + ": killed pending MAM query for archived conversation"); } } } @@ -428,7 +476,9 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } private void kill(Query query) { - Log.d(Config.LOGTAG, query.getAccount().getJid().asBareJid() + ": killing mam query prematurely"); + Log.d( + Config.LOGTAG, + query.getAccount().getJid().asBareJid() + ": killing mam query prematurely"); query.callback = null; this.finalizeQuery(query, false); if (query.isCatchup() && query.getActualMessageCount() > 0) { @@ -440,11 +490,20 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { private void processPostponed(Query query) { query.account.getAxolotlService().processPostponed(); query.pendingReceiptRequests.removeAll(query.receiptRequests); - Log.d(Config.LOGTAG, query.getAccount().getJid().asBareJid() + ": found " + query.pendingReceiptRequests.size() + " pending receipt requests"); + Log.d( + Config.LOGTAG, + query.getAccount().getJid().asBareJid() + + ": found " + + query.pendingReceiptRequests.size() + + " pending receipt requests"); Iterator iterator = query.pendingReceiptRequests.iterator(); while (iterator.hasNext()) { ReceiptRequest rr = iterator.next(); - mXmppConnectionService.sendMessagePacket(query.account, mXmppConnectionService.getMessageGenerator().received(query.account, rr.getJid(), rr.getId())); + mXmppConnectionService.sendMessagePacket( + query.account, + mXmppConnectionService + .getMessageGenerator() + .received(query.account, rr.getJid(), rr.getId())); iterator.remove(); } } @@ -465,7 +524,8 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { @Override public void onAdvancedStreamFeaturesAvailable(Account account) { - if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { + if (account.getXmppConnection() != null + && account.getXmppConnection().getFeatures().mam()) { this.catchup(account); } } @@ -492,9 +552,12 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { private boolean catchup = true; public final Version version; - Query(Conversation conversation, MamReference start, long end, boolean catchup) { - this(conversation.getAccount(), Version.get(conversation.getAccount(), conversation), catchup ? start : start.timeOnly(), end); + this( + conversation.getAccount(), + Version.get(conversation.getAccount(), conversation), + catchup ? start : start.timeOnly(), + end); this.conversation = conversation; this.pagingOrder = catchup ? PagingOrder.NORMAL : PagingOrder.REVERSE; this.catchup = catchup; @@ -517,7 +580,12 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } private Query page(String reference) { - Query query = new Query(this.account, this.version, new MamReference(this.start, reference), this.end); + Query query = + new Query( + this.account, + this.version, + new MamReference(this.start, reference), + this.end); query.conversation = conversation; query.totalCount = totalCount; query.actualCount = actualCount; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index e8fb3ddee314146616b56f84a3a22c388ecf85eb..ff38d2ed57d57de0d58ef928beba1465e282182e 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -507,7 +507,7 @@ public class XmppConnectionService extends Service { private LruCache mBitmapCache; private final BroadcastReceiver mInternalEventReceiver = new InternalEventReceiver(); private final BroadcastReceiver mInternalRestrictedEventReceiver = - new RestrictedEventReceiver(Arrays.asList(TorServiceUtils.ACTION_STATUS)); + new RestrictedEventReceiver(List.of(TorServiceUtils.ACTION_STATUS)); private final BroadcastReceiver mInternalScreenEventReceiver = new InternalEventReceiver(); private static String generateFetchKey(Account account, final Avatar avatar) { @@ -4535,7 +4535,7 @@ public class XmppConnectionService extends Service { if (packet.getType() == Iq.Type.RESULT) { callback.onPushSucceeded(); } else { - Log.d(Config.LOGTAG, "failed: " + packet.toString()); + Log.d(Config.LOGTAG, "failed: " + packet); callback.onPushFailed(); } } @@ -5125,7 +5125,7 @@ public class XmppConnectionService extends Service { if (error == null) { Log.d(Config.LOGTAG, ERROR + "(server error)"); } else { - Log.d(Config.LOGTAG, ERROR + error.toString()); + Log.d(Config.LOGTAG, ERROR + error); } } if (callback != null) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index d58c7a0fc524730e2297edd7865e431a099f4f5f..459ef2f0be6248eab923febd321012cefe683722 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -531,8 +531,7 @@ public class ConversationFragment extends XmppFragment @Override public void onClick(View v) { Object tag = v.getTag(); - if (tag instanceof SendButtonAction) { - SendButtonAction action = (SendButtonAction) tag; + if (tag instanceof SendButtonAction action) { switch (action) { case TAKE_PHOTO: case RECORD_VIDEO: diff --git a/src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java b/src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java index ae3406a2d821b17fd6782bd236120b8a09dca636..88c378f9ba370a7ae03f59d6a7aeb6b43c31eba1 100644 --- a/src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java @@ -4,20 +4,15 @@ import android.app.Dialog; import android.content.Context; import android.os.Bundle; import android.widget.AutoCompleteTextView; - import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; import androidx.fragment.app.DialogFragment; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import java.util.ArrayList; -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.DialogCreateConferenceBinding; import eu.siacs.conversations.ui.util.DelayedHintHelper; +import java.util.ArrayList; +import java.util.List; public class CreatePrivateGroupChatDialog extends DialogFragment { @@ -41,23 +36,36 @@ public class CreatePrivateGroupChatDialog extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); + final MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(requireActivity()); builder.setTitle(R.string.create_private_group_chat); - final DialogCreateConferenceBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.dialog_create_conference, null, false); + final DialogCreateConferenceBinding binding = + DataBindingUtil.inflate( + getActivity().getLayoutInflater(), + R.layout.dialog_create_conference, + null, + false); ArrayList mActivatedAccounts = getArguments().getStringArrayList(ACCOUNTS_LIST_KEY); - StartConversationActivity.populateAccountSpinner(getActivity(), mActivatedAccounts, binding.account); + StartConversationActivity.populateAccountSpinner( + getActivity(), mActivatedAccounts, binding.account); builder.setView(binding.getRoot()); - builder.setPositiveButton(R.string.choose_participants, (dialog, which) -> mListener.onCreateDialogPositiveClick(binding.account, binding.groupChatName.getText().toString().trim())); + builder.setPositiveButton( + R.string.choose_participants, + (dialog, which) -> + mListener.onCreateDialogPositiveClick( + binding.account, + binding.groupChatName.getText().toString().trim())); builder.setNegativeButton(R.string.cancel, null); DelayedHintHelper.setHint(R.string.providing_a_name_is_optional, binding.groupChatName); - binding.groupChatName.setOnEditorActionListener((v, actionId, event) -> { - mListener.onCreateDialogPositiveClick(binding.account, binding.groupChatName.getText().toString().trim()); - return true; - }); + binding.groupChatName.setOnEditorActionListener( + (v, actionId, event) -> { + mListener.onCreateDialogPositiveClick( + binding.account, binding.groupChatName.getText().toString().trim()); + return true; + }); return builder.create(); } - public interface CreateConferenceDialogListener { void onCreateDialogPositiveClick(AutoCompleteTextView spinner, String subject); } @@ -68,8 +76,8 @@ public class CreatePrivateGroupChatDialog extends DialogFragment { try { mListener = (CreateConferenceDialogListener) context; } catch (ClassCastException e) { - throw new ClassCastException(context.toString() - + " must implement CreateConferenceDialogListener"); + throw new ClassCastException( + context + " must implement CreateConferenceDialogListener"); } } diff --git a/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java b/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java index b12aba750cb0accb3a0ace23dccf6000425ba759..7351823f6708e2e87ad6073f092e64da84b04f8e 100644 --- a/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java @@ -288,7 +288,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke mListener = (CreatePublicChannelDialogListener) context; } catch (ClassCastException e) { throw new ClassCastException( - context.toString() + " must implement CreateConferenceDialogListener"); + context + " must implement CreateConferenceDialogListener"); } } diff --git a/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java b/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java index 849554d87eadb5730a5479abefa496397bd38760..34261fa4d31fb1f0ae6e2b502ea51e5735882dc8 100644 --- a/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java @@ -6,118 +6,136 @@ import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.widget.AutoCompleteTextView; -import android.widget.Spinner; - import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; import androidx.fragment.app.DialogFragment; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputLayout; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.DialogJoinConferenceBinding; import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; import eu.siacs.conversations.ui.interfaces.OnBackendConnected; import eu.siacs.conversations.ui.util.DelayedHintHelper; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; public class JoinConferenceDialog extends DialogFragment implements OnBackendConnected { - private static final String PREFILLED_JID_KEY = "prefilled_jid"; - private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list"; - private JoinConferenceDialogListener mListener; - private KnownHostsAdapter knownHostsAdapter; - - public static JoinConferenceDialog newInstance(String prefilledJid, List accounts) { - JoinConferenceDialog dialog = new JoinConferenceDialog(); - Bundle bundle = new Bundle(); - bundle.putString(PREFILLED_JID_KEY, prefilledJid); - bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList) accounts); - dialog.setArguments(bundle); - return dialog; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - setRetainInstance(true); - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); - builder.setTitle(R.string.join_public_channel); - final DialogJoinConferenceBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.dialog_join_conference, null, false); - DelayedHintHelper.setHint(R.string.channel_full_jid_example, binding.jid); - this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.item_autocomplete); - binding.jid.setAdapter(knownHostsAdapter); - String prefilledJid = getArguments().getString(PREFILLED_JID_KEY); - if (prefilledJid != null) { - binding.jid.append(prefilledJid); - } - StartConversationActivity.populateAccountSpinner(getActivity(), getArguments().getStringArrayList(ACCOUNTS_LIST_KEY), binding.account); - builder.setView(binding.getRoot()); - builder.setPositiveButton(R.string.join, null); - builder.setNegativeButton(R.string.cancel, null); - AlertDialog dialog = builder.create(); - dialog.show(); - dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(view -> mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.accountJidLayout, binding.jid)); - binding.jid.setOnEditorActionListener((v, actionId, event) -> { - mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.accountJidLayout, binding.jid); - return true; - }); - return dialog; - } - - @Override - public void onBackendConnected() { - refreshKnownHosts(); - } - - private void refreshKnownHosts() { - Activity activity = getActivity(); - if (activity instanceof XmppActivity) { - Collection hosts = ((XmppActivity) activity).xmppConnectionService.getKnownConferenceHosts(); - this.knownHostsAdapter.refresh(hosts); - } - } - - @Override - public void onAttach(@NonNull final Context context) { - super.onAttach(context); - try { - mListener = (JoinConferenceDialogListener) context; - } catch (ClassCastException e) { - throw new ClassCastException(context.toString() - + " must implement JoinConferenceDialogListener"); - } - } - - @Override - public void onDestroyView() { - Dialog dialog = getDialog(); - if (dialog != null && getRetainInstance()) { - dialog.setDismissMessage(null); - } - super.onDestroyView(); - } - - @Override - public void onStart() { - super.onStart(); - final Activity activity = getActivity(); - if (activity instanceof XmppActivity && ((XmppActivity) activity).xmppConnectionService != null) { - refreshKnownHosts(); - } - } - - public interface JoinConferenceDialogListener { - void onJoinDialogPositiveClick(Dialog dialog, AutoCompleteTextView spinner, TextInputLayout jidLayout, AutoCompleteTextView jid); - } + private static final String PREFILLED_JID_KEY = "prefilled_jid"; + private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list"; + private JoinConferenceDialogListener mListener; + private KnownHostsAdapter knownHostsAdapter; + + public static JoinConferenceDialog newInstance(String prefilledJid, List accounts) { + JoinConferenceDialog dialog = new JoinConferenceDialog(); + Bundle bundle = new Bundle(); + bundle.putString(PREFILLED_JID_KEY, prefilledJid); + bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList) accounts); + dialog.setArguments(bundle); + return dialog; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setRetainInstance(true); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(requireActivity()); + builder.setTitle(R.string.join_public_channel); + final DialogJoinConferenceBinding binding = + DataBindingUtil.inflate( + getActivity().getLayoutInflater(), + R.layout.dialog_join_conference, + null, + false); + DelayedHintHelper.setHint(R.string.channel_full_jid_example, binding.jid); + this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.item_autocomplete); + binding.jid.setAdapter(knownHostsAdapter); + String prefilledJid = getArguments().getString(PREFILLED_JID_KEY); + if (prefilledJid != null) { + binding.jid.append(prefilledJid); + } + StartConversationActivity.populateAccountSpinner( + getActivity(), + getArguments().getStringArrayList(ACCOUNTS_LIST_KEY), + binding.account); + builder.setView(binding.getRoot()); + builder.setPositiveButton(R.string.join, null); + builder.setNegativeButton(R.string.cancel, null); + AlertDialog dialog = builder.create(); + dialog.show(); + dialog.getButton(DialogInterface.BUTTON_POSITIVE) + .setOnClickListener( + view -> + mListener.onJoinDialogPositiveClick( + dialog, + binding.account, + binding.accountJidLayout, + binding.jid)); + binding.jid.setOnEditorActionListener( + (v, actionId, event) -> { + mListener.onJoinDialogPositiveClick( + dialog, binding.account, binding.accountJidLayout, binding.jid); + return true; + }); + return dialog; + } + + @Override + public void onBackendConnected() { + refreshKnownHosts(); + } + + private void refreshKnownHosts() { + Activity activity = getActivity(); + if (activity instanceof XmppActivity) { + Collection hosts = + ((XmppActivity) activity).xmppConnectionService.getKnownConferenceHosts(); + this.knownHostsAdapter.refresh(hosts); + } + } + + @Override + public void onAttach(@NonNull final Context context) { + super.onAttach(context); + try { + mListener = (JoinConferenceDialogListener) context; + } catch (ClassCastException e) { + throw new ClassCastException(context + " must implement JoinConferenceDialogListener"); + } + } + + @Override + public void onDestroyView() { + Dialog dialog = getDialog(); + if (dialog != null && getRetainInstance()) { + dialog.setDismissMessage(null); + } + super.onDestroyView(); + } + + @Override + public void onStart() { + super.onStart(); + final Activity activity = getActivity(); + if (activity instanceof XmppActivity + && ((XmppActivity) activity).xmppConnectionService != null) { + refreshKnownHosts(); + } + } + + public interface JoinConferenceDialogListener { + void onJoinDialogPositiveClick( + Dialog dialog, + AutoCompleteTextView spinner, + TextInputLayout jidLayout, + AutoCompleteTextView jid); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java b/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java index 66b3fb886f93bdf5da2cce289f5eb37250fa8f96..eab1a705082feea4fca22488d21fd7abf436448b 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java @@ -5,50 +5,57 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.inputmethod.InputMethodManager; - import androidx.appcompat.app.ActionBar; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.ListItem; +import java.util.Collections; +import java.util.List; public class ShortcutActivity extends AbstractSearchableListItemActivity { - private static final List BLACKLISTED_ACTIVITIES = Arrays.asList("com.teslacoilsw.launcher.ChooseActionIntentActivity"); + private static final List BLACKLISTED_ACTIVITIES = + List.of("com.teslacoilsw.launcher.ChooseActionIntentActivity"); @Override - protected void refreshUiReal() { - - } + protected void refreshUiReal() {} @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getListView().setOnItemClickListener((parent, view, position, id) -> { - - final ComponentName callingActivity = getCallingActivity(); + getListView() + .setOnItemClickListener( + (parent, view, position, id) -> { + final ComponentName callingActivity = getCallingActivity(); - final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); + final InputMethodManager imm = + (InputMethodManager) + getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow( + getSearchEditText().getWindowToken(), + InputMethodManager.HIDE_IMPLICIT_ONLY); - ListItem listItem = getListItems().get(position); - final boolean legacy = BLACKLISTED_ACTIVITIES.contains(callingActivity == null ? null : callingActivity.getClassName()); - Intent shortcut = xmppConnectionService.getShortcutService().createShortcut(((Contact) listItem), legacy); - setResult(RESULT_OK,shortcut); - finish(); - }); + ListItem listItem = getListItems().get(position); + final boolean legacy = + BLACKLISTED_ACTIVITIES.contains( + callingActivity == null + ? null + : callingActivity.getClassName()); + Intent shortcut = + xmppConnectionService + .getShortcutService() + .createShortcut(((Contact) listItem), legacy); + setResult(RESULT_OK, shortcut); + finish(); + }); } @Override public void onStart() { super.onStart(); ActionBar bar = getSupportActionBar(); - if(bar != null){ + if (bar != null) { bar.setTitle(R.string.create_shortcut); } } @@ -63,8 +70,7 @@ public class ShortcutActivity extends AbstractSearchableListItemActivity { for (final Account account : xmppConnectionService.getAccounts()) { if (account.isEnabled()) { for (final Contact contact : account.getRoster().getContacts()) { - if (contact.showInContactList() - && contact.match(this, needle)) { + if (contact.showInContactList() && contact.match(this, needle)) { getListItems().add(contact); } } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 8a3fc8a543f6fdd4f72a765029b2f89f8b8a9b15..d597599bd1c97a52441136275eb3da664479e19e 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -181,8 +181,7 @@ public abstract class XmppActivity extends ActionBarActivity { private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { if (imageView != null) { final Drawable drawable = imageView.getDrawable(); - if (drawable instanceof AsyncDrawable) { - final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + if (drawable instanceof AsyncDrawable asyncDrawable) { return asyncDrawable.getBitmapWorkerTask(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java index 88f7636bfa612fecfe44661104fd717af37896e6..fd9869f6bfa3b8ac7caedba76183d9844bfcbbc3 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java @@ -30,7 +30,7 @@ public class KnownHostsAdapter extends ArrayAdapter { final String local = split[0].toLowerCase(Locale.ENGLISH); if (Config.QUICKSY_DOMAIN != null && E164_PATTERN.matcher(local).matches()) { - builder.add(local + '@' + Config.QUICKSY_DOMAIN.toString()); + builder.add(local + '@' + Config.QUICKSY_DOMAIN); } else { for (String domain : domains) { builder.add(local + '@' + domain); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java index 411dadf584295480e2eb8e29290df6a0ecdafdf3..4869f0d2d3a09cf63e4ee798655837ddf4031040 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java @@ -77,10 +77,8 @@ public class MediaAdapter extends RecyclerView.Adapter { private abstract static class MessageItemViewHolder /*extends RecyclerView.ViewHolder*/ { - private View itemView; + private final View itemView; private MessageItemViewHolder(@NonNull View itemView) { this.itemView = itemView; diff --git a/src/main/java/eu/siacs/conversations/ui/service/CameraManager.java b/src/main/java/eu/siacs/conversations/ui/service/CameraManager.java index f3af5d3ed0823f374cb4b9bd4aafa0337b4a905e..a0b3e216f7706dad5bc936bca5a5f38aa694764e 100644 --- a/src/main/java/eu/siacs/conversations/ui/service/CameraManager.java +++ b/src/main/java/eu/siacs/conversations/ui/service/CameraManager.java @@ -25,9 +25,8 @@ import android.hardware.Camera.CameraInfo; import android.hardware.Camera.PreviewCallback; import android.util.Log; import android.view.TextureView; - import com.google.zxing.PlanarYUVLuminanceSource; - +import eu.siacs.conversations.Config; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -35,8 +34,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -import eu.siacs.conversations.Config; - /** * @author Andreas Schildbach */ @@ -69,7 +66,10 @@ public final class CameraManager { return cameraInfo.orientation; } - public Camera open(final TextureView textureView, final int displayOrientation, final boolean continuousAutoFocus) + public Camera open( + final TextureView textureView, + final int displayOrientation, + final boolean continuousAutoFocus) throws IOException { final int cameraId = determineCameraId(); Camera.getCameraInfo(cameraId, cameraInfo); @@ -80,8 +80,7 @@ public final class CameraManager { camera.setDisplayOrientation((720 - displayOrientation - cameraInfo.orientation) % 360); else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) camera.setDisplayOrientation((720 - displayOrientation + cameraInfo.orientation) % 360); - else - throw new IllegalStateException("facing: " + cameraInfo.facing); + else throw new IllegalStateException("facing: " + cameraInfo.facing); camera.setPreviewTexture(textureView.getSurfaceTexture()); @@ -105,18 +104,22 @@ public final class CameraManager { boolean isTexturePortrait = width < height; boolean isCameraPortrait = cameraResolution.width < cameraResolution.height; if (isTexturePortrait == isCameraPortrait) { - widthFactor = (float)cameraResolution.width / width; - heightFactor = (float)cameraResolution.height / height; + widthFactor = (float) cameraResolution.width / width; + heightFactor = (float) cameraResolution.height / height; orientedFrame = new Rect(frame); } else { - widthFactor = (float)cameraResolution.width / height; - heightFactor = (float)cameraResolution.height / width; + widthFactor = (float) cameraResolution.width / height; + heightFactor = (float) cameraResolution.height / width; // Swap X and Y coordinates to flip frame to the same orientation as cameraResolution orientedFrame = new Rect(frame.top, frame.left, frame.bottom, frame.right); } - framePreview = new RectF(orientedFrame.left * widthFactor, orientedFrame.top * heightFactor, - orientedFrame.right * widthFactor, orientedFrame.bottom * heightFactor); + framePreview = + new RectF( + orientedFrame.left * widthFactor, + orientedFrame.top * heightFactor, + orientedFrame.right * widthFactor, + orientedFrame.bottom * heightFactor); final String savedParameters = parameters == null ? null : parameters.flatten(); @@ -130,7 +133,7 @@ public final class CameraManager { camera.setParameters(parameters2); setDesiredCameraParameters(camera, cameraResolution, continuousAutoFocus); } catch (final RuntimeException x2) { - Log.d(Config.LOGTAG,"problem setting camera parameters", x2); + Log.d(Config.LOGTAG, "problem setting camera parameters", x2); } } } @@ -139,7 +142,7 @@ public final class CameraManager { camera.startPreview(); return camera; } catch (final RuntimeException x) { - Log.w(Config.LOGTAG,"something went wrong while starting camera preview", x); + Log.w(Config.LOGTAG, "something went wrong while starting camera preview", x); camera.release(); throw x; } @@ -152,15 +155,13 @@ public final class CameraManager { // prefer back-facing camera for (int i = 0; i < cameraCount; i++) { Camera.getCameraInfo(i, cameraInfo); - if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) - return i; + if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) return i; } // fall back to front-facing camera for (int i = 0; i < cameraCount; i++) { Camera.getCameraInfo(i, cameraInfo); - if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) - return i; + if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) return i; } return -1; @@ -171,29 +172,28 @@ public final class CameraManager { try { camera.stopPreview(); } catch (final RuntimeException x) { - Log.w(Config.LOGTAG,"something went wrong while stopping camera preview", x); + Log.w(Config.LOGTAG, "something went wrong while stopping camera preview", x); } camera.release(); } } - private static final Comparator numPixelComparator = new Comparator() { - @Override - public int compare(final Camera.Size size1, final Camera.Size size2) { - final int pixels1 = size1.height * size1.width; - final int pixels2 = size2.height * size2.width; - - if (pixels1 < pixels2) - return 1; - else if (pixels1 > pixels2) - return -1; - else - return 0; - } - }; + private static final Comparator numPixelComparator = + new Comparator() { + @Override + public int compare(final Camera.Size size1, final Camera.Size size2) { + final int pixels1 = size1.height * size1.width; + final int pixels2 = size2.height * size2.width; + + if (pixels1 < pixels2) return 1; + else if (pixels1 > pixels2) return -1; + else return 0; + } + }; - private static Camera.Size findBestPreviewSizeValue(final Camera.Parameters parameters, int width, int height) { + private static Camera.Size findBestPreviewSizeValue( + final Camera.Parameters parameters, int width, int height) { if (height > width) { final int temp = width; width = height; @@ -203,11 +203,11 @@ public final class CameraManager { final float screenAspectRatio = (float) width / (float) height; final List rawSupportedSizes = parameters.getSupportedPreviewSizes(); - if (rawSupportedSizes == null) - return parameters.getPreviewSize(); + if (rawSupportedSizes == null) return parameters.getPreviewSize(); // sort by size, descending - final List supportedPreviewSizes = new ArrayList(rawSupportedSizes); + final List supportedPreviewSizes = + new ArrayList(rawSupportedSizes); Collections.sort(supportedPreviewSizes, numPixelComparator); Camera.Size bestSize = null; @@ -217,8 +217,7 @@ public final class CameraManager { final int realWidth = supportedPreviewSize.width; final int realHeight = supportedPreviewSize.height; final int realPixels = realWidth * realHeight; - if (realPixels < MIN_PREVIEW_PIXELS || realPixels > MAX_PREVIEW_PIXELS) - continue; + if (realPixels < MIN_PREVIEW_PIXELS || realPixels > MAX_PREVIEW_PIXELS) continue; final boolean isCandidatePortrait = realWidth < realHeight; final int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth; @@ -234,27 +233,32 @@ public final class CameraManager { } } - if (bestSize != null) - return bestSize; - else - return parameters.getPreviewSize(); + if (bestSize != null) return bestSize; + else return parameters.getPreviewSize(); } @SuppressLint("InlinedApi") - private static void setDesiredCameraParameters(final Camera camera, final Camera.Size cameraResolution, + private static void setDesiredCameraParameters( + final Camera camera, + final Camera.Size cameraResolution, final boolean continuousAutoFocus) { final Camera.Parameters parameters = camera.getParameters(); - if (parameters == null) - return; + if (parameters == null) return; final List supportedFocusModes = parameters.getSupportedFocusModes(); - final String focusMode = continuousAutoFocus - ? findValue(supportedFocusModes, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE, - Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, Camera.Parameters.FOCUS_MODE_AUTO, - Camera.Parameters.FOCUS_MODE_MACRO) - : findValue(supportedFocusModes, Camera.Parameters.FOCUS_MODE_AUTO, Camera.Parameters.FOCUS_MODE_MACRO); - if (focusMode != null) - parameters.setFocusMode(focusMode); + final String focusMode = + continuousAutoFocus + ? findValue( + supportedFocusModes, + Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE, + Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, + Camera.Parameters.FOCUS_MODE_AUTO, + Camera.Parameters.FOCUS_MODE_MACRO) + : findValue( + supportedFocusModes, + Camera.Parameters.FOCUS_MODE_AUTO, + Camera.Parameters.FOCUS_MODE_MACRO); + if (focusMode != null) parameters.setFocusMode(focusMode); parameters.setPreviewSize(cameraResolution.width, cameraResolution.height); @@ -265,26 +269,31 @@ public final class CameraManager { try { camera.setOneShotPreviewCallback(callback); } catch (final RuntimeException x) { - Log.d(Config.LOGTAG,"problem requesting preview frame, callback won't be called", x); + Log.d(Config.LOGTAG, "problem requesting preview frame, callback won't be called", x); } } public PlanarYUVLuminanceSource buildLuminanceSource(final byte[] data) { - return new PlanarYUVLuminanceSource(data, cameraResolution.width, cameraResolution.height, - (int) framePreview.left, (int) framePreview.top, (int) framePreview.width(), - (int) framePreview.height(), false); + return new PlanarYUVLuminanceSource( + data, + cameraResolution.width, + cameraResolution.height, + (int) framePreview.left, + (int) framePreview.top, + (int) framePreview.width(), + (int) framePreview.height(), + false); } public void setTorch(final boolean enabled) { - if (enabled != getTorchEnabled(camera)) - setTorchEnabled(camera, enabled); + if (enabled != getTorchEnabled(camera)) setTorchEnabled(camera, enabled); } private static boolean getTorchEnabled(final Camera camera) { final Camera.Parameters parameters = camera.getParameters(); if (parameters != null) { final String flashMode = camera.getParameters().getFlashMode(); - return flashMode != null && (Camera.Parameters.FLASH_MODE_ON.equals(flashMode) + return (Camera.Parameters.FLASH_MODE_ON.equals(flashMode) || Camera.Parameters.FLASH_MODE_TORCH.equals(flashMode)); } @@ -298,10 +307,12 @@ public final class CameraManager { if (supportedFlashModes != null) { final String flashMode; if (enabled) - flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_TORCH, - Camera.Parameters.FLASH_MODE_ON); - else - flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_OFF); + flashMode = + findValue( + supportedFlashModes, + Camera.Parameters.FLASH_MODE_TORCH, + Camera.Parameters.FLASH_MODE_ON); + else flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_OFF); if (flashMode != null) { camera.cancelAutoFocus(); // autofocus can cause conflict @@ -314,8 +325,7 @@ public final class CameraManager { private static String findValue(final Collection values, final String... valuesToFind) { for (final String valueToFind : valuesToFind) - if (values.contains(valueToFind)) - return valueToFind; + if (values.contains(valueToFind)) return valueToFind; return null; } diff --git a/src/main/java/eu/siacs/conversations/ui/util/AvatarWorkerTask.java b/src/main/java/eu/siacs/conversations/ui/util/AvatarWorkerTask.java index f5331c7a618341304ceafc263bee2706c2cf9067..9b5e749ca669b8fa01aa98e67f8b31c96c4ff279 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/AvatarWorkerTask.java +++ b/src/main/java/eu/siacs/conversations/ui/util/AvatarWorkerTask.java @@ -7,22 +7,18 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.widget.ImageView; - import androidx.annotation.DimenRes; - -import java.lang.ref.WeakReference; -import java.util.concurrent.RejectedExecutionException; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.ui.XmppActivity; +import java.lang.ref.WeakReference; +import java.util.concurrent.RejectedExecutionException; public class AvatarWorkerTask extends AsyncTask { private final WeakReference imageViewReference; private AvatarService.Avatarable avatarable = null; - private @DimenRes - final int size; + private @DimenRes final int size; public AvatarWorkerTask(ImageView imageView, @DimenRes int size) { imageViewReference = new WeakReference<>(imageView); @@ -36,7 +32,8 @@ public class AvatarWorkerTask extends AsyncTask= 0) { - message.setSpan(new TypefaceSpan("monospace"), start, start + jid.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + message.setSpan( + new TypefaceSpan("monospace"), + start, + start + jid.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } builder.setMessage(message); builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.ban_now, (dialog, which) -> { - activity.xmppConnectionService.changeAffiliationInConference(conversation, user.getRealJid(), MucOptions.Affiliation.OUTCAST, onAffiliationChanged); - if (user.getRole() != MucOptions.Role.NONE) { - activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE); - } - }); + builder.setPositiveButton( + R.string.ban_now, + (dialog, which) -> { + activity.xmppConnectionService.changeAffiliationInConference( + conversation, + user.getRealJid(), + MucOptions.Affiliation.OUTCAST, + onAffiliationChanged); + if (user.getRole() != MucOptions.Role.NONE) { + activity.xmppConnectionService.changeRoleInConference( + conversation, user.getName(), MucOptions.Role.NONE); + } + }); builder.create().show(); } } private static void startConversation(User user, XmppActivity activity) { if (user.getRealJid() != null) { - Conversation newConversation = activity.xmppConnectionService.findOrCreateConversation(user.getAccount(), user.getRealJid().asBareJid(), false, true); + Conversation newConversation = + activity.xmppConnectionService.findOrCreateConversation( + user.getAccount(), user.getRealJid().asBareJid(), false, true); activity.switchToConversation(newConversation); } } -} \ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/ui/widget/ScannerView.java b/src/main/java/eu/siacs/conversations/ui/widget/ScannerView.java index b755ae516e7ba7b9ff24155583ce3f9b0868a344..826a637d256be8834c466089278d4bc473aea775 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/ScannerView.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/ScannerView.java @@ -28,22 +28,18 @@ import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; - import androidx.core.content.ContextCompat; - import com.google.zxing.ResultPoint; - +import eu.siacs.conversations.R; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import eu.siacs.conversations.R; - /** * @author Andreas Schildbach */ public class ScannerView extends View { - private static final long LASER_ANIMATION_DELAY_MS = 100l; + private static final long LASER_ANIMATION_DELAY_MS = 100L; private static final int DOT_OPACITY = 0xa0; private static final int DOT_TTL_MS = 500; @@ -81,8 +77,12 @@ public class ScannerView extends View { dotPaint.setAntiAlias(true); } - public void setFraming(final Rect frame, final RectF framePreview, final int displayRotation, - final int cameraRotation, final boolean cameraFlip) { + public void setFraming( + final Rect frame, + final RectF framePreview, + final int displayRotation, + final int cameraRotation, + final boolean cameraFlip) { this.frame = frame; matrix.setRectToRect(framePreview, new RectF(frame), ScaleToFit.FILL); matrix.postRotate(-displayRotation, frame.exactCenterX(), frame.exactCenterY()); @@ -99,15 +99,14 @@ public class ScannerView extends View { } public void addDot(final ResultPoint dot) { - dots.put(new float[] { dot.getX(), dot.getY() }, System.currentTimeMillis()); + dots.put(new float[] {dot.getX(), dot.getY()}, System.currentTimeMillis()); invalidate(); } @Override public void onDraw(final Canvas canvas) { - if (frame == null) - return; + if (frame == null) return; final long now = System.currentTimeMillis(); @@ -142,7 +141,8 @@ public class ScannerView extends View { canvas.drawRect(frame, laserPaint); // draw points - for (final Iterator> i = dots.entrySet().iterator(); i.hasNext();) { + for (final Iterator> i = dots.entrySet().iterator(); + i.hasNext(); ) { final Map.Entry entry = i.next(); final long age = now - entry.getValue(); if (age < DOT_TTL_MS) { diff --git a/src/main/java/eu/siacs/conversations/utils/CursorUtils.java b/src/main/java/eu/siacs/conversations/utils/CursorUtils.java index 91befa22936a1912801662c9cd74e692c2ee9b7c..23e8401cd101988f38ee6b529db704a616537bcc 100644 --- a/src/main/java/eu/siacs/conversations/utils/CursorUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/CursorUtils.java @@ -9,8 +9,7 @@ public class CursorUtils { public static void upgradeCursorWindowSize(final Cursor cursor) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { - if (cursor instanceof AbstractWindowedCursor) { - final AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor; + if (cursor instanceof AbstractWindowedCursor windowedCursor) { windowedCursor.setWindow(new CursorWindow("4M", 4 * 1024 * 1024)); } if (cursor instanceof SQLiteCursor) { @@ -18,5 +17,4 @@ public class CursorUtils { } } } - } diff --git a/src/main/java/eu/siacs/conversations/utils/GeoHelper.java b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java index 0616b11f5979693ad95b37f7fe3283ed26027e9b..0c4032eb26e0e9d581ae75e6ccdf4bbc6069f478 100644 --- a/src/main/java/eu/siacs/conversations/utils/GeoHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/GeoHelper.java @@ -12,8 +12,8 @@ import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.ui.ShareLocationActivity; import eu.siacs.conversations.ui.ShowLocationActivity; -import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.regex.Matcher; import org.osmdroid.util.GeoPoint; @@ -160,11 +160,8 @@ public class GeoHelper { private static String getLabel(Context context, Message message) { if (message.getStatus() == Message.STATUS_RECEIVED) { - try { - return URLEncoder.encode(UIHelper.getMessageDisplayName(message), "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); - } + return URLEncoder.encode( + UIHelper.getMessageDisplayName(message), StandardCharsets.UTF_8); } else { return context.getString(R.string.me); } diff --git a/src/main/java/eu/siacs/conversations/utils/ImStyleParser.java b/src/main/java/eu/siacs/conversations/utils/ImStyleParser.java index d21bfadf133857fd0f15d359e812c503c441e671..dae03877589cebef224e53b00d95f24a7fc7831e 100644 --- a/src/main/java/eu/siacs/conversations/utils/ImStyleParser.java +++ b/src/main/java/eu/siacs/conversations/utils/ImStyleParser.java @@ -35,11 +35,11 @@ import java.util.List; public class ImStyleParser { - private final static List KEYWORDS = Arrays.asList('*', '_', '~', '`'); - private final static List NO_SUB_PARSING_KEYWORDS = Arrays.asList('`'); - private final static List BLOCK_KEYWORDS = Arrays.asList('`'); - private final static boolean ALLOW_EMPTY = false; - private final static boolean PARSE_HIGHER_ORDER_END = true; + private static final List KEYWORDS = Arrays.asList('*', '_', '~', '`'); + private static final List NO_SUB_PARSING_KEYWORDS = List.of('`'); + private static final List BLOCK_KEYWORDS = List.of('`'); + private static final boolean ALLOW_EMPTY = false; + private static final boolean PARSE_HIGHER_ORDER_END = true; public static List