From f7cab134717196ac13732a1ce76ec32ba9870352 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 23 Jan 2025 13:23:49 +0100 Subject: [PATCH 01/39] version bump to 2.17.9 + changelog --- CHANGELOG.md | 5 +++++ build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/4213104.txt | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/4213104.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 9516c25bd2476b23b1f137e4a72d75e20ac5ea61..ae720c1515a9a2f88c2da5202f5569876f9a4e7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Version 2.17.9 + +* Make use of SASL SCRAM Downgrade Protection (XEP-0474) +* Send reactions to MUC PMs to correct JID + ### Version 2.17.8 * Fix some minor UI bugs diff --git a/build.gradle b/build.gradle index b3c85abf16bcbb8a947c56669a7e0969766bfa2b..8895988ea9d4a6abeff2191dbab8ffae40da80a7 100644 --- a/build.gradle +++ b/build.gradle @@ -113,8 +113,8 @@ android { defaultConfig { minSdkVersion 23 targetSdkVersion 34 - versionCode 42129 - versionName "2.17.8" + versionCode 42131 + versionName "2.17.9" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/4213104.txt b/fastlane/metadata/android/en-US/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..886e2fc13126f2cea2102fdb48b3cfe9ecb2f9ae --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Make use of SASL SCRAM Downgrade Protection (XEP-0474) +* Send reactions to MUC PMs to correct JID From 91dadf5692c770d319873f7d4de1b8a8473d4138 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 23 Jan 2025 13:30:13 +0100 Subject: [PATCH 02/39] remove references to indivdual propriatary services --- .../java/eu/siacs/conversations/Config.java | 20 ------------------- .../conversations/entities/Conversation.java | 6 ------ 2 files changed, 26 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 219c463f4bfac7f6aba98f28e4e65d837e9fa90f..8417b20221c923aa0bf71bd0c84535bb73d9d11b 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -2,12 +2,8 @@ package eu.siacs.conversations; import android.graphics.Bitmap; import android.net.Uri; -import eu.siacs.conversations.crypto.XmppDomainVerifier; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.chatstate.ChatState; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; import java.util.Locale; public final class Config { @@ -179,22 +175,6 @@ public final class Config { "_NULL_", "_EXPORT_", "_anon_", "_RC4_", "_DES_", "_MD5", }; - public static class OMEMO_EXCEPTIONS { - // if the own account matches one of the following domains OMEMO won’t be turned on - // automatically - public static final List ACCOUNT_DOMAINS = Collections.singletonList("s.ms"); - - // if the contacts domain matches one of the following domains OMEMO won’t be turned on - // automatically - // can be used for well known, widely used gateways - private static final List CONTACT_DOMAINS = - Arrays.asList("cheogram.com", "*.covid.monal.im"); - - public static boolean matchesContactDomain(final String domain) { - return XmppDomainVerifier.matchDomain(domain, CONTACT_DOMAINS); - } - } - private Config() {} public static final class Map { diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 0fec5bcb3d64ffee9cb0250a13635e0c84b6bf27..1f192d57a22f1bc1693a47f1946cfc2d6049fd44 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -165,12 +165,6 @@ public class Conversation extends AbstractEntity if (conversation.getContact().isOwnServer()) { return false; } - final String contact = conversation.getJid().getDomain().toEscapedString(); - final String account = conversation.getAccount().getServer(); - if (Config.OMEMO_EXCEPTIONS.matchesContactDomain(contact) - || Config.OMEMO_EXCEPTIONS.ACCOUNT_DOMAINS.contains(account)) { - return false; - } return conversation.isSingleOrPrivateAndNonAnonymous() || conversation.getBooleanAttribute( ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS, false); From cd46067681c23551f3a7ba3eb18409c291d790ba Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 24 Jan 2025 10:34:23 +0100 Subject: [PATCH 03/39] ensure that message is parsed into bubble OR receipt, marker, reaction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit ensures that Conversations always parses a message into a bubble or as meta data (receipt, display marker, reaction) never both and with a preference for bubble. Conversations goes through great length to ensure that all participants of a group chat have a similiar (or ideally the same) view of the chat history. For example Conversations doesn’t use xhtml, shows a language tag when the content is available in multiple languages, etc. So a message that has both a body and a reaction for example shouldn't show differently in clients that support reactions and clients that don't. Take the following example: ```xml Conversations users are stupid! 💩 ``` This message should render as what it is: A message and the body shouldn't be silently discarded for clients that support reactions. (The same goes for display markers and receipts) Fallback messages for something that is not supposed to render as a bubble should be discouraged. --- .../conversations/parser/MessageParser.java | 1021 +++++++++++------ .../android/xmpp/model/Extension.java | 11 +- 2 files changed, 704 insertions(+), 328 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 3ddb981240cf68030eb37bfbd656b71459b1282e..80430e38477b44959216b84e0aec0559d04dc2d0 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -2,22 +2,8 @@ package eu.siacs.conversations.parser; import android.util.Log; import android.util.Pair; - import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.function.Consumer; - import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -47,22 +33,36 @@ import eu.siacs.conversations.xml.LocalizedContent; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.OnMessagePacketReceived; import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.pep.Avatar; import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.axolotl.Encrypted; import im.conversations.android.xmpp.model.carbons.Received; import im.conversations.android.xmpp.model.carbons.Sent; import im.conversations.android.xmpp.model.correction.Replace; import im.conversations.android.xmpp.model.forward.Forwarded; +import im.conversations.android.xmpp.model.markers.Displayed; import im.conversations.android.xmpp.model.occupant.OccupantId; import im.conversations.android.xmpp.model.reactions.Reactions; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; -public class MessageParser extends AbstractParser implements Consumer { +public class MessageParser extends AbstractParser + implements Consumer { - private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH); + private static final SimpleDateFormat TIME_FORMAT = + new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH); private static final List JINGLE_MESSAGE_ELEMENT_NAMES = Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish"); @@ -71,7 +71,8 @@ public class MessageParser extends AbstractParser implements Consumer deviceIds = IqParser.deviceIds(item); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... "); + Log.d( + Config.LOGTAG, + AxolotlService.getLogprefix(account) + + "Received PEP device list " + + deviceIds + + " update from " + + from + + ", processing... "); final AxolotlService axolotlService = account.getAxolotlService(); axolotlService.registerDevices(from, deviceIds); } else if (Namespace.BOOKMARKS.equals(node) && account.getJid().asBareJid().equals(from)) { @@ -260,7 +303,8 @@ public class MessageParser extends AbstractParser implements Consumer f; f = getForwardedMessagePacket(original, Received.class); f = f == null ? getForwardedMessagePacket(original, Sent.class) : f; @@ -451,19 +539,21 @@ public class MessageParser extends AbstractParser implements Consumer fallbacksBySourceId = Collections.emptySet(); if (conversationMultiMode) { - final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart); + final Jid fallback = + conversation.getMucOptions().getTrueCounterpart(counterpart); origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback); if (origin == null) { try { - fallbacksBySourceId = account.getAxolotlService().findCounterpartsBySourceId(XmppAxolotlMessage.parseSourceId(axolotlEncrypted)); + fallbacksBySourceId = + account.getAxolotlService() + .findCounterpartsBySourceId( + XmppAxolotlMessage.parseSourceId( + axolotlEncrypted)); } catch (IllegalArgumentException e) { - //ignoring + // ignoring } } - if (origin == null && fallbacksBySourceId.size() == 0) { - Log.d(Config.LOGTAG, "axolotl message in anonymous conference received and no possible fallbacks"); + if (origin == null && fallbacksBySourceId.isEmpty()) { + Log.d( + Config.LOGTAG, + "axolotl message in anonymous conference received and no possible" + + " fallbacks"); return; } } else { @@ -615,17 +757,40 @@ public class MessageParser extends AbstractParser implements Consumer 0); - final LocalizedContent subject = packet.findInternationalizedChildContentInDefaultNamespace("subject"); - if (subject != null && conversation.getMucOptions().setSubject(subject.content)) { + final LocalizedContent subject = + packet.findInternationalizedChildContentInDefaultNamespace( + "subject"); + if (subject != null + && conversation.getMucOptions().setSubject(subject.content)) { mXmppConnectionService.updateConversation(conversation); } mXmppConnectionService.updateConversationUi(); @@ -900,7 +1159,10 @@ public class MessageParser extends AbstractParser implements Consumer cryptoTargets = conversation.getAcceptedCryptoTargets(); if (cryptoTargets.remove(user.getRealJid())) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": removed " + jid + " from crypto targets of " + conversation.getName()); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": removed " + + jid + + " from crypto targets of " + + conversation.getName()); conversation.setAcceptedCryptoTargets(cryptoTargets); mXmppConnectionService.updateConversation(conversation); } @@ -935,7 +1209,8 @@ public class MessageParser extends AbstractParser implements Consumer getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, Class clazz) { + private static Pair + getForwardedMessagePacket( + final im.conversations.android.xmpp.model.stanza.Message original, + Class clazz) { final var extension = original.getExtension(clazz); final var forwarded = extension == null ? null : extension.getExtension(Forwarded.class); if (forwarded == null) { @@ -1267,36 +1612,51 @@ public class MessageParser extends AbstractParser implements Consumer(forwardedMessage,timestamp); + return new Pair<>(forwardedMessage, timestamp); } - private static Pair getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, final String name, final String namespace) { + private static Pair + getForwardedMessagePacket( + final im.conversations.android.xmpp.model.stanza.Message original, + final String name, + final String namespace) { final Element wrapper = original.findChild(name, namespace); - final var forwardedElement = wrapper == null ? null : wrapper.findChild("forwarded",Namespace.FORWARD); + final var forwardedElement = + wrapper == null ? null : wrapper.findChild("forwarded", Namespace.FORWARD); if (forwardedElement instanceof Forwarded forwarded) { final Long timestamp = AbstractParser.parseTimestamp(forwarded, null); final var forwardedMessage = forwarded.getMessage(); if (forwardedMessage == null) { return null; } - return new Pair<>(forwardedMessage,timestamp); + return new Pair<>(forwardedMessage, timestamp); } return null; } - private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query, final String id) { - final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid()); + private void dismissNotification( + Account account, Jid counterpart, MessageArchiveService.Query query, final String id) { + final Conversation conversation = + mXmppConnectionService.find(account, counterpart.asBareJid()); if (conversation != null && (query == null || query.isCatchup())) { final String displayableId = conversation.findMostRecentRemoteDisplayableId(); if (displayableId != null && displayableId.equals(id)) { mXmppConnectionService.markRead(conversation); } else { - Log.w(Config.LOGTAG, account.getJid().asBareJid() + ": received dismissing display marker that did not match our last id in that conversation"); + Log.w( + Config.LOGTAG, + account.getJid().asBareJid() + + ": received dismissing display marker that did not match our last" + + " id in that conversation"); } } } - private void processMessageReceipts(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet, final String remoteMsgId, MessageArchiveService.Query query) { + private void processMessageReceipts( + final Account account, + final im.conversations.android.xmpp.model.stanza.Message packet, + final String remoteMsgId, + MessageArchiveService.Query query) { final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0"); final boolean request = packet.hasChild("request", "urn:xmpp:receipts"); if (query == null) { @@ -1308,11 +1668,15 @@ public class MessageParser extends AbstractParser implements Consumer 0) { - final var receipt = mXmppConnectionService.getMessageGenerator().received(account, - packet.getFrom(), - remoteMsgId, - receiptsNamespaces, - packet.getType()); + final var receipt = + mXmppConnectionService + .getMessageGenerator() + .received( + account, + packet.getFrom(), + remoteMsgId, + receiptsNamespaces, + packet.getType()); mXmppConnectionService.sendMessagePacket(account, receipt); } } else if (query.isCatchup()) { @@ -1323,8 +1687,15 @@ public class MessageParser extends AbstractParser implements Consumer E getOnlyExtension(final Class clazz) { + final var extensions = getExtensions(clazz); + if (extensions.size() == 1) { + return Iterables.getOnlyElement(extensions); + } + return null; + } + public Collection getExtensions(final Class clazz) { return Collections2.transform( Collections2.filter(this.children, clazz::isInstance), clazz::cast); From 23f3c752a1bf391c44aabff514114ff329d351c1 Mon Sep 17 00:00:00 2001 From: Juan M Sevilla Date: Wed, 22 Jan 2025 21:55:00 +0000 Subject: [PATCH 04/39] Translated using Weblate (Spanish) Currently translated at 100.0% (1059 of 1059 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/es/ --- src/main/res/values-es/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 9358b93b4ae39e08e4412759d388f0c1ac011f7b..6ae3fab6595cda488690dba859b72105b49d0abe 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -529,7 +529,7 @@ Este campo es requerido Corregir mensaje Enviar mensaje corregido - Ya has confiado la huella digital de esta persona. Al seleccionar “Listo” solo estás confirmando que %s es parte de este chat grupal. + Ya has verificado la huella digital de esta persona. Al seleccionar “Hecho” solo estás confirmando que %s es parte de este chat grupal. Has deshabilitado esta cuenta Error de seguridad: ¡Acceso a archivo inválido! No se ha encontrado ninguna aplicación para compartir la URI @@ -1011,7 +1011,7 @@ Tu contacto utiliza dispositivos no verificados. Escanea su código QR para realizar la verificación e impedir ataques MITM activos. Desconectarse Desconectado - Está utilizando dispositivos no verificados. Escanea el código QR en tus otros dispositivos para realizar la verificación e impedir ataques MITM activos. + Estás utilizando dispositivos no verificados. Escanea el código QR de tus otros dispositivos para realizar la verificación e impedir ataques MITM activos. Informar de spam y bloquear al spammer Informar sobre spam ¡Bienvenido a Quicksy! @@ -1059,7 +1059,7 @@ Organismos de certificación Confiar en los certificados CA del sistema Requerir enlace al canal - La vinculación de canales puede detectar algunos ataques al intermediario + La vinculación de canales puede detectar algunos ataques de intermediario Conexión al servidor Sistema operativo En el dispositivo @@ -1103,7 +1103,7 @@ La llamada está usando el altavoz. La llamada está usando bluetooth. Video desactivado. Toca para activar. - Apartado de inicio de sesión + Método de acceso No se pudo agregar la reacción Agregar reacción… Agregar reacción From 9c3176110bb3a05112f82fc0bf7f03c5f0896735 Mon Sep 17 00:00:00 2001 From: Juan M Sevilla Date: Thu, 23 Jan 2025 00:12:08 +0000 Subject: [PATCH 05/39] Translated using Weblate (Spanish) Currently translated at 100.0% (1059 of 1059 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/es/ --- src/main/res/values-es/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 6ae3fab6595cda488690dba859b72105b49d0abe..9bb2b1da335beb6376fa559e7de3697fff89db75 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -529,7 +529,7 @@ Este campo es requerido Corregir mensaje Enviar mensaje corregido - Ya has verificado la huella digital de esta persona. Al seleccionar “Hecho” solo estás confirmando que %s es parte de este chat grupal. + Ya has confiado en la huella digital de esta persona. Al seleccionar “Hecho” solo estás confirmando que %s es parte de este chat grupal. Has deshabilitado esta cuenta Error de seguridad: ¡Acceso a archivo inválido! No se ha encontrado ninguna aplicación para compartir la URI From 355755e690f1c0faf590e61ce672f470d96341ef Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 24 Jan 2025 18:26:30 +0100 Subject: [PATCH 06/39] get rid of jid escaping api Conversations never supported Jid Escaping but the underlaying library did so we had API for that which could lead to confusion --- .../entities/AccountConfiguration.java | 20 +- .../services/ImportBackupService.java | 27 +- .../ui/EasyOnboardingInviteActivity.java | 7 +- .../conversations/ui/MagicCreateActivity.java | 18 +- .../ui/ManageAccountActivity.java | 18 +- .../conversations/ui/WelcomeActivity.java | 18 +- .../utils/ProvisioningUtils.java | 12 +- .../conversations/utils/SignupUtils.java | 16 +- .../conversations/crypto/sasl/External.java | 6 +- .../siacs/conversations/entities/Account.java | 22 +- .../conversations/entities/Bookmark.java | 521 ++++--- .../siacs/conversations/entities/Contact.java | 82 +- .../conversations/entities/Conversation.java | 3 +- .../conversations/entities/MucOptions.java | 105 +- .../conversations/entities/RawBlockable.java | 17 +- .../conversations/entities/Reaction.java | 8 +- .../eu/siacs/conversations/entities/Room.java | 16 +- .../conversations/generator/IqGenerator.java | 102 +- .../conversations/parser/AbstractParser.java | 333 ++-- .../siacs/conversations/parser/IqParser.java | 186 ++- .../conversations/parser/MessageParser.java | 33 +- .../conversations/parser/PresenceParser.java | 32 +- .../persistance/DatabaseBackend.java | 9 +- .../persistance/UnifiedPushDatabase.java | 39 +- .../conversations/services/AvatarService.java | 1347 +++++++++-------- .../services/CallIntegration.java | 2 +- .../CallIntegrationConnectionService.java | 22 +- .../services/ChannelDiscoveryService.java | 33 +- .../services/NotificationService.java | 14 +- .../services/ShortcutService.java | 17 +- .../services/UnifiedPushBroker.java | 69 +- .../services/XmppConnectionService.java | 19 +- .../conversations/ui/BlockContactDialog.java | 148 +- .../conversations/ui/BlocklistActivity.java | 180 +-- .../ui/ChannelDiscoveryActivity.java | 2 +- ...hooseAccountForProfilePictureActivity.java | 25 +- .../ui/ChooseContactActivity.java | 119 +- .../ui/ConferenceDetailsActivity.java | 9 +- .../ui/ContactDetailsActivity.java | 14 +- .../ui/ConversationFragment.java | 9 +- .../ui/ConversationsOverviewFragment.java | 740 ++++----- .../ui/CreatePublicChannelDialog.java | 169 ++- .../conversations/ui/EditAccountActivity.java | 46 +- .../conversations/ui/EnterJidDialog.java | 35 +- .../ui/MediaBrowserActivity.java | 29 +- .../ui/PublishProfilePictureActivity.java | 6 +- .../conversations/ui/RtpSessionActivity.java | 53 +- .../conversations/ui/ShareWithActivity.java | 2 +- .../ui/StartConversationActivity.java | 50 +- .../conversations/ui/TrustKeysActivity.java | 924 +++++------ .../conversations/ui/UriHandlerActivity.java | 30 +- .../siacs/conversations/ui/XmppActivity.java | 8 +- .../ui/adapter/AccountAdapter.java | 46 +- .../ui/adapter/KnownHostsAdapter.java | 103 +- .../fragment/settings/UpSettingsFragment.java | 48 +- .../conversations/utils/AccountUtils.java | 18 +- .../conversations/utils/BackupFileHeader.java | 38 +- .../conversations/utils/CryptoHelper.java | 99 +- .../utils/IrregularUnicodeDetector.java | 412 ++--- .../siacs/conversations/utils/JidHelper.java | 18 +- .../eu/siacs/conversations/utils/XmppUri.java | 44 +- .../worker/ExportBackupWorker.java | 14 +- .../eu/siacs/conversations/xml/Element.java | 24 +- .../siacs/conversations/xmpp/InvalidJid.java | 155 -- .../java/eu/siacs/conversations/xmpp/Jid.java | 353 ++++- .../siacs/conversations/xmpp/WrappedJid.java | 130 -- .../conversations/xmpp/XmppConnection.java | 6 +- .../xmpp/jingle/JingleConnectionManager.java | 27 +- .../xmpp/jingle/JingleRtpConnection.java | 126 +- .../transports/SocksByteStreamsTransport.java | 22 +- .../android/xmpp/model/bind/Bind.java | 3 +- .../model/sasl2/AuthorizationIdentifier.java | 6 +- .../android/xmpp/model/stanza/Stanza.java | 5 +- 73 files changed, 3852 insertions(+), 3616 deletions(-) delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/WrappedJid.java diff --git a/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java b/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java index 65b6804f9b6e7bb4abac560824bf5958a38dbd0b..c3326d8ee81761121fcba634d788242b6811f373 100644 --- a/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java +++ b/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java @@ -5,7 +5,6 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; import com.google.gson.annotations.SerializedName; - import eu.siacs.conversations.xmpp.Jid; public class AccountConfiguration { @@ -17,7 +16,7 @@ public class AccountConfiguration { public String password; public Jid getJid() { - return Jid.ofEscaped(address); + return Jid.of(address); } public static AccountConfiguration parse(final String input) { @@ -27,24 +26,17 @@ public class AccountConfiguration { } catch (JsonSyntaxException e) { throw new IllegalArgumentException("Not a valid JSON string", e); } - Preconditions.checkArgument( - c.protocol == Protocol.XMPP, - "Protocol must be XMPP" - ); + Preconditions.checkArgument(c.protocol == Protocol.XMPP, "Protocol must be XMPP"); Preconditions.checkArgument( c.address != null && c.getJid().isBareJid() && !c.getJid().isDomainJid(), - "Invalid XMPP address" - ); + "Invalid XMPP address"); Preconditions.checkArgument( - c.password != null && c.password.length() > 0, - "No password specified" - ); + c.password != null && !c.password.isEmpty(), "No password specified"); return c; } public enum Protocol { - @SerializedName("xmpp") XMPP, + @SerializedName("xmpp") + XMPP, } - } - diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java index dd96468ee87427a2fb4a1ef319650c5ab9ff1abb..c15208bfa1b8c0fc383dadbcce3d92ff95292aed 100644 --- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java @@ -16,10 +16,8 @@ import android.os.Binder; import android.os.IBinder; import android.provider.OpenableColumns; import android.util.Log; - import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; - import com.google.common.base.Charsets; import com.google.common.base.Stopwatch; import com.google.common.io.CountingInputStream; @@ -27,7 +25,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; @@ -41,14 +38,6 @@ import eu.siacs.conversations.utils.BackupFileHeader; import eu.siacs.conversations.utils.SerialSingleThreadExecutor; import eu.siacs.conversations.worker.ExportBackupWorker; import eu.siacs.conversations.xmpp.Jid; - -import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.io.CipherInputStream; -import org.bouncycastle.crypto.modes.AEADBlockCipher; -import org.bouncycastle.crypto.modes.GCMBlockCipher; -import org.bouncycastle.crypto.params.AEADParameters; -import org.bouncycastle.crypto.params.KeyParameter; - import java.io.BufferedReader; import java.io.DataInputStream; import java.io.File; @@ -72,8 +61,13 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import java.util.zip.ZipException; - import javax.crypto.BadPaddingException; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.io.CipherInputStream; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; public class ImportBackupService extends Service { @@ -314,10 +308,11 @@ public class ImportBackupService extends Service { final Jid jid = backupFileHeader.getJid(); final Cursor countCursor = db.rawQuery( - "select count(messages.uuid) from messages join conversations on conversations.uuid=messages.conversationUuid join accounts on conversations.accountUuid=accounts.uuid where accounts.username=? and accounts.server=?", - new String[] { - jid.getEscapedLocal(), jid.getDomain().toEscapedString() - }); + "select count(messages.uuid) from messages join conversations on" + + " conversations.uuid=messages.conversationUuid join accounts on" + + " conversations.accountUuid=accounts.uuid where" + + " accounts.username=? and accounts.server=?", + new String[] {jid.getLocal(), jid.getDomain().toString()}); countCursor.moveToFirst(); final int count = countCursor.getInt(0); Log.d( diff --git a/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java b/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java index 9228a5170a8c053f005c9b9a33517fd043dab5e2..874557198c196712980ed227863e736da9f8cb4f 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java @@ -11,13 +11,10 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; - import com.google.android.material.color.MaterialColors; import com.google.common.base.Strings; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityEasyInviteBinding; @@ -153,7 +150,7 @@ public class EasyOnboardingInviteActivity extends XmppActivity } final Intent launchIntent = getIntent(); final String accountExtra = launchIntent.getStringExtra(EXTRA_ACCOUNT); - final Jid jid = accountExtra == null ? null : Jid.ofEscaped(accountExtra); + final Jid jid = accountExtra == null ? null : Jid.of(accountExtra); if (jid == null) { return; } @@ -163,7 +160,7 @@ public class EasyOnboardingInviteActivity extends XmppActivity public static void launch(final Account account, final Activity context) { final Intent intent = new Intent(context, EasyOnboardingInviteActivity.class); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); context.startActivity(intent); } diff --git a/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java b/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java index b6d4d452ee6fa28bfa281f12058d5e87d490bf78..bebf9bab67ba4de49b5bfc39cabb8962bff3e451 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java @@ -7,9 +7,7 @@ import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.widget.Toast; - import androidx.databinding.DataBindingUtil; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityMagicCreateBinding; @@ -17,7 +15,6 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.InstallReferrerUtils; import eu.siacs.conversations.xmpp.Jid; - import java.security.SecureRandom; public class MagicCreateActivity extends XmppActivity implements TextWatcher { @@ -68,15 +65,15 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher { final boolean fixedUsername; if (this.domain != null && this.username != null) { fixedUsername = true; - jid = Jid.ofLocalAndDomainEscaped(this.username, this.domain); + jid = Jid.ofLocalAndDomain(this.username, this.domain); } else if (this.domain != null) { fixedUsername = false; - jid = Jid.ofLocalAndDomainEscaped(username, this.domain); + jid = Jid.ofLocalAndDomain(username, this.domain); } else { fixedUsername = false; - jid = Jid.ofLocalAndDomainEscaped(username, Config.MAGIC_CREATE_DOMAIN); + jid = Jid.ofLocalAndDomain(username, Config.MAGIC_CREATE_DOMAIN); } - if (!jid.getEscapedLocal().equals(jid.getLocal()) + if (!jid.getLocal().equals(jid.getLocal()) || (this.username == null && username.length() < 3)) { binding.usernameLayout.setError(getString(R.string.invalid_username)); binding.username.requestFocus(); @@ -146,12 +143,11 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher { binding.fullJid.setVisibility(View.VISIBLE); final Jid jid; if (this.domain == null) { - jid = Jid.ofLocalAndDomainEscaped(username, Config.MAGIC_CREATE_DOMAIN); + jid = Jid.ofLocalAndDomain(username, Config.MAGIC_CREATE_DOMAIN); } else { - jid = Jid.ofLocalAndDomainEscaped(username, this.domain); + jid = Jid.ofLocalAndDomain(username, this.domain); } - binding.fullJid.setText( - getString(R.string.your_full_jid_will_be, jid.toEscapedString())); + binding.fullJid.setText(getString(R.string.your_full_jid_will_be, jid.toString())); binding.usernameLayout.setError(null); } catch (final IllegalArgumentException e) { binding.fullJid.setVisibility(View.INVISIBLE); diff --git a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java index 6ab32682fd866ac7d906d058220fdbe2ea7598a4..c2a82f8d5cddfa39f08ce263b2afa4e5cc34495a 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -17,13 +17,10 @@ import android.view.MenuItem; import android.view.View; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.databinding.DataBindingUtil; - import com.google.common.base.Strings; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityManageAccountsBinding; @@ -34,12 +31,10 @@ import eu.siacs.conversations.ui.adapter.AccountAdapter; import eu.siacs.conversations.ui.util.MenuDoubleTabUtil; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; - -import org.openintents.openpgp.util.OpenPgpApi; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import org.openintents.openpgp.util.OpenPgpApi; public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, @@ -95,7 +90,7 @@ public class ManageAccountActivity extends XmppActivity String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT); if (jid != null) { try { - this.selectedAccountJid = Jid.ofEscaped(jid); + this.selectedAccountJid = Jid.of(jid); } catch (IllegalArgumentException e) { this.selectedAccountJid = null; } @@ -113,7 +108,7 @@ public class ManageAccountActivity extends XmppActivity public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) { if (selectedAccount != null) { savedInstanceState.putString( - STATE_SELECTED_ACCOUNT, selectedAccount.getJid().asBareJid().toEscapedString()); + STATE_SELECTED_ACCOUNT, selectedAccount.getJid().asBareJid().toString()); } super.onSaveInstanceState(savedInstanceState); } @@ -132,7 +127,7 @@ public class ManageAccountActivity extends XmppActivity menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(false); menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false); } - menu.setHeaderTitle(this.selectedAccount.getJid().asBareJid().toEscapedString()); + menu.setHeaderTitle(this.selectedAccount.getJid().asBareJid().toString()); } @Override @@ -297,7 +292,7 @@ public class ManageAccountActivity extends XmppActivity private void publishAvatar(Account account) { Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); startActivity(intent); } @@ -357,7 +352,8 @@ public class ManageAccountActivity extends XmppActivity Log.d( Config.LOGTAG, account.getJid().asBareJid() - + ": quick start disabled. account will regain this capability on the next connect"); + + ": quick start disabled. account will regain this capability on the" + + " next connect"); } if (!xmppConnectionService.updateAccount(account)) { Toast.makeText(this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show(); diff --git a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java index b2a40976c503c4b744f83e0562092bf85dbb2f62..514d0ccfdfd8afb106d965cc6385fecab84e71a3 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java @@ -1,5 +1,8 @@ package eu.siacs.conversations.ui; +import static eu.siacs.conversations.utils.PermissionUtils.allGranted; +import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; + import android.Manifest; import android.content.ActivityNotFoundException; import android.content.Intent; @@ -12,14 +15,10 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.DataBindingUtil; - -import java.util.Arrays; -import java.util.List; - +import com.google.common.base.Strings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityWelcomeBinding; @@ -30,11 +29,8 @@ import eu.siacs.conversations.utils.InstallReferrerUtils; import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; - -import static eu.siacs.conversations.utils.PermissionUtils.allGranted; -import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; - -import com.google.common.base.Strings; +import java.util.Arrays; +import java.util.List; public class WelcomeActivity extends XmppActivity implements XmppConnectionService.OnAccountCreated, KeyChainAliasCallback { @@ -196,7 +192,7 @@ public class WelcomeActivity extends XmppActivity @Override public void onAccountCreated(final Account account) { final Intent intent = new Intent(this, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().asBareJid().toEscapedString()); + intent.putExtra("jid", account.getJid().asBareJid().toString()); intent.putExtra("init", true); addInviteUri(intent); startActivity(intent); diff --git a/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java b/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java index 593291d95374f4be2f1722b8c46a03fe34ed28dc..fdfe28dbb784ad05a6465c3bcea989192ea83495 100644 --- a/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java +++ b/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java @@ -3,15 +3,13 @@ package eu.siacs.conversations.utils; import android.app.Activity; import android.content.Intent; import android.widget.Toast; - -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.AccountConfiguration; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.EditAccountActivity; import eu.siacs.conversations.xmpp.Jid; +import java.util.List; public class ProvisioningUtils { @@ -20,7 +18,8 @@ public class ProvisioningUtils { try { accountConfiguration = AccountConfiguration.parse(json); } catch (final IllegalArgumentException e) { - Toast.makeText(activity, R.string.improperly_formatted_provisioning, Toast.LENGTH_LONG).show(); + Toast.makeText(activity, R.string.improperly_formatted_provisioning, Toast.LENGTH_LONG) + .show(); return; } final Jid jid = accountConfiguration.getJid(); @@ -31,13 +30,12 @@ public class ProvisioningUtils { } final Intent serviceIntent = new Intent(activity, XmppConnectionService.class); serviceIntent.setAction(XmppConnectionService.ACTION_PROVISION_ACCOUNT); - serviceIntent.putExtra("address", jid.asBareJid().toEscapedString()); + serviceIntent.putExtra("address", jid.asBareJid().toString()); serviceIntent.putExtra("password", accountConfiguration.password); Compatibility.startService(activity, serviceIntent); final Intent intent = new Intent(activity, EditAccountActivity.class); - intent.putExtra("jid", jid.asBareJid().toEscapedString()); + intent.putExtra("jid", jid.asBareJid().toString()); intent.putExtra("init", true); activity.startActivity(intent); } - } diff --git a/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java b/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java index fb088234a24e10ea315bafebd910e28df9caa270..b8123947f98b90e73ef230b50785f28eceefa369 100644 --- a/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java +++ b/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java @@ -2,7 +2,6 @@ package eu.siacs.conversations.utils; import android.app.Activity; import android.content.Intent; - import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; @@ -21,13 +20,14 @@ public class SignupUtils { return true; } - public static Intent getTokenRegistrationIntent(final Activity activity, Jid jid, String preAuth) { + public static Intent getTokenRegistrationIntent( + final Activity activity, Jid jid, String preAuth) { final Intent intent = new Intent(activity, MagicCreateActivity.class); if (jid.isDomainJid()) { - intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toEscapedString()); + intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toString()); } else { - intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toEscapedString()); - intent.putExtra(MagicCreateActivity.EXTRA_USERNAME, jid.getEscapedLocal()); + intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toString()); + intent.putExtra(MagicCreateActivity.EXTRA_USERNAME, jid.getLocal()); } intent.putExtra(MagicCreateActivity.EXTRA_PRE_AUTH, preAuth); return intent; @@ -55,7 +55,9 @@ public class SignupUtils { intent = new Intent(activity, EditAccountActivity.class); intent.putExtra("jid", pendingAccount.getJid().asBareJid().toString()); if (!pendingAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) { - intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, pendingAccount.isOptionSet(Account.OPTION_REGISTER)); + intent.putExtra( + EditAccountActivity.EXTRA_FORCE_REGISTER, + pendingAccount.isOptionSet(Account.OPTION_REGISTER)); } } else { if (service.getAccounts().size() == 0) { @@ -74,4 +76,4 @@ public class SignupUtils { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); return intent; } -} \ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java index 6aba413a5682c9a544797aac797bec6ee6fa35e3..2e8adf1892d5332340e2a9fb28872223807f34df 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java @@ -1,10 +1,8 @@ package eu.siacs.conversations.crypto.sasl; import android.util.Base64; - -import javax.net.ssl.SSLSocket; - import eu.siacs.conversations.entities.Account; +import javax.net.ssl.SSLSocket; public class External extends SaslMechanism { @@ -27,6 +25,6 @@ public class External extends SaslMechanism { @Override public String getClientFirstMessage(final SSLSocket sslSocket) { return Base64.encodeToString( - account.getJid().asBareJid().toEscapedString().getBytes(), Base64.NO_WRAP); + account.getJid().asBareJid().toString().getBytes(), Base64.NO_WRAP); } } diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index ce13ba9f3594d42242854083496af1d0fb2fab94..f55618fe2baeded03cbe51bd7e298d084011ce50 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -268,7 +268,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable } public String getUsername() { - return jid.getEscapedLocal(); + return jid.getLocal(); } public boolean setJid(final Jid next) { @@ -292,7 +292,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable } public String getServer() { - return jid.getDomain().toEscapedString(); + return jid.getDomain().toString(); } public String getPassword() { @@ -508,7 +508,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable final ContentValues values = new ContentValues(); values.put(UUID, uuid); values.put(USERNAME, jid.getLocal()); - values.put(SERVER, jid.getDomain().toEscapedString()); + values.put(SERVER, jid.getDomain().toString()); values.put(PASSWORD, password); values.put(OPTIONS, options); synchronized (this.keys) { @@ -698,11 +698,11 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable public String getShareableUri() { List fingerprints = this.getFingerprints(); - String uri = "xmpp:" + this.getJid().asBareJid().toEscapedString(); - if (fingerprints.size() > 0) { - return XmppUri.getFingerprintUri(uri, fingerprints, ';'); - } else { + final String uri = "xmpp:" + this.getJid().asBareJid().toString(); + if (fingerprints.isEmpty()) { return uri; + } else { + return XmppUri.getFingerprintUri(uri, fingerprints, ';'); } } @@ -710,11 +710,11 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable List fingerprints = this.getFingerprints(); String uri = "https://conversations.im/i/" - + XmppUri.lameUrlEncode(this.getJid().asBareJid().toEscapedString()); - if (fingerprints.size() > 0) { - return XmppUri.getFingerprintUri(uri, fingerprints, '&'); - } else { + + XmppUri.lameUrlEncode(this.getJid().asBareJid().toString()); + if (fingerprints.isEmpty()) { return uri; + } else { + return XmppUri.getFingerprintUri(uri, fingerprints, '&'); } } diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java index de76947173e3bb1de56a8f4f0105457f8bd853f6..2a009482806658f8a7da4dec44796f988dd2d65c 100644 --- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -1,13 +1,15 @@ package eu.siacs.conversations.entities; import android.content.Context; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; - +import eu.siacs.conversations.utils.StringUtils; +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.Jid; import java.lang.ref.WeakReference; import java.util.Collections; import java.util.HashMap; @@ -15,265 +17,260 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import eu.siacs.conversations.utils.StringUtils; -import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.InvalidJid; -import eu.siacs.conversations.xmpp.Jid; - public class Bookmark extends Element implements ListItem { - private final Account account; - private WeakReference conversation; - private Jid jid; - protected Element extensions = new Element("extensions", Namespace.BOOKMARKS2); - - public Bookmark(final Account account, final Jid jid) { - super("conference"); - this.jid = jid; - this.setAttribute("jid", jid); - this.account = account; - } - - private Bookmark(Account account) { - super("conference"); - this.account = account; - } - - public static Map parseFromStorage(Element storage, Account account) { - if (storage == null) { - return Collections.emptyMap(); - } - final HashMap bookmarks = new HashMap<>(); - for (final Element item : storage.getChildren()) { - if (item.getName().equals("conference")) { - final Bookmark bookmark = Bookmark.parse(item, account); - if (bookmark != null) { - final Bookmark old = bookmarks.put(bookmark.jid, bookmark); - if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) { - bookmark.setBookmarkName(old.getBookmarkName()); - } - } - } - } - return bookmarks; - } - - public static Map parseFromPubSub(final Element pubSub, final Account account) { - if (pubSub == null) { - return Collections.emptyMap(); - } - final Element items = pubSub.findChild("items"); - if (items != null && Namespace.BOOKMARKS2.equals(items.getAttribute("node"))) { - final Map bookmarks = new HashMap<>(); - for(Element item : items.getChildren()) { - if (item.getName().equals("item")) { - final Bookmark bookmark = Bookmark.parseFromItem(item, account); - if (bookmark != null) { - bookmarks.put(bookmark.jid, bookmark); - } - } - } - return bookmarks; - } - return Collections.emptyMap(); - } - - public static Bookmark parse(Element element, Account account) { - Bookmark bookmark = new Bookmark(account); - bookmark.setAttributes(element.getAttributes()); - bookmark.setChildren(element.getChildren()); - bookmark.jid = InvalidJid.getNullForInvalid(bookmark.getAttributeAsJid("jid")); - if (bookmark.jid == null) { - return null; - } - return bookmark; - } - - public static Bookmark parseFromItem(Element item, Account account) { - final Element conference = item.findChild("conference", Namespace.BOOKMARKS2); - if (conference == null) { - return null; - } - final Bookmark bookmark = new Bookmark(account); - bookmark.jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("id")); - // TODO verify that we only use bare jids and ignore full jids - if (bookmark.jid == null) { - return null; - } - bookmark.setBookmarkName(conference.getAttribute("name")); - bookmark.setAutojoin(conference.getAttributeAsBoolean("autojoin")); - bookmark.setNick(conference.findChildContent("nick")); - bookmark.setPassword(conference.findChildContent("password")); - final Element extensions = conference.findChild("extensions", Namespace.BOOKMARKS2); - if (extensions != null) { - bookmark.extensions = extensions; - } - return bookmark; - } - - public Element getExtensions() { - return extensions; - } - - public void setAutojoin(boolean autojoin) { - if (autojoin) { - this.setAttribute("autojoin", "true"); - } else { - this.setAttribute("autojoin", "false"); - } - } - - @Override - public int compareTo(final @NonNull ListItem another) { - return this.getDisplayName().compareToIgnoreCase( - another.getDisplayName()); - } - - @Override - public String getDisplayName() { - final Conversation c = getConversation(); - final String name = getBookmarkName(); - if (c != null) { - return c.getName().toString(); - } else if (printableValue(name, false)) { - return name.trim(); - } else { - Jid jid = this.getJid(); - return jid != null && jid.getLocal() != null ? jid.getLocal() : ""; - } - } - - public static boolean printableValue(@Nullable String value, boolean permitNone) { - return value != null && !value.trim().isEmpty() && (permitNone || !"None".equals(value)); - } - - public static boolean printableValue(@Nullable String value) { - return printableValue(value, true); - } - - @Override - public Jid getJid() { - return this.jid; - } - - public Jid getFullJid() { - final String nick = Strings.nullToEmpty(getNick()).trim(); - if (jid == null || nick.isEmpty()) { - return jid; - } - try { - return jid.withResource(nick); - } catch (final IllegalArgumentException e) { - return jid; - } - } - - @Override - public List getTags(final Context context) { - final ImmutableList.Builder tags = new ImmutableList.Builder<>(); - for (final Element element : getChildren()) { - final String content = element.getContent(); - if (Strings.isNullOrEmpty(content)) { - continue; - } - if (element.getName().equals("group")) { - tags.add(new Tag(content)); - } - } - return tags.build(); - } - - public String getNick() { - return Strings.emptyToNull(this.findChildContent("nick")); - } - - public void setNick(String nick) { - Element element = this.findChild("nick"); - if (element == null) { - element = this.addChild("nick"); - } - element.setContent(nick); - } - - public boolean autojoin() { - return this.getAttributeAsBoolean("autojoin"); - } - - public String getPassword() { - return this.findChildContent("password"); - } - - public void setPassword(String password) { - Element element = this.findChild("password"); - if (element != null) { - element.setContent(password); - } - } - - @Override - public boolean match(Context context, String needle) { - if (needle == null) { - return true; - } - needle = needle.toLowerCase(Locale.US); - final Jid jid = getJid(); - return (jid != null && jid.toString().contains(needle)) || - getDisplayName().toLowerCase(Locale.US).contains(needle) || - matchInTag(context, needle); - } - - private boolean matchInTag(Context context, String needle) { - needle = needle.toLowerCase(Locale.US); - for (Tag tag : getTags(context)) { - if (tag.getName().toLowerCase(Locale.US).contains(needle)) { - return true; - } - } - return false; - } - - public Account getAccount() { - return this.account; - } - - public synchronized Conversation getConversation() { - return this.conversation != null ? this.conversation.get() : null; - } - - public synchronized void setConversation(Conversation conversation) { - if (this.conversation != null) { - this.conversation.clear(); - } - if (conversation == null) { - this.conversation = null; - } else { - this.conversation = new WeakReference<>(conversation); - } - } - - public String getBookmarkName() { - return this.getAttribute("name"); - } - - public boolean setBookmarkName(String name) { - String before = getBookmarkName(); - if (name != null) { - this.setAttribute("name", name); - } else { - this.removeAttribute("name"); - } - return StringUtils.changed(before, name); - } - - @Override - public int getAvatarBackgroundColor() { - return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName()); - } - - @Override - public String getAvatarName() { - return getDisplayName(); - } + private final Account account; + private WeakReference conversation; + private Jid jid; + protected Element extensions = new Element("extensions", Namespace.BOOKMARKS2); + + public Bookmark(final Account account, final Jid jid) { + super("conference"); + this.jid = jid; + this.setAttribute("jid", jid); + this.account = account; + } + + private Bookmark(Account account) { + super("conference"); + this.account = account; + } + + public static Map parseFromStorage(Element storage, Account account) { + if (storage == null) { + return Collections.emptyMap(); + } + final HashMap bookmarks = new HashMap<>(); + for (final Element item : storage.getChildren()) { + if (item.getName().equals("conference")) { + final Bookmark bookmark = Bookmark.parse(item, account); + if (bookmark != null) { + final Bookmark old = bookmarks.put(bookmark.jid, bookmark); + if (old != null + && old.getBookmarkName() != null + && bookmark.getBookmarkName() == null) { + bookmark.setBookmarkName(old.getBookmarkName()); + } + } + } + } + return bookmarks; + } + + public static Map parseFromPubSub(final Element pubSub, final Account account) { + if (pubSub == null) { + return Collections.emptyMap(); + } + final Element items = pubSub.findChild("items"); + if (items != null && Namespace.BOOKMARKS2.equals(items.getAttribute("node"))) { + final Map bookmarks = new HashMap<>(); + for (Element item : items.getChildren()) { + if (item.getName().equals("item")) { + final Bookmark bookmark = Bookmark.parseFromItem(item, account); + if (bookmark != null) { + bookmarks.put(bookmark.jid, bookmark); + } + } + } + return bookmarks; + } + return Collections.emptyMap(); + } + + public static Bookmark parse(Element element, Account account) { + Bookmark bookmark = new Bookmark(account); + bookmark.setAttributes(element.getAttributes()); + bookmark.setChildren(element.getChildren()); + bookmark.jid = Jid.Invalid.getNullForInvalid(bookmark.getAttributeAsJid("jid")); + if (bookmark.jid == null) { + return null; + } + return bookmark; + } + + public static Bookmark parseFromItem(Element item, Account account) { + final Element conference = item.findChild("conference", Namespace.BOOKMARKS2); + if (conference == null) { + return null; + } + final Bookmark bookmark = new Bookmark(account); + bookmark.jid = Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("id")); + // TODO verify that we only use bare jids and ignore full jids + if (bookmark.jid == null) { + return null; + } + bookmark.setBookmarkName(conference.getAttribute("name")); + bookmark.setAutojoin(conference.getAttributeAsBoolean("autojoin")); + bookmark.setNick(conference.findChildContent("nick")); + bookmark.setPassword(conference.findChildContent("password")); + final Element extensions = conference.findChild("extensions", Namespace.BOOKMARKS2); + if (extensions != null) { + bookmark.extensions = extensions; + } + return bookmark; + } + + public Element getExtensions() { + return extensions; + } + + public void setAutojoin(boolean autojoin) { + if (autojoin) { + this.setAttribute("autojoin", "true"); + } else { + this.setAttribute("autojoin", "false"); + } + } + + @Override + public int compareTo(final @NonNull ListItem another) { + return this.getDisplayName().compareToIgnoreCase(another.getDisplayName()); + } + + @Override + public String getDisplayName() { + final Conversation c = getConversation(); + final String name = getBookmarkName(); + if (c != null) { + return c.getName().toString(); + } else if (printableValue(name, false)) { + return name.trim(); + } else { + Jid jid = this.getJid(); + return jid != null && jid.getLocal() != null ? jid.getLocal() : ""; + } + } + + public static boolean printableValue(@Nullable String value, boolean permitNone) { + return value != null && !value.trim().isEmpty() && (permitNone || !"None".equals(value)); + } + + public static boolean printableValue(@Nullable String value) { + return printableValue(value, true); + } + + @Override + public Jid getJid() { + return this.jid; + } + + public Jid getFullJid() { + final String nick = Strings.nullToEmpty(getNick()).trim(); + if (jid == null || nick.isEmpty()) { + return jid; + } + try { + return jid.withResource(nick); + } catch (final IllegalArgumentException e) { + return jid; + } + } + + @Override + public List getTags(final Context context) { + final ImmutableList.Builder tags = new ImmutableList.Builder<>(); + for (final Element element : getChildren()) { + final String content = element.getContent(); + if (Strings.isNullOrEmpty(content)) { + continue; + } + if (element.getName().equals("group")) { + tags.add(new Tag(content)); + } + } + return tags.build(); + } + + public String getNick() { + return Strings.emptyToNull(this.findChildContent("nick")); + } + + public void setNick(String nick) { + Element element = this.findChild("nick"); + if (element == null) { + element = this.addChild("nick"); + } + element.setContent(nick); + } + + public boolean autojoin() { + return this.getAttributeAsBoolean("autojoin"); + } + + public String getPassword() { + return this.findChildContent("password"); + } + + public void setPassword(String password) { + Element element = this.findChild("password"); + if (element != null) { + element.setContent(password); + } + } + + @Override + public boolean match(Context context, String needle) { + if (needle == null) { + return true; + } + needle = needle.toLowerCase(Locale.US); + final Jid jid = getJid(); + return (jid != null && jid.toString().contains(needle)) + || getDisplayName().toLowerCase(Locale.US).contains(needle) + || matchInTag(context, needle); + } + + private boolean matchInTag(Context context, String needle) { + needle = needle.toLowerCase(Locale.US); + for (Tag tag : getTags(context)) { + if (tag.getName().toLowerCase(Locale.US).contains(needle)) { + return true; + } + } + return false; + } + + public Account getAccount() { + return this.account; + } + + public synchronized Conversation getConversation() { + return this.conversation != null ? this.conversation.get() : null; + } + + public synchronized void setConversation(Conversation conversation) { + if (this.conversation != null) { + this.conversation.clear(); + } + if (conversation == null) { + this.conversation = null; + } else { + this.conversation = new WeakReference<>(conversation); + } + } + + public String getBookmarkName() { + return this.getAttribute("name"); + } + + public boolean setBookmarkName(String name) { + String before = getBookmarkName(); + if (name != null) { + this.setAttribute("name", name); + } else { + this.removeAttribute("name"); + } + return StringUtils.changed(before, name); + } + + @Override + public int getAvatarBackgroundColor() { + return UIHelper.getColorForName( + jid != null ? jid.asBareJid().toString() : getDisplayName()); + } + + @Override + public String getAvatarName() { + return getDisplayName(); + } } diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 6437cffa338882a4740c4b3f3b488239532429fc..b15381e58c0e63824c999b6a2cb55c29094496c7 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -5,22 +5,8 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.text.TextUtils; - import androidx.annotation.NonNull; - import com.google.common.base.Strings; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Objects; - import eu.siacs.conversations.Config; import eu.siacs.conversations.android.AbstractPhoneContact; import eu.siacs.conversations.android.JabberIdContact; @@ -31,6 +17,15 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.jingle.RtpCapability; import eu.siacs.conversations.xmpp.pep.Avatar; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; public class Contact implements ListItem, Blockable { public static final String TABLENAME = "contacts"; @@ -69,10 +64,21 @@ public class Contact implements ListItem, Blockable { private String mLastPresence = null; private RtpCapability.Capability rtpCapability; - public Contact(final String account, final String systemName, final String serverName, final String presenceName, - final Jid jid, final int subscription, final String photoUri, - final Uri systemAccount, final String keys, final String avatar, final long lastseen, - final String presence, final String groups, final RtpCapability.Capability rtpCapability) { + public Contact( + final String account, + final String systemName, + final String serverName, + final String presenceName, + final Jid jid, + final int subscription, + final String photoUri, + final Uri systemAccount, + final String keys, + final String avatar, + final long lastseen, + final String presence, + final String groups, + final RtpCapability.Capability rtpCapability) { this.accountUuid = account; this.systemName = systemName; this.serverName = serverName; @@ -91,7 +97,7 @@ public class Contact implements ListItem, Blockable { if (avatar != null) { this.avatar = new Avatar(); this.avatar.sha1sum = avatar; - this.avatar.origin = Avatar.Origin.VCARD; //always assume worst + this.avatar.origin = Avatar.Origin.VCARD; // always assume worst } try { this.groups = (groups == null ? new JSONArray() : new JSONArray(groups)); @@ -122,7 +128,8 @@ public class Contact implements ListItem, Blockable { } catch (Exception e) { systemAccount = null; } - return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)), + return new Contact( + cursor.getString(cursor.getColumnIndex(ACCOUNT)), cursor.getString(cursor.getColumnIndex(SYSTEMNAME)), cursor.getString(cursor.getColumnIndex(SERVERNAME)), cursor.getString(cursor.getColumnIndex(PRESENCE_NAME)), @@ -135,7 +142,8 @@ public class Contact implements ListItem, Blockable { cursor.getLong(cursor.getColumnIndex(LAST_TIME)), cursor.getString(cursor.getColumnIndex(LAST_PRESENCE)), cursor.getString(cursor.getColumnIndex(GROUPS)), - RtpCapability.Capability.of(cursor.getString(cursor.getColumnIndex(RTP_CAPABILITY)))); + RtpCapability.Capability.of( + cursor.getString(cursor.getColumnIndex(RTP_CAPABILITY)))); } public String getDisplayName() { @@ -151,12 +159,15 @@ public class Contact implements ListItem, Blockable { return this.systemName; } else if (!TextUtils.isEmpty(this.serverName)) { return this.serverName; - } else if (!TextUtils.isEmpty(this.presenceName) && ((QuickConversationsService.isQuicksy() && JidHelper.isQuicksyDomain(jid.getDomain())) || mutualPresenceSubscription())) { + } else if (!TextUtils.isEmpty(this.presenceName) + && ((QuickConversationsService.isQuicksy() + && JidHelper.isQuicksyDomain(jid.getDomain())) + || mutualPresenceSubscription())) { return this.presenceName; } else if (jid.getLocal() != null) { return JidHelper.localPartOrFallback(jid); } else { - return jid.getDomain().toEscapedString(); + return jid.getDomain().toString(); } } @@ -166,7 +177,7 @@ public class Contact implements ListItem, Blockable { } else if (jid.getLocal() != null) { return JidHelper.localPartOrFallback(jid); } else { - return jid.getDomain().toEscapedString(); + return jid.getDomain().toString(); } } @@ -201,9 +212,9 @@ public class Contact implements ListItem, Blockable { } return true; } else { - return jid.toString().contains(needle) || - getDisplayName().toLowerCase(Locale.US).contains(needle) || - matchInTag(context, needle); + return jid.toString().contains(needle) + || getDisplayName().toLowerCase(Locale.US).contains(needle) + || matchInTag(context, needle); } } @@ -354,8 +365,8 @@ public class Contact implements ListItem, Blockable { } public boolean showInRoster() { - return (this.getOption(Contact.Options.IN_ROSTER) && (!this - .getOption(Contact.Options.DIRTY_DELETE))) + return (this.getOption(Contact.Options.IN_ROSTER) + && (!this.getOption(Contact.Options.DIRTY_DELETE))) || (this.getOption(Contact.Options.DIRTY_PUSH)); } @@ -430,12 +441,11 @@ public class Contact implements ListItem, Blockable { @Override public int compareTo(@NonNull final ListItem another) { - return this.getDisplayName().compareToIgnoreCase( - another.getDisplayName()); + return this.getDisplayName().compareToIgnoreCase(another.getDisplayName()); } public String getServer() { - return getJid().getDomain().toEscapedString(); + return getJid().getDomain().toString(); } public void setAvatar(Avatar avatar) { @@ -446,7 +456,10 @@ public class Contact implements ListItem, Blockable { if (this.avatar != null && this.avatar.equals(avatar)) { return; } - if (!previouslyOmittedPepFetch && this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) { + if (!previouslyOmittedPepFetch + && this.avatar != null + && this.avatar.origin == Avatar.Origin.PEP + && avatar.origin == Avatar.Origin.VCARD) { return; } this.avatar = avatar; @@ -561,7 +574,8 @@ public class Contact implements ListItem, Blockable { @Override public int getAvatarBackgroundColor() { - return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName()); + return UIHelper.getColorForName( + jid != null ? jid.asBareJid().toString() : getDisplayName()); } @Override diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 1f192d57a22f1bc1693a47f1946cfc2d6049fd44..6394ee1a477e936b81a89fe19b0a46e38edcd1b4 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -137,8 +137,7 @@ public class Conversation extends AbstractEntity cursor.getString(cursor.getColumnIndexOrThrow(NAME)), cursor.getString(cursor.getColumnIndexOrThrow(CONTACT)), cursor.getString(cursor.getColumnIndexOrThrow(ACCOUNT)), - JidHelper.parseOrFallbackToInvalid( - cursor.getString(cursor.getColumnIndexOrThrow(CONTACTJID))), + Jid.ofOrInvalid(cursor.getString(cursor.getColumnIndexOrThrow(CONTACTJID))), cursor.getLong(cursor.getColumnIndexOrThrow(CREATED)), cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)), cursor.getInt(cursor.getColumnIndexOrThrow(MODE)), diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 5129d0bfdd774d1204da41fff4379320193b6bba..a1edc2a036d7970f5d5370b7dd6cefc107fb3e24 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -1,14 +1,10 @@ package eu.siacs.conversations.entities; -import android.text.TextUtils; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.services.AvatarService; @@ -21,7 +17,6 @@ import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Field; import eu.siacs.conversations.xmpp.pep.Avatar; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -68,7 +63,8 @@ public class MucOptions { public boolean setSelf(User user) { this.self = user; final boolean roleChanged = this.conversation.setAttribute("role", user.role.toString()); - final boolean affiliationChanged = this.conversation.setAttribute("affiliation", user.affiliation.toString()); + final boolean affiliationChanged = + this.conversation.setAttribute("affiliation", user.affiliation.toString()); return roleChanged || affiliationChanged; } @@ -123,21 +119,30 @@ public class MucOptions { final var identities = serviceDiscoveryResult.getIdentities(); final String identityName = !identities.isEmpty() ? identities.get(0).getName() : null; final Jid jid = conversation.getJid(); - if (identityName != null && !identityName.equals(jid == null ? null : jid.getEscapedLocal())) { + if (identityName != null && !identityName.equals(jid == null ? null : jid.getLocal())) { name = identityName; } else { name = null; } } boolean changed = conversation.setAttribute("muc_name", name); - changed |= conversation.setAttribute(Conversation.ATTRIBUTE_MEMBERS_ONLY, this.hasFeature("muc_membersonly")); - changed |= conversation.setAttribute(Conversation.ATTRIBUTE_MODERATED, this.hasFeature("muc_moderated")); - changed |= conversation.setAttribute(Conversation.ATTRIBUTE_NON_ANONYMOUS, this.hasFeature("muc_nonanonymous")); + changed |= + conversation.setAttribute( + Conversation.ATTRIBUTE_MEMBERS_ONLY, this.hasFeature("muc_membersonly")); + changed |= + conversation.setAttribute( + Conversation.ATTRIBUTE_MODERATED, this.hasFeature("muc_moderated")); + changed |= + conversation.setAttribute( + Conversation.ATTRIBUTE_NON_ANONYMOUS, this.hasFeature("muc_nonanonymous")); return changed; } private Data getRoomInfoForm() { - final List forms = serviceDiscoveryResult == null ? Collections.emptyList() : serviceDiscoveryResult.forms; + final List forms = + serviceDiscoveryResult == null + ? Collections.emptyList() + : serviceDiscoveryResult.forms; return forms.isEmpty() ? new Data() : forms.get(0); } @@ -146,7 +151,8 @@ public class MucOptions { } public boolean hasFeature(String feature) { - return this.serviceDiscoveryResult != null && this.serviceDiscoveryResult.features.contains(feature); + return this.serviceDiscoveryResult != null + && this.serviceDiscoveryResult.features.contains(feature); } public boolean hasVCards() { @@ -154,7 +160,8 @@ public class MucOptions { } public boolean canInvite() { - final boolean hasPermission = !membersOnly() || self.getRole().ranks(Role.MODERATOR) || allowInvites(); + final boolean hasPermission = + !membersOnly() || self.getRole().ranks(Role.MODERATOR) || allowInvites(); return hasPermission && online(); } @@ -177,7 +184,7 @@ public class MucOptions { public boolean allowPm() { final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowpm"); if (field == null) { - return true; //fall back if field does not exists + return true; // fall back if field does not exists } if ("anyone".equals(field.getValue())) { return true; @@ -192,7 +199,7 @@ public class MucOptions { public boolean allowPmRaw() { final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowpm"); - return field == null || Arrays.asList("anyone","participants").contains(field.getValue()); + return field == null || Arrays.asList("anyone", "participants").contains(field.getValue()); } public boolean participating() { @@ -204,7 +211,9 @@ public class MucOptions { } public List getFeatures() { - return this.serviceDiscoveryResult != null ? this.serviceDiscoveryResult.features : Collections.emptyList(); + return this.serviceDiscoveryResult != null + ? this.serviceDiscoveryResult.features + : Collections.emptyList(); } public boolean nonanonymous() { @@ -240,7 +249,8 @@ public class MucOptions { break; } } - boolean self = user.realJid != null && user.realJid.equals(account.getJid().asBareJid()); + boolean self = + user.realJid != null && user.realJid.equals(account.getJid().asBareJid()); if (membersOnly() && nonanonymous() && user.affiliation.ranks(Affiliation.MEMBER) @@ -257,7 +267,7 @@ public class MucOptions { return user; } - //returns true if real jid was new; + // returns true if real jid was new; public boolean updateUser(User user) { User old; boolean realJidFound = false; @@ -266,7 +276,7 @@ public class MucOptions { realJidFound = old != null; if (old != null) { if (old.fullJid != null) { - return false; //don't add. user already exists + return false; // don't add. user already exists } else { synchronized (users) { users.remove(old); @@ -288,7 +298,10 @@ public class MucOptions { if (old != null) { users.remove(old); } - boolean fullJidIsSelf = isOnline && user.getFullJid() != null && user.getFullJid().equals(self.getFullJid()); + boolean fullJidIsSelf = + isOnline + && user.getFullJid() != null + && user.getFullJid().equals(self.getFullJid()); if ((!membersOnly() || user.getAffiliation().ranks(Affiliation.MEMBER)) && user.getAffiliation().outranks(Affiliation.OUTCAST) && !fullJidIsSelf) { @@ -329,7 +342,9 @@ public class MucOptions { public User findUserByOccupantId(final String occupantId) { synchronized (this.users) { - return Strings.isNullOrEmpty(occupantId) ? null : Iterables.find(this.users, u -> occupantId.equals(u.occupantId),null); + return Strings.isNullOrEmpty(occupantId) + ? null + : Iterables.find(this.users, u -> occupantId.equals(u.occupantId), null); } } @@ -345,7 +360,8 @@ public class MucOptions { public User findUser(ReadByMarker readByMarker) { if (readByMarker.getRealJid() != null) { - return findOrCreateUserByRealJid(readByMarker.getRealJid().asBareJid(), readByMarker.getFullJid()); + return findOrCreateUserByRealJid( + readByMarker.getRealJid().asBareJid(), readByMarker.getFullJid()); } else if (readByMarker.getFullJid() != null) { return findUserByFullJid(readByMarker.getFullJid()); } else { @@ -361,7 +377,7 @@ public class MucOptions { if (existing != null) { return existing; } else if (reaction.from != null) { - return new User(this,reaction.from); + return new User(this, reaction.from); } else { return null; } @@ -369,7 +385,7 @@ public class MucOptions { public List findUsers(final Collection reactions) { final ImmutableList.Builder builder = new ImmutableList.Builder<>(); - for(final Reaction reaction : reactions) { + for (final Reaction reaction : reactions) { final var user = findUser(reaction); if (user != null) { builder.add(user); @@ -400,7 +416,8 @@ public class MucOptions { synchronized (users) { ArrayList users = new ArrayList<>(); for (User user : this.users) { - if (!user.isDomain() && (includeOffline || user.getRole().ranks(Role.PARTICIPANT))) { + if (!user.isDomain() + && (includeOffline || user.getRole().ranks(Role.PARTICIPANT))) { users.add(user); } } @@ -429,7 +446,8 @@ public class MucOptions { jids.add(account.getJid().asBareJid()); synchronized (users) { for (User user : users) { - if (user.getRealJid() == null || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) { + if (user.getRealJid() == null + || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) { subset.add(user); } if (subset.size() >= max) { @@ -445,7 +463,8 @@ public class MucOptions { HashSet jids = new HashSet<>(); for (User user : users) { jids.add(user.getAccount().getJid().asBareJid()); - if (user.getRealJid() == null || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) { + if (user.getRealJid() == null + || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) { subset.add(user); } if (subset.size() >= max) { @@ -477,7 +496,8 @@ public class MucOptions { public String getProposedNickPure() { final Bookmark bookmark = this.conversation.getBookmark(); - final String bookmarkedNick = normalize(account.getJid(), bookmark == null ? null : bookmark.getNick()); + final String bookmarkedNick = + normalize(account.getJid(), bookmark == null ? null : bookmark.getNick()); if (bookmarkedNick != null) { return bookmarkedNick; } else { @@ -503,7 +523,6 @@ public class MucOptions { } catch (final IllegalArgumentException e) { return null; } - } public String getActualNick() { @@ -649,7 +668,8 @@ public class MucOptions { public String getPassword() { this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD); - if (this.password == null && conversation.getBookmark() != null + if (this.password == null + && conversation.getBookmark() != null && conversation.getBookmark().getPassword() != null) { return conversation.getBookmark().getPassword(); } else { @@ -674,7 +694,12 @@ public class MucOptions { ArrayList members = new ArrayList<>(); synchronized (users) { for (User user : users) { - if (user.affiliation.ranks(Affiliation.MEMBER) && user.realJid != null && !user.realJid.asBareJid().equals(conversation.account.getJid().asBareJid()) && (!user.isDomain() || includeDomains)) { + if (user.affiliation.ranks(Affiliation.MEMBER) + && user.realJid != null + && !user.realJid + .asBareJid() + .equals(conversation.account.getJid().asBareJid()) + && (!user.isDomain() || includeDomains)) { members.add(user.realJid); } } @@ -790,9 +815,7 @@ public class MucOptions { void onFailure(); } - public interface OnRenameListener extends OnEventListener { - - } + public interface OnRenameListener extends OnEventListener {} public static class User implements Comparable, AvatarService.Avatarable { private Role role = Role.NONE; @@ -867,7 +890,10 @@ public class MucOptions { if (avatar != null) { return avatar.getFilename(); } - Avatar avatar = realJid != null ? getAccount().getRoster().getContact(realJid).getAvatar() : null; + Avatar avatar = + realJid != null + ? getAccount().getRoster().getContact(realJid).getAvatar() + : null; return avatar == null ? null : avatar.getFilename(); } @@ -895,7 +921,6 @@ public class MucOptions { if (realJid != null ? !realJid.equals(user.realJid) : user.realJid != null) return false; return fullJid != null ? fullJid.equals(user.fullJid) : user.fullJid == null; - } public boolean isDomain() { @@ -913,7 +938,13 @@ public class MucOptions { @Override public String toString() { - return "[fulljid:" + fullJid + ",realjid:" + realJid + ",affiliation" + affiliation.toString() + "]"; + return "[fulljid:" + + fullJid + + ",realjid:" + + realJid + + ",affiliation" + + affiliation.toString() + + "]"; } public boolean realJidMatchesAccount() { diff --git a/src/main/java/eu/siacs/conversations/entities/RawBlockable.java b/src/main/java/eu/siacs/conversations/entities/RawBlockable.java index 664a6a1c938a705509f5ade4b927141dc5b83e6c..97f63d99cfe6eb4a13bb224687a28f0b3309dda4 100644 --- a/src/main/java/eu/siacs/conversations/entities/RawBlockable.java +++ b/src/main/java/eu/siacs/conversations/entities/RawBlockable.java @@ -2,14 +2,12 @@ package eu.siacs.conversations.entities; import android.content.Context; import android.text.TextUtils; - +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.Jid; import java.util.Collections; import java.util.List; import java.util.Locale; -import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.xmpp.Jid; - public class RawBlockable implements ListItem, Blockable { private final Account account; @@ -40,7 +38,7 @@ public class RawBlockable implements ListItem, Blockable { if (jid.isFullJid()) { return jid.getResource(); } else { - return jid.toEscapedString(); + return jid.toString(); } } @@ -62,7 +60,7 @@ public class RawBlockable implements ListItem, Blockable { needle = needle.toLowerCase(Locale.US).trim(); String[] parts = needle.split("\\s+"); for (String part : parts) { - if (!jid.toEscapedString().contains(part)) { + if (!jid.toString().contains(part)) { return false; } } @@ -76,7 +74,7 @@ public class RawBlockable implements ListItem, Blockable { @Override public int getAvatarBackgroundColor() { - return UIHelper.getColorForName(jid.toEscapedString()); + return UIHelper.getColorForName(jid.toString()); } @Override @@ -86,7 +84,6 @@ public class RawBlockable implements ListItem, Blockable { @Override public int compareTo(ListItem o) { - return this.getDisplayName().compareToIgnoreCase( - o.getDisplayName()); + return this.getDisplayName().compareToIgnoreCase(o.getDisplayName()); } -} \ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/entities/Reaction.java b/src/main/java/eu/siacs/conversations/entities/Reaction.java index 3a945675f7aa225ee88fcbf3b60ea44d82238292..cd860cd1fdbfa50e10072742f95569c2ddc9910e 100644 --- a/src/main/java/eu/siacs/conversations/entities/Reaction.java +++ b/src/main/java/eu/siacs/conversations/entities/Reaction.java @@ -1,9 +1,7 @@ package eu.siacs.conversations.entities; import android.util.Log; - import androidx.annotation.NonNull; - import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import com.google.common.collect.Collections2; @@ -20,11 +18,9 @@ import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; - import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.Emoticons; import eu.siacs.conversations.xmpp.Jid; - import java.io.IOException; import java.util.Arrays; import java.util.Collection; @@ -137,7 +133,7 @@ public class Reaction { if (value == null) { out.nullValue(); } else { - out.value(value.toEscapedString()); + out.value(value.toString()); } } @@ -148,7 +144,7 @@ public class Reaction { return null; } else if (in.peek() == JsonToken.STRING) { final String value = in.nextString(); - return Jid.ofEscaped(value); + return Jid.of(value); } throw new IOException("Unexpected token"); } diff --git a/src/main/java/eu/siacs/conversations/entities/Room.java b/src/main/java/eu/siacs/conversations/entities/Room.java index 9e1d61fc3dc904ebeef044edd4552c59cb5dc3eb..c702c3189bfde4306b067b8448b718d6ab173731 100644 --- a/src/main/java/eu/siacs/conversations/entities/Room.java +++ b/src/main/java/eu/siacs/conversations/entities/Room.java @@ -3,7 +3,6 @@ package eu.siacs.conversations.entities; import com.google.common.base.Objects; import com.google.common.base.Strings; import com.google.common.collect.ComparisonChain; - import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.utils.LanguageUtils; import eu.siacs.conversations.utils.UIHelper; @@ -25,9 +24,7 @@ public class Room implements AvatarService.Avatarable, Comparable { this.nusers = nusers; } - public Room() { - - } + public Room() {} public String getName() { return name; @@ -52,7 +49,7 @@ public class Room implements AvatarService.Avatarable, Comparable { @Override public int getAvatarBackgroundColor() { Jid room = getRoom(); - return UIHelper.getColorForName(room != null ? room.asBareJid().toEscapedString() : name); + return UIHelper.getColorForName(room != null ? room.asBareJid().toString() : name); } @Override @@ -65,9 +62,9 @@ public class Room implements AvatarService.Avatarable, Comparable { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Room room = (Room) o; - return Objects.equal(address, room.address) && - Objects.equal(name, room.name) && - Objects.equal(description, room.description); + return Objects.equal(address, room.address) + && Objects.equal(name, room.name) + && Objects.equal(description, room.description); } @Override @@ -75,7 +72,6 @@ public class Room implements AvatarService.Avatarable, Comparable { return Objects.hashCode(address, name, description); } - public boolean contains(String needle) { return Strings.nullToEmpty(name).contains(needle) || Strings.nullToEmpty(description).contains(needle) @@ -90,4 +86,4 @@ public class Room implements AvatarService.Avatarable, Comparable { .compare(Strings.nullToEmpty(address), Strings.nullToEmpty(o.address)) .result(); } -} \ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 85e3a0d7c762e0f4b9bb69be132761d6ae488408..9a4dad2a5af411ae4e916a1349199d2fc5dcdd90 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -1,25 +1,8 @@ package eu.siacs.conversations.generator; - import android.os.Bundle; import android.util.Base64; import android.util.Log; - -import org.whispersystems.libsignal.IdentityKey; -import org.whispersystems.libsignal.ecc.ECPublicKey; -import org.whispersystems.libsignal.state.PreKeyRecord; -import org.whispersystems.libsignal.state.SignedPreKeyRecord; - -import java.nio.ByteBuffer; -import java.security.cert.CertificateEncodingException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.TimeZone; -import java.util.UUID; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.AxolotlService; @@ -35,6 +18,19 @@ 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 java.nio.ByteBuffer; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.TimeZone; +import java.util.UUID; +import org.whispersystems.libsignal.IdentityKey; +import org.whispersystems.libsignal.ecc.ECPublicKey; +import org.whispersystems.libsignal.state.PreKeyRecord; +import org.whispersystems.libsignal.state.SignedPreKeyRecord; public class IqGenerator extends AbstractGenerator { @@ -152,7 +148,7 @@ public class IqGenerator extends AbstractGenerator { final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); final Element retract = pubsub.addChild("retract"); retract.setAttribute("node", node); - retract.setAttribute("notify","true"); + retract.setAttribute("notify", "true"); retract.addChild("item").setAttribute("id", id); return packet; } @@ -165,7 +161,8 @@ public class IqGenerator extends AbstractGenerator { return publish(Namespace.AVATAR_DATA, item, options); } - public Iq publishElement(final String namespace, final Element element, String id, final Bundle options) { + public Iq publishElement( + final String namespace, final Element element, String id, final Bundle options) { final Element item = new Element("item"); item.setAttribute("id", id); item.addChild(element); @@ -175,8 +172,7 @@ public class IqGenerator extends AbstractGenerator { public Iq publishAvatarMetadata(final Avatar avatar, final Bundle options) { final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); - final Element metadata = item - .addChild("metadata", Namespace.AVATAR_METADATA); + final Element metadata = item.addChild("metadata", Namespace.AVATAR_METADATA); final Element info = metadata.addChild("info"); info.setAttribute("bytes", avatar.size); info.setAttribute("id", avatar.sha1sum); @@ -263,7 +259,7 @@ public class IqGenerator extends AbstractGenerator { if (password != null) { conference.addChild("password").setContent(password); } - conference.setAttribute("autojoin",String.valueOf(autojoin)); + conference.setAttribute("autojoin", String.valueOf(autojoin)); conference.addChild(bookmark.getExtensions()); return conference; } @@ -286,8 +282,12 @@ public class IqGenerator extends AbstractGenerator { return displayed; } - public Iq publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, - final Set preKeyRecords, final int deviceId, Bundle publishOptions) { + public Iq publishBundles( + final SignedPreKeyRecord signedPreKeyRecord, + final IdentityKey identityKey, + final Set preKeyRecords, + final int deviceId, + Bundle publishOptions) { final Element item = new Element("item"); item.setAttribute("id", "current"); final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX); @@ -296,21 +296,26 @@ public class IqGenerator extends AbstractGenerator { ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey(); signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(), Base64.NO_WRAP)); final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature"); - signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.NO_WRAP)); + signedPreKeySignature.setContent( + Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.NO_WRAP)); final Element identityKeyElement = bundle.addChild("identityKey"); - identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.NO_WRAP)); + identityKeyElement.setContent( + Base64.encodeToString(identityKey.serialize(), Base64.NO_WRAP)); final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX); for (PreKeyRecord preKeyRecord : preKeyRecords) { final Element prekey = prekeys.addChild("preKeyPublic"); prekey.setAttribute("preKeyId", preKeyRecord.getId()); - prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.NO_WRAP)); + prekey.setContent( + Base64.encodeToString( + preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.NO_WRAP)); } return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions); } - public Iq publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) { + public Iq publishVerification( + byte[] signature, X509Certificate[] certificates, final int deviceId) { final Element item = new Element("item"); item.setAttribute("id", "current"); final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX); @@ -318,13 +323,16 @@ public class IqGenerator extends AbstractGenerator { for (int i = 0; i < certificates.length; ++i) { try { Element certificate = chain.addChild("certificate"); - certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.NO_WRAP)); + certificate.setContent( + Base64.encodeToString(certificates[i].getEncoded(), Base64.NO_WRAP)); certificate.setAttribute("index", i); } catch (CertificateEncodingException e) { Log.d(Config.LOGTAG, "could not encode certificate"); } } - verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.NO_WRAP)); + verification + .addChild("signature") + .setContent(Base64.encodeToString(signature, Base64.NO_WRAP)); return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item); } @@ -337,7 +345,7 @@ public class IqGenerator extends AbstractGenerator { if (mam.muc()) { packet.setTo(mam.getWith()); } else if (mam.getWith() != null) { - data.put("with", mam.getWith().toEscapedString()); + data.put("with", mam.getWith().toString()); } final long start = mam.getStart(); final long end = mam.getEnd(); @@ -366,7 +374,8 @@ public class IqGenerator extends AbstractGenerator { return iq; } - public Iq generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) { + public Iq generateSetBlockRequest( + final Jid jid, final boolean reportSpam, final String serverMsgId) { final Iq iq = new Iq(Iq.Type.SET); final Element block = iq.addChild("block", Namespace.BLOCKING); final Element item = block.addChild("item").setAttribute("jid", jid); @@ -457,7 +466,9 @@ public class IqGenerator extends AbstractGenerator { ByteBuffer bb = ByteBuffer.wrap(new byte[16]); bb.putLong(uuid.getMostSignificantBits()); bb.putLong(uuid.getLeastSignificantBits()); - return Base64.encodeToString(bb.array(), Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP) + name.substring(pos); + return Base64.encodeToString( + bb.array(), Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP) + + name.substring(pos); } catch (Exception e) { return name; } @@ -466,7 +477,8 @@ public class IqGenerator extends AbstractGenerator { } } - public static Iq generateCreateAccountWithCaptcha(final Account account, final String id, final Data data) { + public static Iq generateCreateAccountWithCaptcha( + final Account account, final String id, final Data data) { final Iq register = new Iq(Iq.Type.SET); register.setFrom(account.getJid().asBareJid()); register.setTo(account.getDomain()); @@ -492,7 +504,7 @@ public class IqGenerator extends AbstractGenerator { data.put("token", token); data.put("android-id", deviceId); if (muc != null) { - data.put("muc", muc.toEscapedString()); + data.put("muc", muc.toString()); } data.submit(); command.addChild(data); @@ -539,7 +551,9 @@ public class IqGenerator extends AbstractGenerator { public Iq queryAffiliation(Conversation conversation, String affiliation) { final Iq packet = new Iq(Iq.Type.GET); packet.setTo(conversation.getJid().asBareJid()); - packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation); + packet.query("http://jabber.org/protocol/muc#admin") + .addChild("item") + .setAttribute("affiliation", affiliation); return packet; } @@ -551,9 +565,9 @@ public class IqGenerator extends AbstractGenerator { options.putString("muc#roomconfig_whois", "anyone"); options.putString("muc#roomconfig_changesubject", "0"); options.putString("muc#roomconfig_allowinvites", "0"); - options.putString("muc#roomconfig_enablearchiving", "1"); //prosody - options.putString("mam", "1"); //ejabberd community - options.putString("muc#roomconfig_mam", "1"); //ejabberd saas + options.putString("muc#roomconfig_enablearchiving", "1"); // prosody + options.putString("mam", "1"); // ejabberd community + options.putString("muc#roomconfig_mam", "1"); // ejabberd saas return options; } @@ -564,9 +578,9 @@ public class IqGenerator extends AbstractGenerator { options.putString("muc#roomconfig_publicroom", "1"); options.putString("muc#roomconfig_whois", "moderators"); options.putString("muc#roomconfig_changesubject", "0"); - options.putString("muc#roomconfig_enablearchiving", "1"); //prosody - options.putString("mam", "1"); //ejabberd community - options.putString("muc#roomconfig_mam", "1"); //ejabberd saas + options.putString("muc#roomconfig_enablearchiving", "1"); // prosody + options.putString("mam", "1"); // ejabberd community + options.putString("muc#roomconfig_mam", "1"); // ejabberd saas return options; } @@ -592,14 +606,14 @@ public class IqGenerator extends AbstractGenerator { public Iq queryDiscoItems(final Jid jid) { final Iq packet = new Iq(Iq.Type.GET); packet.setTo(jid); - packet.addChild("query",Namespace.DISCO_ITEMS); + packet.addChild("query", Namespace.DISCO_ITEMS); return packet; } public Iq queryDiscoInfo(final Jid jid) { final Iq packet = new Iq(Iq.Type.GET); packet.setTo(jid); - packet.addChild("query",Namespace.DISCO_INFO); + packet.addChild("query", Namespace.DISCO_INFO); return packet; } } diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index ac42857fd1dc96866a9cd011c6c38cf87679d2ee..8f5178d8e83ad08b77e63a8f2619afed2302d4ca 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -1,93 +1,93 @@ package eu.siacs.conversations.parser; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; - import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import im.conversations.android.xmpp.model.stanza.Stanza; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; public abstract class AbstractParser { - protected final XmppConnectionService mXmppConnectionService; - protected final Account account; - - protected AbstractParser(final XmppConnectionService service, final Account account) { - this.mXmppConnectionService = service; - this.account = account; - } - - public static Long parseTimestamp(Element element, Long d) { - return parseTimestamp(element,d,false); - } - - public static Long parseTimestamp(Element element, Long d, boolean ignoreCsiAndSm) { - long min = Long.MAX_VALUE; - boolean returnDefault = true; - final Jid to; - if (ignoreCsiAndSm && element instanceof Stanza stanza) { - to = stanza.getTo(); - } else { - to = null; - } - for(Element child : element.getChildren()) { - if ("delay".equals(child.getName()) && "urn:xmpp:delay".equals(child.getNamespace())) { - final Jid f = to == null ? null : InvalidJid.getNullForInvalid(child.getAttributeAsJid("from")); - if (f != null && (to.asBareJid().equals(f) || to.getDomain().equals(f))) { - continue; - } - final String stamp = child.getAttribute("stamp"); - if (stamp != null) { - try { - min = Math.min(min,AbstractParser.parseTimestamp(stamp)); - returnDefault = false; - } catch (Throwable t) { - //ignore - } - } - } - } - if (returnDefault) { - return d; - } else { - return min; - } - } - - public static long parseTimestamp(Element element) { - return parseTimestamp(element, System.currentTimeMillis()); - } - - public static long parseTimestamp(String timestamp) throws ParseException { - timestamp = timestamp.replace("Z", "+0000"); - SimpleDateFormat dateFormat; - long ms; - if (timestamp.length() >= 25 && timestamp.charAt(19) == '.') { - String millis = timestamp.substring(19,timestamp.length() - 5); - try { - double fractions = Double.parseDouble("0" + millis); - ms = Math.round(1000 * fractions); - } catch (NumberFormatException e) { - ms = 0; - } - } else { - ms = 0; - } - timestamp = timestamp.substring(0,19)+timestamp.substring(timestamp.length() -5); - dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US); - return Math.min(dateFormat.parse(timestamp).getTime()+ms, System.currentTimeMillis()); - } + protected final XmppConnectionService mXmppConnectionService; + protected final Account account; + + protected AbstractParser(final XmppConnectionService service, final Account account) { + this.mXmppConnectionService = service; + this.account = account; + } + + public static Long parseTimestamp(Element element, Long d) { + return parseTimestamp(element, d, false); + } + + public static Long parseTimestamp(Element element, Long d, boolean ignoreCsiAndSm) { + long min = Long.MAX_VALUE; + boolean returnDefault = true; + final Jid to; + if (ignoreCsiAndSm && element instanceof Stanza stanza) { + to = stanza.getTo(); + } else { + to = null; + } + for (Element child : element.getChildren()) { + if ("delay".equals(child.getName()) && "urn:xmpp:delay".equals(child.getNamespace())) { + final Jid f = + to == null + ? null + : Jid.Invalid.getNullForInvalid(child.getAttributeAsJid("from")); + if (f != null && (to.asBareJid().equals(f) || to.getDomain().equals(f))) { + continue; + } + final String stamp = child.getAttribute("stamp"); + if (stamp != null) { + try { + min = Math.min(min, AbstractParser.parseTimestamp(stamp)); + returnDefault = false; + } catch (Throwable t) { + // ignore + } + } + } + } + if (returnDefault) { + return d; + } else { + return min; + } + } + + public static long parseTimestamp(Element element) { + return parseTimestamp(element, System.currentTimeMillis()); + } + + public static long parseTimestamp(String timestamp) throws ParseException { + timestamp = timestamp.replace("Z", "+0000"); + SimpleDateFormat dateFormat; + long ms; + if (timestamp.length() >= 25 && timestamp.charAt(19) == '.') { + String millis = timestamp.substring(19, timestamp.length() - 5); + try { + double fractions = Double.parseDouble("0" + millis); + ms = Math.round(1000 * fractions); + } catch (NumberFormatException e) { + ms = 0; + } + } else { + ms = 0; + } + timestamp = timestamp.substring(0, 19) + timestamp.substring(timestamp.length() - 5); + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US); + return Math.min(dateFormat.parse(timestamp).getTime() + ms, System.currentTimeMillis()); + } public static long getTimestamp(final String input) throws ParseException { if (input == null) { @@ -120,93 +120,94 @@ public abstract class AbstractParser { } } - protected void updateLastseen(final Account account, final Jid from) { - final Contact contact = account.getRoster().getContact(from); - contact.setLastResource(from.isBareJid() ? "" : from.getResource()); - } - - protected static String avatarData(Element items) { - Element item = items.findChild("item"); - if (item == null) { - return null; - } - return item.findChildContent("data", "urn:xmpp:avatar:data"); - } - - public static MucOptions.User parseItem(Conversation conference, Element item) { - return parseItem(conference,item, null); - } - - public static MucOptions.User parseItem(final Conversation conference, Element item, Jid fullJid) { - final String local = conference.getJid().getLocal(); - final String domain = conference.getJid().getDomain().toEscapedString(); - String affiliation = item.getAttribute("affiliation"); - String role = item.getAttribute("role"); - String nick = item.getAttribute("nick"); - if (nick != null && fullJid == null) { - try { - fullJid = Jid.of(local, domain, nick); - } catch (IllegalArgumentException e) { - fullJid = null; - } - } - final Jid realJid = item.getAttributeAsJid("jid"); - MucOptions.User user = new MucOptions.User(conference.getMucOptions(), fullJid); - if (InvalidJid.isValid(realJid)) { - user.setRealJid(realJid); - } - user.setAffiliation(affiliation); - user.setRole(role); - return user; - } - - public static String extractErrorMessage(final Element packet) { - final Element error = packet.findChild("error"); - if (error != null && error.getChildren().size() > 0) { - final List errorNames = orderedElementNames(error.getChildren()); - final String text = error.findChildContent("text"); - if (text != null && !text.trim().isEmpty()) { - return prefixError(errorNames)+text; - } else if (errorNames.size() > 0){ - return prefixError(errorNames)+errorNames.get(0).replace("-"," "); - } - } - return null; - } - - public static String errorMessage(Element packet) { - final Element error = packet.findChild("error"); - if (error != null && error.getChildren().size() > 0) { - final List errorNames = orderedElementNames(error.getChildren()); - final String text = error.findChildContent("text"); - if (text != null && !text.trim().isEmpty()) { - return text; - } else if (errorNames.size() > 0){ - return errorNames.get(0).replace("-"," "); - } - } - return null; - } - - private static String prefixError(List errorNames) { - if (errorNames.size() > 0) { - return errorNames.get(0)+'\u001f'; - } - return ""; - } - - private static List orderedElementNames(List children) { - List names = new ArrayList<>(); - for(Element child : children) { - final String name = child.getName(); - if (name != null && !name.equals("text")) { - if ("urn:ietf:params:xml:ns:xmpp-stanzas".equals(child.getNamespace())) { - names.add(name); - } else { - names.add(0, name); - } - } - } - return names; - } + protected void updateLastseen(final Account account, final Jid from) { + final Contact contact = account.getRoster().getContact(from); + contact.setLastResource(from.isBareJid() ? "" : from.getResource()); + } + + protected static String avatarData(Element items) { + Element item = items.findChild("item"); + if (item == null) { + return null; + } + return item.findChildContent("data", "urn:xmpp:avatar:data"); + } + + public static MucOptions.User parseItem(Conversation conference, Element item) { + return parseItem(conference, item, null); + } + + public static MucOptions.User parseItem( + final Conversation conference, Element item, Jid fullJid) { + final String local = conference.getJid().getLocal(); + final String domain = conference.getJid().getDomain().toString(); + String affiliation = item.getAttribute("affiliation"); + String role = item.getAttribute("role"); + String nick = item.getAttribute("nick"); + if (nick != null && fullJid == null) { + try { + fullJid = Jid.of(local, domain, nick); + } catch (IllegalArgumentException e) { + fullJid = null; + } + } + final Jid realJid = item.getAttributeAsJid("jid"); + MucOptions.User user = new MucOptions.User(conference.getMucOptions(), fullJid); + if (Jid.Invalid.isValid(realJid)) { + user.setRealJid(realJid); + } + user.setAffiliation(affiliation); + user.setRole(role); + return user; + } + + public static String extractErrorMessage(final Element packet) { + final Element error = packet.findChild("error"); + if (error != null && error.getChildren().size() > 0) { + final List errorNames = orderedElementNames(error.getChildren()); + final String text = error.findChildContent("text"); + if (text != null && !text.trim().isEmpty()) { + return prefixError(errorNames) + text; + } else if (errorNames.size() > 0) { + return prefixError(errorNames) + errorNames.get(0).replace("-", " "); + } + } + return null; + } + + public static String errorMessage(Element packet) { + final Element error = packet.findChild("error"); + if (error != null && error.getChildren().size() > 0) { + final List errorNames = orderedElementNames(error.getChildren()); + final String text = error.findChildContent("text"); + if (text != null && !text.trim().isEmpty()) { + return text; + } else if (errorNames.size() > 0) { + return errorNames.get(0).replace("-", " "); + } + } + return null; + } + + private static String prefixError(List errorNames) { + if (errorNames.size() > 0) { + return errorNames.get(0) + '\u001f'; + } + return ""; + } + + private static List orderedElementNames(List children) { + List names = new ArrayList<>(); + for (Element child : children) { + final String name = child.getName(); + if (name != null && !name.equals("text")) { + if ("urn:ietf:params:xml:ns:xmpp-stanzas".equals(child.getNamespace())) { + names.add(name); + } else { + names.add(0, name); + } + } + } + return names; + } } diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 339c025c9ac160d6e2ee3e689d0f2cf62a582452..14efffd532f2846c388e364e9929a098bcf9a6db 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -3,31 +3,9 @@ package eu.siacs.conversations.parser; import android.text.TextUtils; import android.util.Log; import android.util.Pair; - import androidx.annotation.NonNull; - import com.google.common.base.CharMatcher; import com.google.common.io.BaseEncoding; - -import org.whispersystems.libsignal.IdentityKey; -import org.whispersystems.libsignal.InvalidKeyException; -import org.whispersystems.libsignal.ecc.Curve; -import org.whispersystems.libsignal.ecc.ECPublicKey; -import org.whispersystems.libsignal.state.PreKeyBundle; - -import java.io.ByteArrayInputStream; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; - import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; @@ -36,11 +14,27 @@ import eu.siacs.conversations.entities.Room; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.forms.Data; import im.conversations.android.xmpp.model.stanza.Iq; +import java.io.ByteArrayInputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import org.whispersystems.libsignal.IdentityKey; +import org.whispersystems.libsignal.InvalidKeyException; +import org.whispersystems.libsignal.ecc.Curve; +import org.whispersystems.libsignal.ecc.ECPublicKey; +import org.whispersystems.libsignal.state.PreKeyBundle; public class IqParser extends AbstractParser implements Consumer { @@ -94,8 +88,7 @@ public class IqParser extends AbstractParser implements Consumer { TextUtils.isEmpty(roomName) ? name : roomName, description, language, - nusers - ); + nusers); } private void rosterItems(final Account account, final Element query) { @@ -105,14 +98,16 @@ public class IqParser extends AbstractParser implements Consumer { } for (final Element item : query.getChildren()) { if (item.getName().equals("item")) { - final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); + final Jid jid = Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("jid")); if (jid == null) { continue; } final String name = item.getAttribute("name"); final String subscription = item.getAttribute("subscription"); final Contact contact = account.getRoster().getContact(jid); - boolean bothPre = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); + boolean bothPre = + contact.getOption(Contact.Options.TO) + && contact.getOption(Contact.Options.FROM); if (!contact.getOption(Contact.Options.DIRTY_PUSH)) { contact.setServerName(name); contact.parseGroupsFromElement(item); @@ -126,9 +121,15 @@ public class IqParser extends AbstractParser implements Consumer { contact.resetOption(Contact.Options.DIRTY_PUSH); contact.parseSubscriptionFromElement(item); } - boolean both = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); + boolean both = + contact.getOption(Contact.Options.TO) + && contact.getOption(Contact.Options.FROM); if ((both != bothPre) && both) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": gained mutual presence subscription with " + contact.getJid()); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": gained mutual presence subscription with " + + contact.getJid()); AxolotlService axolotlService = account.getAxolotlService(); if (axolotlService != null) { axolotlService.clearErrorsInFetchStatusMap(contact.getJid()); @@ -181,7 +182,15 @@ public class IqParser extends AbstractParser implements Consumer { Integer id = Integer.valueOf(device.getAttribute("id")); deviceIds.add(id); } catch (NumberFormatException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered invalid node in PEP (" + e.getMessage() + "):" + device.toString() + ", skipping..."); + Log.e( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Encountered invalid node in PEP (" + + e.getMessage() + + "):" + + device.toString() + + ", skipping..."); } } } @@ -210,7 +219,12 @@ public class IqParser extends AbstractParser implements Consumer { try { publicKey = Curve.decodePoint(base64decode(signedPreKeyPublic), 0); } catch (final IllegalArgumentException | InvalidKeyException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid signedPreKeyPublic in PEP: " + e.getMessage()); + Log.e( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Invalid signedPreKeyPublic in PEP: " + + e.getMessage()); } return publicKey; } @@ -223,7 +237,9 @@ public class IqParser extends AbstractParser implements Consumer { try { return base64decode(signedPreKeySignature); } catch (final IllegalArgumentException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : Invalid base64 in signedPreKeySignature"); + Log.e( + Config.LOGTAG, + AxolotlService.LOGPREFIX + " : Invalid base64 in signedPreKeySignature"); return null; } } @@ -236,7 +252,12 @@ public class IqParser extends AbstractParser implements Consumer { try { return new IdentityKey(base64decode(identityKey), 0); } catch (final IllegalArgumentException | InvalidKeyException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid identityKey in PEP: " + e.getMessage()); + Log.e( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Invalid identityKey in PEP: " + + e.getMessage()); return null; } } @@ -245,7 +266,12 @@ public class IqParser extends AbstractParser implements Consumer { Map preKeyRecords = new HashMap<>(); Element item = getItem(packet); if (item == null) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find in bundle IQ packet: " + packet); + Log.d( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Couldn't find in bundle IQ packet: " + + packet); return null; } final Element bundleElement = item.findChild("bundle"); @@ -254,12 +280,22 @@ public class IqParser extends AbstractParser implements Consumer { } final Element prekeysElement = bundleElement.findChild("prekeys"); if (prekeysElement == null) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find in bundle IQ packet: " + packet); + Log.d( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Couldn't find in bundle IQ packet: " + + packet); return null; } for (Element preKeyPublicElement : prekeysElement.getChildren()) { if (!preKeyPublicElement.getName().equals("preKeyPublic")) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered unexpected tag in prekeys list: " + preKeyPublicElement); + Log.d( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Encountered unexpected tag in prekeys list: " + + preKeyPublicElement); continue; } final String preKey = preKeyPublicElement.getContent(); @@ -272,9 +308,22 @@ public class IqParser extends AbstractParser implements Consumer { final ECPublicKey preKeyPublic = Curve.decodePoint(base64decode(preKey), 0); preKeyRecords.put(preKeyId, preKeyPublic); } catch (NumberFormatException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "could not parse preKeyId from preKey " + preKeyPublicElement.toString()); + Log.e( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "could not parse preKeyId from preKey " + + preKeyPublicElement.toString()); } catch (Throwable e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid preKeyPublic (ID=" + preKeyId + ") in PEP: " + e.getMessage() + ", skipping..."); + Log.e( + Config.LOGTAG, + AxolotlService.LOGPREFIX + + " : " + + "Invalid preKeyPublic (ID=" + + preKeyId + + ") in PEP: " + + e.getMessage() + + ", skipping..."); } } return preKeyRecords; @@ -286,7 +335,8 @@ public class IqParser extends AbstractParser implements Consumer { public static Pair verification(final Iq packet) { Element item = getItem(packet); - Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null; + Element verification = + item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null; Element chain = verification != null ? verification.findChild("chain") : null; String signature = verification != null ? verification.findChildContent("signature") : null; if (chain != null && signature != null) { @@ -300,7 +350,11 @@ public class IqParser extends AbstractParser implements Consumer { if (cert == null) { continue; } - certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(BaseEncoding.base64().decode(cert))); + certificates[i] = + (X509Certificate) + certificateFactory.generateCertificate( + new ByteArrayInputStream( + BaseEncoding.base64().decode(cert))); ++i; } return new Pair<>(certificates, BaseEncoding.base64().decode(signature)); @@ -332,8 +386,15 @@ public class IqParser extends AbstractParser implements Consumer { || signedPreKeySignature.length == 0) { return null; } - return new PreKeyBundle(0, 0, 0, null, - signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); + return new PreKeyBundle( + 0, + 0, + 0, + null, + signedPreKeyId, + signedPreKeyPublic, + signedPreKeySignature, + identityKey); } public static List preKeys(final Iq preKeys) { @@ -342,8 +403,7 @@ public class IqParser extends AbstractParser implements Consumer { if (preKeyPublics != null) { for (Integer preKeyId : preKeyPublics.keySet()) { ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId); - bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, - 0, null, null, null)); + bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, 0, null, null, null)); } } @@ -363,15 +423,19 @@ public class IqParser extends AbstractParser implements Consumer { account.getRoster().markAllAsNotInRoster(); } this.rosterItems(account, query); - } else if ((packet.hasChild("block", Namespace.BLOCKING) || packet.hasChild("blocklist", Namespace.BLOCKING)) && - packet.fromServer(account)) { + } else if ((packet.hasChild("block", Namespace.BLOCKING) + || packet.hasChild("blocklist", Namespace.BLOCKING)) + && packet.fromServer(account)) { // Block list or block push. Log.d(Config.LOGTAG, "Received blocklist update from server"); final Element blocklist = packet.findChild("blocklist", Namespace.BLOCKING); final Element block = packet.findChild("block", Namespace.BLOCKING); - final Collection items = blocklist != null ? blocklist.getChildren() : - (block != null ? block.getChildren() : null); - // If this is a response to a blocklist query, clear the block list and replace with the new one. + final Collection items = + blocklist != null + ? blocklist.getChildren() + : (block != null ? block.getChildren() : null); + // If this is a response to a blocklist query, clear the block list and replace with the + // new one. // Otherwise, just update the existing blocklist. if (packet.getType() == Iq.Type.RESULT) { account.clearBlocklist(); @@ -382,7 +446,8 @@ public class IqParser extends AbstractParser implements Consumer { // Create a collection of Jids from the packet for (final Element item : items) { if (item.getName().equals("item")) { - final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); + final Jid jid = + Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("jid")); if (jid != null) { jids.add(jid); } @@ -405,10 +470,12 @@ public class IqParser extends AbstractParser implements Consumer { final Iq response = packet.generateResponse(Iq.Type.RESULT); mXmppConnectionService.sendIqPacket(account, response, null); } - } else if (packet.hasChild("unblock", Namespace.BLOCKING) && - packet.fromServer(account) && packet.getType() == Iq.Type.SET) { + } else if (packet.hasChild("unblock", Namespace.BLOCKING) + && packet.fromServer(account) + && packet.getType() == Iq.Type.SET) { Log.d(Config.LOGTAG, "Received unblock update from server"); - final Collection items = packet.findChild("unblock", Namespace.BLOCKING).getChildren(); + final Collection items = + packet.findChild("unblock", Namespace.BLOCKING).getChildren(); if (items.isEmpty()) { // No children to unblock == unblock all account.getBlocklist().clear(); @@ -416,7 +483,8 @@ public class IqParser extends AbstractParser implements Consumer { final Collection jids = new ArrayList<>(items.size()); for (final Element item : items) { if (item.getName().equals("item")) { - final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); + final Jid jid = + Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("jid")); if (jid != null) { jids.add(jid); } @@ -430,10 +498,10 @@ public class IqParser extends AbstractParser implements Consumer { } else if (packet.hasChild("open", "http://jabber.org/protocol/ibb") || packet.hasChild("data", "http://jabber.org/protocol/ibb") || packet.hasChild("close", "http://jabber.org/protocol/ibb")) { - mXmppConnectionService.getJingleConnectionManager() - .deliverIbbPacket(account, packet); + mXmppConnectionService.getJingleConnectionManager().deliverIbbPacket(account, packet); } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { - final Iq response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet); + final Iq response = + mXmppConnectionService.getIqGenerator().discoResponse(account, packet); mXmppConnectionService.sendIqPacket(account, response, null); } else if (packet.hasChild("query", "jabber:iq:version") && isGet) { final Iq response = mXmppConnectionService.getIqGenerator().versionResponse(packet); @@ -452,7 +520,8 @@ public class IqParser extends AbstractParser implements Consumer { response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet); } mXmppConnectionService.sendIqPacket(account, response, null); - } else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) && packet.getType() == Iq.Type.SET) { + } else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) + && packet.getType() == Iq.Type.SET) { final Jid transport = packet.getFrom(); final Element push = packet.findChild("push", Namespace.UNIFIED_PUSH); final boolean success = @@ -480,5 +549,4 @@ public class IqParser extends AbstractParser implements Consumer { } } } - } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 80430e38477b44959216b84e0aec0559d04dc2d0..9188c1b661ab828341f3fb9ec6363c44f517e581 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -31,7 +31,6 @@ import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.LocalizedContent; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; @@ -95,7 +94,7 @@ public class MessageParser extends AbstractParser for (Element child : packet.getChildren()) { if (child.getName().equals("stanza-id") && Namespace.STANZA_IDS.equals(child.getNamespace()) - && by.equals(InvalidJid.getNullForInvalid(child.getAttributeAsJid("by")))) { + && by.equals(Jid.Invalid.getNullForInvalid(child.getAttributeAsJid("by")))) { return child.getAttribute("id"); } } @@ -105,7 +104,7 @@ public class MessageParser extends AbstractParser private static Jid getTrueCounterpart(Element mucUserElement, Jid fallback) { final Element item = mucUserElement == null ? null : mucUserElement.findChild("item"); Jid result = - item == null ? null : InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); + item == null ? null : Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("jid")); return result != null ? result : fallback; } @@ -154,7 +153,7 @@ public class MessageParser extends AbstractParser final XmppAxolotlMessage xmppAxolotlMessage; try { xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.asBareJid()); - } catch (Exception e) { + } catch (final Exception e) { Log.d( Config.LOGTAG, conversation.getAccount().getJid().asBareJid() @@ -224,13 +223,13 @@ public class MessageParser extends AbstractParser final Element invite = mucUser.findChild("invite"); if (invite != null) { final String password = mucUser.findChildContent("password"); - final Jid from = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("from")); - final Jid to = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("to")); + final Jid from = Jid.Invalid.getNullForInvalid(invite.getAttributeAsJid("from")); + final Jid to = Jid.Invalid.getNullForInvalid(invite.getAttributeAsJid("to")); if (to != null && from == null) { Log.d(Config.LOGTAG, "do not parse outgoing mediated invite " + message); return null; } - final Jid room = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from")); + final Jid room = Jid.Invalid.getNullForInvalid(message.getAttributeAsJid("from")); if (room == null) { return null; } @@ -239,8 +238,8 @@ public class MessageParser extends AbstractParser } final Element conference = message.findChild("x", "jabber:x:conference"); if (conference != null) { - Jid from = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from")); - Jid room = InvalidJid.getNullForInvalid(conference.getAttributeAsJid("jid")); + Jid from = Jid.Invalid.getNullForInvalid(message.getAttributeAsJid("from")); + Jid room = Jid.Invalid.getNullForInvalid(conference.getAttributeAsJid("jid")); if (room == null) { return null; } @@ -333,7 +332,7 @@ public class MessageParser extends AbstractParser } } if (retract != null) { - final Jid id = InvalidJid.getNullForInvalid(retract.getAttributeAsJid("id")); + final Jid id = Jid.Invalid.getNullForInvalid(retract.getAttributeAsJid("id")); if (id != null) { account.removeBookmark(id); Log.d( @@ -567,7 +566,7 @@ public class MessageParser extends AbstractParser } boolean notify = false; - if (from == null || !InvalidJid.isValid(from) || !InvalidJid.isValid(to)) { + if (from == null || !Jid.Invalid.isValid(from) || !Jid.Invalid.isValid(to)) { Log.e(Config.LOGTAG, "encountered invalid message from='" + from + "' to='" + to + "'"); return; } @@ -607,7 +606,7 @@ public class MessageParser extends AbstractParser occupant = null; } boolean isMucStatusMessage = - InvalidJid.hasValidFrom(packet) + Jid.Invalid.hasValidFrom(packet) && from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status"); @@ -668,7 +667,7 @@ public class MessageParser extends AbstractParser || mucUserElement != null || account.getXmppConnection() .getMucServersWithholdAccount() - .contains(counterpart.getDomain().toEscapedString()); + .contains(counterpart.getDomain().toString()); final Conversation conversation = mXmppConnectionService.findOrCreateConversation( account, @@ -1161,7 +1160,7 @@ public class MessageParser extends AbstractParser } if (conversation != null && mucUserElement != null - && InvalidJid.hasValidFrom(packet) + && Jid.Invalid.hasValidFrom(packet) && from.isBareJid()) { for (Element child : mucUserElement.getChildren()) { if ("status".equals(child.getName())) { @@ -1381,7 +1380,7 @@ public class MessageParser extends AbstractParser final Element event = original.findChild("event", "http://jabber.org/protocol/pubsub#event"); - if (event != null && InvalidJid.hasValidFrom(original) && original.getFrom().isBareJid()) { + if (event != null && Jid.Invalid.hasValidFrom(original) && original.getFrom().isBareJid()) { if (event.hasChild("items")) { parseEvent(event, original.getFrom(), account); } else if (event.hasChild("delete")) { @@ -1392,7 +1391,7 @@ public class MessageParser extends AbstractParser } final String nick = packet.findChildContent("nick", Namespace.NICK); - if (nick != null && InvalidJid.hasValidFrom(original)) { + if (nick != null && Jid.Invalid.hasValidFrom(original)) { if (mXmppConnectionService.isMuc(account, from)) { return; } @@ -1444,7 +1443,7 @@ public class MessageParser extends AbstractParser Jid from) { final var id = displayed.getId(); // TODO we don’t even use 'sender' any more. Remove this! - final Jid sender = InvalidJid.getNullForInvalid(displayed.getAttributeAsJid("sender")); + final Jid sender = Jid.Invalid.getNullForInvalid(displayed.getAttributeAsJid("sender")); if (packet.fromAccount(account) && !selfAddressed) { final Conversation c = mXmppConnectionService.find(account, counterpart.asBareJid()); final Message message = diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index e50d1096c26c66d6671e59d27fbd25cfc54b0aee..088938402ec24c4e6958dbd5660bfcddbf9a8fec 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -1,7 +1,6 @@ package eu.siacs.conversations.parser; import android.util.Log; - import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.crypto.axolotl.AxolotlService; @@ -17,24 +16,23 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.pep.Avatar; import im.conversations.android.xmpp.model.occupant.OccupantId; - -import org.openintents.openpgp.util.OpenPgpUtils; - import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import org.openintents.openpgp.util.OpenPgpUtils; -public class PresenceParser extends AbstractParser implements Consumer { +public class PresenceParser extends AbstractParser + implements Consumer { public PresenceParser(final XmppConnectionService service, final Account account) { super(service, account); } - public void parseConferencePresence(final im.conversations.android.xmpp.model.stanza.Presence packet, Account account) { + public void parseConferencePresence( + final im.conversations.android.xmpp.model.stanza.Presence packet, Account account) { final Conversation conversation = packet.getFrom() == null ? null @@ -58,7 +56,9 @@ public class PresenceParser extends AbstractParser implements Consumer= " + "account = ? AND transport = ? AND instance = ? AND endpoint IS NOT NULL" + + " AND expiration >= " + expiration, new String[] {account, transport, instance}, null, @@ -131,17 +130,26 @@ public class UnifiedPushDatabase extends SQLiteOpenHelper { public List deletePushTargets() { final SQLiteDatabase sqLiteDatabase = getReadableDatabase(); final ImmutableList.Builder builder = new ImmutableList.Builder<>(); - try (final Cursor cursor = sqLiteDatabase.query("push",new String[]{"application","instance"},null,null,null,null,null)) { + try (final Cursor cursor = + sqLiteDatabase.query( + "push", + new String[] {"application", "instance"}, + null, + null, + null, + null, + null)) { if (cursor != null && cursor.moveToFirst()) { - builder.add(new PushTarget( - cursor.getString(cursor.getColumnIndexOrThrow("application")), - cursor.getString(cursor.getColumnIndexOrThrow("instance")))); + builder.add( + new PushTarget( + cursor.getString(cursor.getColumnIndexOrThrow("application")), + cursor.getString(cursor.getColumnIndexOrThrow("instance")))); } } catch (final Exception e) { - Log.d(Config.LOGTAG,"unable to retrieve push targets",e); + Log.d(Config.LOGTAG, "unable to retrieve push targets", e); return builder.build(); } - sqLiteDatabase.delete("push",null,null); + sqLiteDatabase.delete("push", null, null); return builder.build(); } @@ -149,9 +157,10 @@ public class UnifiedPushDatabase extends SQLiteOpenHelper { final SQLiteDatabase sqLiteDatabase = getReadableDatabase(); try (final Cursor cursor = sqLiteDatabase.rawQuery( - "SELECT EXISTS(SELECT endpoint FROM push WHERE account = ? AND transport = ?)", + "SELECT EXISTS(SELECT endpoint FROM push WHERE account = ? AND transport =" + + " ?)", new String[] { - transport.account.getUuid(), transport.transport.toEscapedString() + transport.account.getUuid(), transport.transport.toString() })) { if (cursor != null && cursor.moveToFirst()) { return cursor.getInt(0) > 0; diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index e9e827c566e29a15d68817514aa8a617caf56dba..46021557624fff321a0cda32724cb2f7072ce701 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -16,17 +16,9 @@ import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.util.LruCache; - import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import androidx.core.content.res.ResourcesCompat; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -43,647 +35,706 @@ import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.XmppConnection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; public class AvatarService implements OnAdvancedStreamFeaturesLoaded { - private static final int FG_COLOR = 0xFFFAFAFA; - private static final int TRANSPARENT = 0x00000000; - private static final int PLACEHOLDER_COLOR = 0xFF202020; - - public static final int SYSTEM_UI_AVATAR_SIZE = 48; - - private static final String PREFIX_CONTACT = "contact"; - private static final String PREFIX_CONVERSATION = "conversation"; - private static final String PREFIX_ACCOUNT = "account"; - private static final String PREFIX_GENERIC = "generic"; - - private static final String CHANNEL_SYMBOL = "#"; - - final private Set sizes = new HashSet<>(); - final private HashMap> conversationDependentKeys = new HashMap<>(); - - protected XmppConnectionService mXmppConnectionService = null; - - AvatarService(XmppConnectionService service) { - this.mXmppConnectionService = service; - } - - public static int getSystemUiAvatarSize(final Context context) { - return (int) (SYSTEM_UI_AVATAR_SIZE * context.getResources().getDisplayMetrics().density); - } - - public Bitmap get(final Avatarable avatarable, final int size, final boolean cachedOnly) { - if (avatarable instanceof Account) { - return get((Account) avatarable,size,cachedOnly); - } else if (avatarable instanceof Conversation) { - return get((Conversation) avatarable, size, cachedOnly); - } else if (avatarable instanceof Message) { - return get((Message) avatarable, size, cachedOnly); - } else if (avatarable instanceof ListItem) { - return get((ListItem) avatarable, size, cachedOnly); - } else if (avatarable instanceof MucOptions.User) { - return get((MucOptions.User) avatarable, size, cachedOnly); - } else if (avatarable instanceof Room) { - return get((Room) avatarable, size, cachedOnly); - } - throw new AssertionError("AvatarService does not know how to generate avatar from "+avatarable.getClass().getName()); - - } - - private Bitmap get(final Room result, final int size, boolean cacheOnly) { - final Jid room = result.getRoom(); - Conversation conversation = room != null ? mXmppConnectionService.findFirstMuc(room) : null; - if (conversation != null) { - return get(conversation,size,cacheOnly); - } - return get(CHANNEL_SYMBOL, room != null ? room.asBareJid().toEscapedString() : result.getName(), size, cacheOnly); - } - - private Bitmap get(final Contact contact, final int size, boolean cachedOnly) { - if (contact.isSelf()) { - return get(contact.getAccount(), size, cachedOnly); - } - final String KEY = key(contact, size); - Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); - if (avatar != null || cachedOnly) { - return avatar; - } - if (contact.getAvatarFilename() != null && QuickConversationsService.isQuicksy()) { - avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatarFilename(), size); - } - if (avatar == null && contact.getProfilePhoto() != null) { - avatar = mXmppConnectionService.getFileBackend().cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size); - } - if (avatar == null && contact.getAvatarFilename() != null) { - avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatarFilename(), size); - } - if (avatar == null) { - avatar = get(contact.getDisplayName(), contact.getJid().asBareJid().toString(), size, false); - } - this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); - return avatar; - } - - public Bitmap getRoundedShortcut(final MucOptions mucOptions) { - final DisplayMetrics metrics = mXmppConnectionService.getResources().getDisplayMetrics(); - final int size = Math.round(metrics.density * 48); - final Bitmap bitmap = get(mucOptions, size, false); - final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(output); - final Paint paint = new Paint(); - drawAvatar(bitmap, canvas, paint); - return output; - } - - public Bitmap getRoundedShortcut(final Contact contact) { - return getRoundedShortcut(contact, false); - } - - public Bitmap getRoundedShortcutWithIcon(final Contact contact) { - return getRoundedShortcut(contact, true); - } - - private Bitmap getRoundedShortcut(final Contact contact, boolean withIcon) { - DisplayMetrics metrics = mXmppConnectionService.getResources().getDisplayMetrics(); - int size = Math.round(metrics.density * 48); - Bitmap bitmap = get(contact, size); - Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(output); - final Paint paint = new Paint(); - - drawAvatar(bitmap, canvas, paint); - if (withIcon) { - drawIcon(canvas, paint); - } - return output; - } - - private static void drawAvatar(Bitmap bitmap, Canvas canvas, Paint paint) { - final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); - paint.setAntiAlias(true); - canvas.drawARGB(0, 0, 0, 0); - canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - canvas.drawBitmap(bitmap, rect, rect, paint); - } - - private void drawIcon(Canvas canvas, Paint paint) { - final Resources resources = mXmppConnectionService.getResources(); - final Bitmap icon = getRoundLauncherIcon(resources); - if (icon == null) { - return; - } - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); - - int iconSize = Math.round(canvas.getHeight() / 2.6f); - - int left = canvas.getWidth() - iconSize; - int top = canvas.getHeight() - iconSize; - final Rect rect = new Rect(left, top, left + iconSize, top + iconSize); - canvas.drawBitmap(icon, null, rect, paint); - } - - private static Bitmap getRoundLauncherIcon(Resources resources) { - - final Drawable drawable = ResourcesCompat.getDrawable(resources, R.mipmap.new_launcher_round,null); - if (drawable == null) { - return null; - } - - if (drawable instanceof BitmapDrawable) { - return ((BitmapDrawable)drawable).getBitmap(); - } - - Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - - return bitmap; - } - - public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) { - Contact c = user.getContact(); - if (c != null && (c.getProfilePhoto() != null || c.getAvatarFilename() != null || user.getAvatar() == null)) { - return get(c, size, cachedOnly); - } else { - return getImpl(user, size, cachedOnly); - } - } - - private Bitmap getImpl(final MucOptions.User user, final int size, boolean cachedOnly) { - final String KEY = key(user, size); - Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); - if (avatar != null || cachedOnly) { - return avatar; - } - if (user.getAvatar() != null) { - avatar = mXmppConnectionService.getFileBackend().getAvatar(user.getAvatar(), size); - } - if (avatar == null) { - Contact contact = user.getContact(); - if (contact != null) { - avatar = get(contact, size, false); - } else { - String seed = user.getRealJid() != null ? user.getRealJid().asBareJid().toString() : null; - avatar = get(user.getName(), seed, size, false); - } - } - this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); - return avatar; - } - - public void clear(Contact contact) { - synchronized (this.sizes) { - for (final Integer size : sizes) { - this.mXmppConnectionService.getBitmapCache().remove(key(contact, size)); - } - } - for (Conversation conversation : mXmppConnectionService.findAllConferencesWith(contact)) { - MucOptions.User user = conversation.getMucOptions().findUserByRealJid(contact.getJid().asBareJid()); - if (user != null) { - clear(user); - } - clear(conversation); - } - } - - private String key(Contact contact, int size) { - synchronized (this.sizes) { - this.sizes.add(size); - } - return PREFIX_CONTACT + - '\0' + - contact.getAccount().getJid().asBareJid() + - '\0' + - emptyOnNull(contact.getJid()) + - '\0' + - size; - } - - private String key(MucOptions.User user, int size) { - synchronized (this.sizes) { - this.sizes.add(size); - } - return PREFIX_CONTACT + - '\0' + - user.getAccount().getJid().asBareJid() + - '\0' + - emptyOnNull(user.getFullJid()) + - '\0' + - emptyOnNull(user.getRealJid()) + - '\0' + - size; - } - - public Bitmap get(ListItem item, int size) { - return get(item, size, false); - } - - public Bitmap get(ListItem item, int size, boolean cachedOnly) { - if (item instanceof RawBlockable) { - return get(item.getDisplayName(), item.getJid().toEscapedString(), size, cachedOnly); - } else if (item instanceof Contact) { - return get((Contact) item, size, cachedOnly); - } else if (item instanceof Bookmark) { - Bookmark bookmark = (Bookmark) item; - if (bookmark.getConversation() != null) { - return get(bookmark.getConversation(), size, cachedOnly); - } else { - Jid jid = bookmark.getJid(); - Account account = bookmark.getAccount(); - Contact contact = jid == null ? null : account.getRoster().getContact(jid); - if (contact != null && contact.getAvatarFilename() != null) { - return get(contact, size, cachedOnly); - } - String seed = jid != null ? jid.asBareJid().toString() : null; - return get(bookmark.getDisplayName(), seed, size, cachedOnly); - } - } else { - String seed = item.getJid() != null ? item.getJid().asBareJid().toString() : null; - return get(item.getDisplayName(), seed, size, cachedOnly); - } - } - - public Bitmap get(Conversation conversation, int size) { - return get(conversation, size, false); - } - - public Bitmap get(Conversation conversation, int size, boolean cachedOnly) { - if (conversation.getMode() == Conversation.MODE_SINGLE) { - return get(conversation.getContact(), size, cachedOnly); - } else { - return get(conversation.getMucOptions(), size, cachedOnly); - } - } - - public void clear(Conversation conversation) { - if (conversation.getMode() == Conversation.MODE_SINGLE) { - clear(conversation.getContact()); - } else { - clear(conversation.getMucOptions()); - synchronized (this.conversationDependentKeys) { - Set keys = this.conversationDependentKeys.get(conversation.getUuid()); - if (keys == null) { - return; - } - LruCache cache = this.mXmppConnectionService.getBitmapCache(); - for (String key : keys) { - cache.remove(key); - } - keys.clear(); - } - } - } - - private Bitmap get(MucOptions mucOptions, int size, boolean cachedOnly) { - final String KEY = key(mucOptions, size); - Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); - if (bitmap != null || cachedOnly) { - return bitmap; - } - - bitmap = mXmppConnectionService.getFileBackend().getAvatar(mucOptions.getAvatar(), size); - - if (bitmap == null) { - Conversation c = mucOptions.getConversation(); - if (mucOptions.isPrivateAndNonAnonymous()) { - final List users = mucOptions.getUsersRelevantForNameAndAvatar(); - if (users.size() == 0) { - bitmap = getImpl(c.getName().toString(), c.getJid().asBareJid().toString(), size); - } else { - bitmap = getImpl(users, size); - } - } else { - bitmap = getImpl(CHANNEL_SYMBOL, c.getJid().asBareJid().toString(), size); - } - } - - this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap); - - return bitmap; - } - - private Bitmap get(List users, int size, boolean cachedOnly) { - final String KEY = key(users, size); - Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); - if (bitmap != null || cachedOnly) { - return bitmap; - } - bitmap = getImpl(users, size); - this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap); - return bitmap; - } - - private Bitmap getImpl(List users, int size) { - int count = users.size(); - Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - bitmap.eraseColor(TRANSPARENT); - if (count == 0) { - throw new AssertionError("Unable to draw tiles for 0 users"); - } else if (count == 1) { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); - drawTile(canvas, users.get(0).getAccount(), size / 2 + 1, 0, size, size); - } else if (count == 2) { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); - drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size); - } else if (count == 3) { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); - drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size / 2 - 1); - drawTile(canvas, users.get(2), size / 2 + 1, size / 2 + 1, size, - size); - } else if (count == 4) { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); - drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); - drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); - drawTile(canvas, users.get(3), size / 2 + 1, size / 2 + 1, size, - size); - } else { - drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); - drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); - drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); - drawTile(canvas, "\u2026", PLACEHOLDER_COLOR, size / 2 + 1, size / 2 + 1, - size, size); - } - return bitmap; - } - - public void clear(final MucOptions options) { - if (options == null) { - return; - } - synchronized (this.sizes) { - for (Integer size : sizes) { - this.mXmppConnectionService.getBitmapCache().remove(key(options, size)); - } - } - } - - private String key(final MucOptions options, int size) { - synchronized (this.sizes) { - this.sizes.add(size); - } - return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid() + "_" + size; - } - - private String key(List users, int size) { - final Conversation conversation = users.get(0).getConversation(); - StringBuilder builder = new StringBuilder("TILE_"); - builder.append(conversation.getUuid()); - - for (MucOptions.User user : users) { - builder.append("\0"); - builder.append(emptyOnNull(user.getRealJid())); - builder.append("\0"); - builder.append(emptyOnNull(user.getFullJid())); - } - builder.append('\0'); - builder.append(size); - final String key = builder.toString(); - synchronized (this.conversationDependentKeys) { - Set keys; - if (this.conversationDependentKeys.containsKey(conversation.getUuid())) { - keys = this.conversationDependentKeys.get(conversation.getUuid()); - } else { - keys = new HashSet<>(); - this.conversationDependentKeys.put(conversation.getUuid(), keys); - } - keys.add(key); - } - return key; - } - - public Bitmap get(Account account, int size) { - return get(account, size, false); - } - - public Bitmap get(Account account, int size, boolean cachedOnly) { - final String KEY = key(account, size); - Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY); - if (avatar != null || cachedOnly) { - return avatar; - } - avatar = mXmppConnectionService.getFileBackend().getAvatar(account.getAvatar(), size); - if (avatar == null) { - final String displayName = account.getDisplayName(); - final String jid = account.getJid().asBareJid().toEscapedString(); - if (QuickConversationsService.isQuicksy() && !TextUtils.isEmpty(displayName)) { - avatar = get(displayName, jid, size, false); - } else { - avatar = get(jid, null, size, false); - } - } - mXmppConnectionService.getBitmapCache().put(KEY, avatar); - return avatar; - } - - public Bitmap get(Message message, int size, boolean cachedOnly) { - final Conversational conversation = message.getConversation(); - if (message.getType() == Message.TYPE_STATUS && message.getCounterparts() != null && message.getCounterparts().size() > 1) { - return get(message.getCounterparts(), size, cachedOnly); - } else if (message.getStatus() == Message.STATUS_RECEIVED) { - Contact c = message.getContact(); - if (c != null && (c.getProfilePhoto() != null || c.getAvatarFilename() != null)) { - return get(c, size, cachedOnly); - } else if (conversation instanceof Conversation && message.getConversation().getMode() == Conversation.MODE_MULTI) { - final Jid trueCounterpart = message.getTrueCounterpart(); - final MucOptions mucOptions = ((Conversation) conversation).getMucOptions(); - MucOptions.User user; - if (trueCounterpart != null) { - user = mucOptions.findOrCreateUserByRealJid(trueCounterpart, message.getCounterpart()); - } else { - user = mucOptions.findUserByFullJid(message.getCounterpart()); - } - if (user != null) { - return getImpl(user, size, cachedOnly); - } - } else if (c != null) { - return get(c, size, cachedOnly); - } - Jid tcp = message.getTrueCounterpart(); - String seed = tcp != null ? tcp.asBareJid().toString() : null; - return get(UIHelper.getMessageDisplayName(message), seed, size, cachedOnly); - } else { - return get(conversation.getAccount(), size, cachedOnly); - } - } - - public void clear(Account account) { - synchronized (this.sizes) { - for (Integer size : sizes) { - this.mXmppConnectionService.getBitmapCache().remove(key(account, size)); - } - } - } - - public void clear(MucOptions.User user) { - synchronized (this.sizes) { - for (Integer size : sizes) { - this.mXmppConnectionService.getBitmapCache().remove(key(user, size)); - } - } - } - - private String key(Account account, int size) { - synchronized (this.sizes) { - this.sizes.add(size); - } - return PREFIX_ACCOUNT + "_" + account.getUuid() + "_" - + size; - } - - /*public Bitmap get(String name, int size) { - return get(name,null, size,false); - }*/ - - public Bitmap get(final String name, String seed, final int size, boolean cachedOnly) { - final String KEY = key(seed == null ? name : name+"\0"+seed, size); - Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY); - if (bitmap != null || cachedOnly) { - return bitmap; - } - bitmap = getImpl(name, seed, size); - mXmppConnectionService.getBitmapCache().put(KEY, bitmap); - return bitmap; - } - - public static Bitmap get(final Jid jid, final int size) { - return getImpl(jid.asBareJid().toEscapedString(), null, size); - } - - private static Bitmap getImpl(final String name, final String seed, final int size) { - Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - final String trimmedName = name == null ? "" : name.trim(); - drawTile(canvas, trimmedName, seed, 0, 0, size, size); - return bitmap; - } - - private String key(String name, int size) { - synchronized (this.sizes) { - this.sizes.add(size); - } - return PREFIX_GENERIC + "_" + name + "_" + size; - } - - private static boolean drawTile(Canvas canvas, String letter, int tileColor, int left, int top, int right, int bottom) { - letter = letter.toUpperCase(Locale.getDefault()); - Paint tilePaint = new Paint(), textPaint = new Paint(); - tilePaint.setColor(tileColor); - textPaint.setFlags(Paint.ANTI_ALIAS_FLAG); - textPaint.setColor(FG_COLOR); - textPaint.setTypeface(Typeface.create("sans-serif-light", - Typeface.NORMAL)); - textPaint.setTextSize((float) ((right - left) * 0.8)); - Rect rect = new Rect(); - - canvas.drawRect(new Rect(left, top, right, bottom), tilePaint); - textPaint.getTextBounds(letter, 0, 1, rect); - float width = textPaint.measureText(letter); - canvas.drawText(letter, (right + left) / 2 - width / 2, (top + bottom) - / 2 + rect.height() / 2, textPaint); - return true; - } - - private boolean drawTile(Canvas canvas, MucOptions.User user, int left, int top, int right, int bottom) { - Contact contact = user.getContact(); - if (contact != null) { - Uri uri = null; - if (contact.getAvatarFilename() != null && QuickConversationsService.isQuicksy()) { - uri = mXmppConnectionService.getFileBackend().getAvatarUri(contact.getAvatarFilename()); - } else if (contact.getProfilePhoto() != null) { - uri = Uri.parse(contact.getProfilePhoto()); - } else if (contact.getAvatarFilename() != null) { - uri = mXmppConnectionService.getFileBackend().getAvatarUri(contact.getAvatarFilename()); - } - if (drawTile(canvas, uri, left, top, right, bottom)) { - return true; - } - } else if (user.getAvatar() != null) { - Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar()); - if (drawTile(canvas, uri, left, top, right, bottom)) { - return true; - } - } - if (contact != null) { - String seed = contact.getJid().asBareJid().toString(); - drawTile(canvas, contact.getDisplayName(), seed, left, top, right, bottom); - } else { - String seed = user.getRealJid() == null ? null : user.getRealJid().asBareJid().toString(); - drawTile(canvas, user.getName(), seed, left, top, right, bottom); - } - return true; - } - - private boolean drawTile(Canvas canvas, Account account, int left, int top, int right, int bottom) { - String avatar = account.getAvatar(); - if (avatar != null) { - Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(avatar); - if (uri != null) { - if (drawTile(canvas, uri, left, top, right, bottom)) { - return true; - } - } - } - String name = account.getJid().asBareJid().toString(); - return drawTile(canvas, name, name, left, top, right, bottom); - } - - private static boolean drawTile(Canvas canvas, String name, String seed, int left, int top, int right, int bottom) { - if (name != null) { - final String letter = name.equals(CHANNEL_SYMBOL) ? name : getFirstLetter(name); - final int color = UIHelper.getColorForName(seed == null ? name : seed); - drawTile(canvas, letter, color, left, top, right, bottom); - return true; - } - return false; - } - - private static String getFirstLetter(String name) { - for (Character c : name.toCharArray()) { - if (Character.isLetterOrDigit(c)) { - return c.toString(); - } - } - return "X"; - } - - private boolean drawTile(Canvas canvas, Uri uri, int left, int top, int right, int bottom) { - if (uri != null) { - Bitmap bitmap = mXmppConnectionService.getFileBackend() - .cropCenter(uri, bottom - top, right - left); - if (bitmap != null) { - drawTile(canvas, bitmap, left, top, right, bottom); - return true; - } - } - return false; - } - - private boolean drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop, int dstright, int dstbottom) { - Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom); - canvas.drawBitmap(bm, null, dst, null); - return true; - } - - @Override - public void onAdvancedStreamFeaturesAvailable(Account account) { - XmppConnection.Features features = account.getXmppConnection().getFeatures(); - if (features.pep() && !features.pepPersistent()) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has pep but is not persistent"); - if (account.getAvatar() != null) { - mXmppConnectionService.republishAvatarIfNeeded(account); - } - } - } - - private static String emptyOnNull(@Nullable Jid value) { - return value == null ? "" : value.toString(); - } - - public interface Avatarable { - @ColorInt int getAvatarBackgroundColor(); - String getAvatarName(); - } + private static final int FG_COLOR = 0xFFFAFAFA; + private static final int TRANSPARENT = 0x00000000; + private static final int PLACEHOLDER_COLOR = 0xFF202020; + + public static final int SYSTEM_UI_AVATAR_SIZE = 48; + + private static final String PREFIX_CONTACT = "contact"; + private static final String PREFIX_CONVERSATION = "conversation"; + private static final String PREFIX_ACCOUNT = "account"; + private static final String PREFIX_GENERIC = "generic"; + + private static final String CHANNEL_SYMBOL = "#"; + + private final Set sizes = new HashSet<>(); + private final HashMap> conversationDependentKeys = new HashMap<>(); + + protected XmppConnectionService mXmppConnectionService = null; + + AvatarService(XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + public static int getSystemUiAvatarSize(final Context context) { + return (int) (SYSTEM_UI_AVATAR_SIZE * context.getResources().getDisplayMetrics().density); + } + + public Bitmap get(final Avatarable avatarable, final int size, final boolean cachedOnly) { + if (avatarable instanceof Account) { + return get((Account) avatarable, size, cachedOnly); + } else if (avatarable instanceof Conversation) { + return get((Conversation) avatarable, size, cachedOnly); + } else if (avatarable instanceof Message) { + return get((Message) avatarable, size, cachedOnly); + } else if (avatarable instanceof ListItem) { + return get((ListItem) avatarable, size, cachedOnly); + } else if (avatarable instanceof MucOptions.User) { + return get((MucOptions.User) avatarable, size, cachedOnly); + } else if (avatarable instanceof Room) { + return get((Room) avatarable, size, cachedOnly); + } + throw new AssertionError( + "AvatarService does not know how to generate avatar from " + + avatarable.getClass().getName()); + } + + private Bitmap get(final Room result, final int size, boolean cacheOnly) { + final Jid room = result.getRoom(); + Conversation conversation = room != null ? mXmppConnectionService.findFirstMuc(room) : null; + if (conversation != null) { + return get(conversation, size, cacheOnly); + } + return get( + CHANNEL_SYMBOL, + room != null ? room.asBareJid().toString() : result.getName(), + size, + cacheOnly); + } + + private Bitmap get(final Contact contact, final int size, boolean cachedOnly) { + if (contact.isSelf()) { + return get(contact.getAccount(), size, cachedOnly); + } + final String KEY = key(contact, size); + Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null || cachedOnly) { + return avatar; + } + if (contact.getAvatarFilename() != null && QuickConversationsService.isQuicksy()) { + avatar = + mXmppConnectionService + .getFileBackend() + .getAvatar(contact.getAvatarFilename(), size); + } + if (avatar == null && contact.getProfilePhoto() != null) { + avatar = + mXmppConnectionService + .getFileBackend() + .cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size); + } + if (avatar == null && contact.getAvatarFilename() != null) { + avatar = + mXmppConnectionService + .getFileBackend() + .getAvatar(contact.getAvatarFilename(), size); + } + if (avatar == null) { + avatar = + get( + contact.getDisplayName(), + contact.getJid().asBareJid().toString(), + size, + false); + } + this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + + public Bitmap getRoundedShortcut(final MucOptions mucOptions) { + final DisplayMetrics metrics = mXmppConnectionService.getResources().getDisplayMetrics(); + final int size = Math.round(metrics.density * 48); + final Bitmap bitmap = get(mucOptions, size, false); + final Bitmap output = + Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(output); + final Paint paint = new Paint(); + drawAvatar(bitmap, canvas, paint); + return output; + } + + public Bitmap getRoundedShortcut(final Contact contact) { + return getRoundedShortcut(contact, false); + } + + public Bitmap getRoundedShortcutWithIcon(final Contact contact) { + return getRoundedShortcut(contact, true); + } + + private Bitmap getRoundedShortcut(final Contact contact, boolean withIcon) { + DisplayMetrics metrics = mXmppConnectionService.getResources().getDisplayMetrics(); + int size = Math.round(metrics.density * 48); + Bitmap bitmap = get(contact, size); + Bitmap output = + Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(output); + final Paint paint = new Paint(); + + drawAvatar(bitmap, canvas, paint); + if (withIcon) { + drawIcon(canvas, paint); + } + return output; + } + + private static void drawAvatar(Bitmap bitmap, Canvas canvas, Paint paint) { + final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + canvas.drawCircle( + bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(bitmap, rect, rect, paint); + } + + private void drawIcon(Canvas canvas, Paint paint) { + final Resources resources = mXmppConnectionService.getResources(); + final Bitmap icon = getRoundLauncherIcon(resources); + if (icon == null) { + return; + } + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); + + int iconSize = Math.round(canvas.getHeight() / 2.6f); + + int left = canvas.getWidth() - iconSize; + int top = canvas.getHeight() - iconSize; + final Rect rect = new Rect(left, top, left + iconSize, top + iconSize); + canvas.drawBitmap(icon, null, rect, paint); + } + + private static Bitmap getRoundLauncherIcon(Resources resources) { + + final Drawable drawable = + ResourcesCompat.getDrawable(resources, R.mipmap.new_launcher_round, null); + if (drawable == null) { + return null; + } + + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + } + + Bitmap bitmap = + Bitmap.createBitmap( + drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + + return bitmap; + } + + public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) { + Contact c = user.getContact(); + if (c != null + && (c.getProfilePhoto() != null + || c.getAvatarFilename() != null + || user.getAvatar() == null)) { + return get(c, size, cachedOnly); + } else { + return getImpl(user, size, cachedOnly); + } + } + + private Bitmap getImpl(final MucOptions.User user, final int size, boolean cachedOnly) { + final String KEY = key(user, size); + Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null || cachedOnly) { + return avatar; + } + if (user.getAvatar() != null) { + avatar = mXmppConnectionService.getFileBackend().getAvatar(user.getAvatar(), size); + } + if (avatar == null) { + Contact contact = user.getContact(); + if (contact != null) { + avatar = get(contact, size, false); + } else { + String seed = + user.getRealJid() != null ? user.getRealJid().asBareJid().toString() : null; + avatar = get(user.getName(), seed, size, false); + } + } + this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + + public void clear(Contact contact) { + synchronized (this.sizes) { + for (final Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove(key(contact, size)); + } + } + for (Conversation conversation : mXmppConnectionService.findAllConferencesWith(contact)) { + MucOptions.User user = + conversation.getMucOptions().findUserByRealJid(contact.getJid().asBareJid()); + if (user != null) { + clear(user); + } + clear(conversation); + } + } + + private String key(Contact contact, int size) { + synchronized (this.sizes) { + this.sizes.add(size); + } + return PREFIX_CONTACT + + '\0' + + contact.getAccount().getJid().asBareJid() + + '\0' + + emptyOnNull(contact.getJid()) + + '\0' + + size; + } + + private String key(MucOptions.User user, int size) { + synchronized (this.sizes) { + this.sizes.add(size); + } + return PREFIX_CONTACT + + '\0' + + user.getAccount().getJid().asBareJid() + + '\0' + + emptyOnNull(user.getFullJid()) + + '\0' + + emptyOnNull(user.getRealJid()) + + '\0' + + size; + } + + public Bitmap get(ListItem item, int size) { + return get(item, size, false); + } + + public Bitmap get(ListItem item, int size, boolean cachedOnly) { + if (item instanceof RawBlockable) { + return get(item.getDisplayName(), item.getJid().toString(), size, cachedOnly); + } else if (item instanceof Contact) { + return get((Contact) item, size, cachedOnly); + } else if (item instanceof Bookmark bookmark) { + if (bookmark.getConversation() != null) { + return get(bookmark.getConversation(), size, cachedOnly); + } else { + Jid jid = bookmark.getJid(); + Account account = bookmark.getAccount(); + Contact contact = jid == null ? null : account.getRoster().getContact(jid); + if (contact != null && contact.getAvatarFilename() != null) { + return get(contact, size, cachedOnly); + } + String seed = jid != null ? jid.asBareJid().toString() : null; + return get(bookmark.getDisplayName(), seed, size, cachedOnly); + } + } else { + String seed = item.getJid() != null ? item.getJid().asBareJid().toString() : null; + return get(item.getDisplayName(), seed, size, cachedOnly); + } + } + + public Bitmap get(Conversation conversation, int size) { + return get(conversation, size, false); + } + + public Bitmap get(Conversation conversation, int size, boolean cachedOnly) { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + return get(conversation.getContact(), size, cachedOnly); + } else { + return get(conversation.getMucOptions(), size, cachedOnly); + } + } + + public void clear(Conversation conversation) { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + clear(conversation.getContact()); + } else { + clear(conversation.getMucOptions()); + synchronized (this.conversationDependentKeys) { + Set keys = this.conversationDependentKeys.get(conversation.getUuid()); + if (keys == null) { + return; + } + LruCache cache = this.mXmppConnectionService.getBitmapCache(); + for (String key : keys) { + cache.remove(key); + } + keys.clear(); + } + } + } + + private Bitmap get(MucOptions mucOptions, int size, boolean cachedOnly) { + final String KEY = key(mucOptions, size); + Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (bitmap != null || cachedOnly) { + return bitmap; + } + + bitmap = mXmppConnectionService.getFileBackend().getAvatar(mucOptions.getAvatar(), size); + + if (bitmap == null) { + Conversation c = mucOptions.getConversation(); + if (mucOptions.isPrivateAndNonAnonymous()) { + final List users = mucOptions.getUsersRelevantForNameAndAvatar(); + if (users.size() == 0) { + bitmap = + getImpl( + c.getName().toString(), + c.getJid().asBareJid().toString(), + size); + } else { + bitmap = getImpl(users, size); + } + } else { + bitmap = getImpl(CHANNEL_SYMBOL, c.getJid().asBareJid().toString(), size); + } + } + + this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap); + + return bitmap; + } + + private Bitmap get(List users, int size, boolean cachedOnly) { + final String KEY = key(users, size); + Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (bitmap != null || cachedOnly) { + return bitmap; + } + bitmap = getImpl(users, size); + this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap); + return bitmap; + } + + private Bitmap getImpl(List users, int size) { + int count = users.size(); + Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + bitmap.eraseColor(TRANSPARENT); + if (count == 0) { + throw new AssertionError("Unable to draw tiles for 0 users"); + } else if (count == 1) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, users.get(0).getAccount(), size / 2 + 1, 0, size, size); + } else if (count == 2) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size); + } else if (count == 3) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, users.get(2), size / 2 + 1, size / 2 + 1, size, size); + } else if (count == 4) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); + drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); + drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, users.get(3), size / 2 + 1, size / 2 + 1, size, size); + } else { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); + drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); + drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, "\u2026", PLACEHOLDER_COLOR, size / 2 + 1, size / 2 + 1, size, size); + } + return bitmap; + } + + public void clear(final MucOptions options) { + if (options == null) { + return; + } + synchronized (this.sizes) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove(key(options, size)); + } + } + } + + private String key(final MucOptions options, int size) { + synchronized (this.sizes) { + this.sizes.add(size); + } + return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid() + "_" + size; + } + + private String key(List users, int size) { + final Conversation conversation = users.get(0).getConversation(); + StringBuilder builder = new StringBuilder("TILE_"); + builder.append(conversation.getUuid()); + + for (MucOptions.User user : users) { + builder.append("\0"); + builder.append(emptyOnNull(user.getRealJid())); + builder.append("\0"); + builder.append(emptyOnNull(user.getFullJid())); + } + builder.append('\0'); + builder.append(size); + final String key = builder.toString(); + synchronized (this.conversationDependentKeys) { + Set keys; + if (this.conversationDependentKeys.containsKey(conversation.getUuid())) { + keys = this.conversationDependentKeys.get(conversation.getUuid()); + } else { + keys = new HashSet<>(); + this.conversationDependentKeys.put(conversation.getUuid(), keys); + } + keys.add(key); + } + return key; + } + + public Bitmap get(Account account, int size) { + return get(account, size, false); + } + + public Bitmap get(Account account, int size, boolean cachedOnly) { + final String KEY = key(account, size); + Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null || cachedOnly) { + return avatar; + } + avatar = mXmppConnectionService.getFileBackend().getAvatar(account.getAvatar(), size); + if (avatar == null) { + final String displayName = account.getDisplayName(); + final String jid = account.getJid().asBareJid().toString(); + if (QuickConversationsService.isQuicksy() && !TextUtils.isEmpty(displayName)) { + avatar = get(displayName, jid, size, false); + } else { + avatar = get(jid, null, size, false); + } + } + mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + + public Bitmap get(Message message, int size, boolean cachedOnly) { + final Conversational conversation = message.getConversation(); + if (message.getType() == Message.TYPE_STATUS + && message.getCounterparts() != null + && message.getCounterparts().size() > 1) { + return get(message.getCounterparts(), size, cachedOnly); + } else if (message.getStatus() == Message.STATUS_RECEIVED) { + Contact c = message.getContact(); + if (c != null && (c.getProfilePhoto() != null || c.getAvatarFilename() != null)) { + return get(c, size, cachedOnly); + } else if (conversation instanceof Conversation + && message.getConversation().getMode() == Conversation.MODE_MULTI) { + final Jid trueCounterpart = message.getTrueCounterpart(); + final MucOptions mucOptions = ((Conversation) conversation).getMucOptions(); + MucOptions.User user; + if (trueCounterpart != null) { + user = + mucOptions.findOrCreateUserByRealJid( + trueCounterpart, message.getCounterpart()); + } else { + user = mucOptions.findUserByFullJid(message.getCounterpart()); + } + if (user != null) { + return getImpl(user, size, cachedOnly); + } + } else if (c != null) { + return get(c, size, cachedOnly); + } + Jid tcp = message.getTrueCounterpart(); + String seed = tcp != null ? tcp.asBareJid().toString() : null; + return get(UIHelper.getMessageDisplayName(message), seed, size, cachedOnly); + } else { + return get(conversation.getAccount(), size, cachedOnly); + } + } + + public void clear(Account account) { + synchronized (this.sizes) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove(key(account, size)); + } + } + } + + public void clear(MucOptions.User user) { + synchronized (this.sizes) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove(key(user, size)); + } + } + } + + private String key(Account account, int size) { + synchronized (this.sizes) { + this.sizes.add(size); + } + return PREFIX_ACCOUNT + "_" + account.getUuid() + "_" + size; + } + + /*public Bitmap get(String name, int size) { + return get(name,null, size,false); + }*/ + + public Bitmap get(final String name, String seed, final int size, boolean cachedOnly) { + final String KEY = key(seed == null ? name : name + "\0" + seed, size); + Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY); + if (bitmap != null || cachedOnly) { + return bitmap; + } + bitmap = getImpl(name, seed, size); + mXmppConnectionService.getBitmapCache().put(KEY, bitmap); + return bitmap; + } + + public static Bitmap get(final Jid jid, final int size) { + return getImpl(jid.asBareJid().toString(), null, size); + } + + private static Bitmap getImpl(final String name, final String seed, final int size) { + Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + final String trimmedName = name == null ? "" : name.trim(); + drawTile(canvas, trimmedName, seed, 0, 0, size, size); + return bitmap; + } + + private String key(String name, int size) { + synchronized (this.sizes) { + this.sizes.add(size); + } + return PREFIX_GENERIC + "_" + name + "_" + size; + } + + private static boolean drawTile( + Canvas canvas, String letter, int tileColor, int left, int top, int right, int bottom) { + letter = letter.toUpperCase(Locale.getDefault()); + Paint tilePaint = new Paint(), textPaint = new Paint(); + tilePaint.setColor(tileColor); + textPaint.setFlags(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(FG_COLOR); + textPaint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); + textPaint.setTextSize((float) ((right - left) * 0.8)); + Rect rect = new Rect(); + + canvas.drawRect(new Rect(left, top, right, bottom), tilePaint); + textPaint.getTextBounds(letter, 0, 1, rect); + float width = textPaint.measureText(letter); + canvas.drawText( + letter, + (right + left) / 2 - width / 2, + (top + bottom) / 2 + rect.height() / 2, + textPaint); + return true; + } + + private boolean drawTile( + Canvas canvas, MucOptions.User user, int left, int top, int right, int bottom) { + Contact contact = user.getContact(); + if (contact != null) { + Uri uri = null; + if (contact.getAvatarFilename() != null && QuickConversationsService.isQuicksy()) { + uri = + mXmppConnectionService + .getFileBackend() + .getAvatarUri(contact.getAvatarFilename()); + } else if (contact.getProfilePhoto() != null) { + uri = Uri.parse(contact.getProfilePhoto()); + } else if (contact.getAvatarFilename() != null) { + uri = + mXmppConnectionService + .getFileBackend() + .getAvatarUri(contact.getAvatarFilename()); + } + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } else if (user.getAvatar() != null) { + Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar()); + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } + if (contact != null) { + String seed = contact.getJid().asBareJid().toString(); + drawTile(canvas, contact.getDisplayName(), seed, left, top, right, bottom); + } else { + String seed = + user.getRealJid() == null ? null : user.getRealJid().asBareJid().toString(); + drawTile(canvas, user.getName(), seed, left, top, right, bottom); + } + return true; + } + + private boolean drawTile( + Canvas canvas, Account account, int left, int top, int right, int bottom) { + String avatar = account.getAvatar(); + if (avatar != null) { + Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(avatar); + if (uri != null) { + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } + } + String name = account.getJid().asBareJid().toString(); + return drawTile(canvas, name, name, left, top, right, bottom); + } + + private static boolean drawTile( + Canvas canvas, String name, String seed, int left, int top, int right, int bottom) { + if (name != null) { + final String letter = name.equals(CHANNEL_SYMBOL) ? name : getFirstLetter(name); + final int color = UIHelper.getColorForName(seed == null ? name : seed); + drawTile(canvas, letter, color, left, top, right, bottom); + return true; + } + return false; + } + + private static String getFirstLetter(String name) { + for (Character c : name.toCharArray()) { + if (Character.isLetterOrDigit(c)) { + return c.toString(); + } + } + return "X"; + } + + private boolean drawTile(Canvas canvas, Uri uri, int left, int top, int right, int bottom) { + if (uri != null) { + Bitmap bitmap = + mXmppConnectionService + .getFileBackend() + .cropCenter(uri, bottom - top, right - left); + if (bitmap != null) { + drawTile(canvas, bitmap, left, top, right, bottom); + return true; + } + } + return false; + } + + private boolean drawTile( + Canvas canvas, Bitmap bm, int dstleft, int dsttop, int dstright, int dstbottom) { + Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom); + canvas.drawBitmap(bm, null, dst, null); + return true; + } + + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + XmppConnection.Features features = account.getXmppConnection().getFeatures(); + if (features.pep() && !features.pepPersistent()) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has pep but is not persistent"); + if (account.getAvatar() != null) { + mXmppConnectionService.republishAvatarIfNeeded(account); + } + } + } + + private static String emptyOnNull(@Nullable Jid value) { + return value == null ? "" : value.toString(); + } + + public interface Avatarable { + @ColorInt + int getAvatarBackgroundColor(); + + String getAvatarName(); + } } diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index f4d4fae35c70d0e3d9dd861f2883c76df56fc315..747f79fb31f2c14167ee9f6b00f6f5c0b57fec1f 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegration.java @@ -455,7 +455,7 @@ public class CallIntegration extends Connection { } public static Uri address(final Jid contact) { - return Uri.parse(String.format("xmpp:%s", contact.toEscapedString())); + return Uri.parse(String.format("xmpp:%s", contact.toString())); } public void verifyDisconnected() { diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java b/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java index a225dcbee935a5e2782eb6a6873b4ee156939c2b..35950439600c7392505f29be81921d8e5d01fac4 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java @@ -20,14 +20,11 @@ import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.util.Log; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; - import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -40,7 +37,6 @@ import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; - import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -91,7 +87,8 @@ public class CallIntegrationConnectionService extends ConnectionService { if (service == null) { Log.d( Config.LOGTAG, - "CallIntegrationConnection service was unable to bind to XmppConnectionService"); + "CallIntegrationConnection service was unable to bind to" + + " XmppConnectionService"); return Connection.createFailedConnection( new DisconnectCause(DisconnectCause.ERROR, "service connection not found")); } @@ -107,8 +104,8 @@ public class CallIntegrationConnectionService extends ConnectionService { Log.d(Config.LOGTAG, "create outgoing rtp connection!"); final Intent intent = new Intent(service, RtpSessionActivity.class); intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, account.getJid().toEscapedString()); - intent.putExtra(RtpSessionActivity.EXTRA_WITH, with.toEscapedString()); + intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, account.getJid().toString()); + intent.putExtra(RtpSessionActivity.EXTRA_WITH, with.toString()); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); final Connection callIntegration; @@ -135,7 +132,8 @@ public class CallIntegrationConnectionService extends ConnectionService { return Connection.createFailedConnection( new DisconnectCause( DisconnectCause.ERROR, - "Phone is busy. Probably race condition. Try again in a moment")); + "Phone is busy. Probably race condition. Try again in a" + + " moment")); } if (proposal == null) { // TODO instead of just null checking try to get the sessionID @@ -187,9 +185,9 @@ public class CallIntegrationConnectionService extends ConnectionService { } final Jid jid; if ("tel".equals(uri.getScheme())) { - jid = Jid.ofEscaped(extras.getString(EXTRA_ADDRESS)); + jid = Jid.of(extras.getString(EXTRA_ADDRESS)); } else { - jid = Jid.ofEscaped(uri.getSchemeSpecificPart()); + jid = Jid.of(uri.getSchemeSpecificPart()); } final int videoState = extras.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE); final Set media = @@ -226,7 +224,7 @@ public class CallIntegrationConnectionService extends ConnectionService { return Connection.createFailedConnection( new DisconnectCause(DisconnectCause.ERROR, "service connection not found")); } - final var jid = Jid.ofEscaped(uri.getSchemeSpecificPart()); + final var jid = Jid.of(uri.getSchemeSpecificPart()); final Account account = service.findAccountByUuid(phoneAccountHandle.getId()); final var weakReference = service.getJingleConnectionManager().findJingleRtpConnection(account, jid, sid); @@ -365,7 +363,7 @@ public class CallIntegrationConnectionService extends ConnectionService { } else { // for Android 8 we need to put in a fake tel uri final var outgoingCallExtras = new Bundle(); - outgoingCallExtras.putString(EXTRA_ADDRESS, with.toEscapedString()); + outgoingCallExtras.putString(EXTRA_ADDRESS, with.toString()); extras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, outgoingCallExtras); address = Uri.parse("tel:0"); } diff --git a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java index 5a8c58ac6d1fa1acfa3282ed22458fef9a83bb18..9e09e56e962c40cf9c34f069f61bc6902ce16e1a 100644 --- a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java +++ b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java @@ -1,14 +1,10 @@ package eu.siacs.conversations.services; - import android.util.Log; - import androidx.annotation.NonNull; - import com.google.common.base.Strings; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; - import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Room; @@ -17,18 +13,7 @@ import eu.siacs.conversations.http.services.MuclumbusService; import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; - import im.conversations.android.xmpp.model.stanza.Iq; - -import okhttp3.OkHttpClient; -import okhttp3.ResponseBody; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -38,6 +23,13 @@ import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import okhttp3.OkHttpClient; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; public class ChannelDiscoveryService { @@ -57,7 +49,8 @@ public class ChannelDiscoveryService { this.muclumbusService = null; return; } - final OkHttpClient.Builder builder = HttpConnectionManager.okHttpClient(service).newBuilder(); + final OkHttpClient.Builder builder = + HttpConnectionManager.okHttpClient(service).newBuilder(); if (service.useTorToConnect()) { builder.proxy(HttpConnectionManager.getProxy()); } @@ -205,10 +198,8 @@ public class ChannelDiscoveryService { account, infoRequest, infoResponse -> { - if (infoResponse.getType() - == Iq.Type.RESULT) { - final Room room = - IqParser.parseRoom(infoResponse); + if (infoResponse.getType() == Iq.Type.RESULT) { + final Room room = IqParser.parseRoom(infoResponse); if (room != null) { rooms.add(room); } @@ -260,7 +251,7 @@ public class ChannelDiscoveryService { continue; } for (final String mucService : xmppConnection.getMucServers()) { - Jid jid = Jid.ofEscaped(mucService); + final Jid jid = Jid.of(mucService); if (!localMucServices.containsKey(jid)) { localMucServices.put(jid, account); } diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 83bef27e7bd094dc5368b2b0e7a34638d58e9da8..cd2045774e28a29685af493b956b3aed0211307e 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -573,9 +573,8 @@ public class NotificationService { final Intent fullScreenIntent = new Intent(mXmppConnectionService, RtpSessionActivity.class); fullScreenIntent.putExtra( - RtpSessionActivity.EXTRA_ACCOUNT, - id.account.getJid().asBareJid().toEscapedString()); - fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString()); + RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toString()); + fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toString()); fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId); fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -702,9 +701,8 @@ public class NotificationService { new Intent(mXmppConnectionService, RtpSessionActivity.class); fullScreenIntent.setAction(action); fullScreenIntent.putExtra( - RtpSessionActivity.EXTRA_ACCOUNT, - id.account.getJid().asBareJid().toEscapedString()); - fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString()); + RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toString()); + fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toString()); fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId); return PendingIntent.getActivity( mXmppConnectionService, @@ -1902,7 +1900,7 @@ public class NotificationService { } else if (errors.size() == 1) { mBuilder.setContentTitle( mXmppConnectionService.getString(R.string.problem_connecting_to_account)); - mBuilder.setContentText(errors.get(0).getJid().asBareJid().toEscapedString()); + mBuilder.setContentText(errors.get(0).getJid().asBareJid().toString()); } else { mBuilder.setContentTitle( mXmppConnectionService.getString(R.string.problem_connecting_to_accounts)); @@ -1961,7 +1959,7 @@ public class NotificationService { intent = new Intent(mXmppConnectionService, AccountUtils.MANAGE_ACCOUNT_ACTIVITY); } else { intent = new Intent(mXmppConnectionService, EditAccountActivity.class); - intent.putExtra("jid", errors.get(0).getJid().asBareJid().toEscapedString()); + intent.putExtra("jid", errors.get(0).getJid().asBareJid().toString()); intent.putExtra(EditAccountActivity.EXTRA_OPENED_FROM_NOTIFICATION, true); } mBuilder.setContentIntent( diff --git a/src/main/java/eu/siacs/conversations/services/ShortcutService.java b/src/main/java/eu/siacs/conversations/services/ShortcutService.java index b2a3cce750b9d19df4b78fd204497917ea809996..132f7091200d3df424b50086f1bf8c2f6e84a008 100644 --- a/src/main/java/eu/siacs/conversations/services/ShortcutService.java +++ b/src/main/java/eu/siacs/conversations/services/ShortcutService.java @@ -159,17 +159,15 @@ public class ShortcutService { } private static String getShortcutId(final Contact contact) { - return contact.getAccount().getJid().asBareJid().toEscapedString() + return contact.getAccount().getJid().asBareJid().toString() + "#" - + contact.getJid().asBareJid().toEscapedString(); + + contact.getJid().asBareJid().toString(); } private static String getShortcutId(final MucOptions mucOptions) { final Account account = mucOptions.getAccount(); final Jid jid = mucOptions.getConversation().getJid(); - return account.getJid().asBareJid().toEscapedString() - + "#" - + jid.asBareJid().toEscapedString(); + return account.getJid().asBareJid().toString() + "#" + jid.asBareJid().toString(); } private Intent getShortcutIntent(final MucOptions mucOptions) { @@ -179,17 +177,12 @@ public class ShortcutService { Uri.parse( String.format( "xmpp:%s?join", - mucOptions - .getConversation() - .getJid() - .asBareJid() - .toEscapedString()))); + mucOptions.getConversation().getJid().asBareJid().toString()))); } private Intent getShortcutIntent(final Contact contact) { return getShortcutIntent( - contact.getAccount(), - Uri.parse("xmpp:" + contact.getJid().asBareJid().toEscapedString())); + contact.getAccount(), Uri.parse("xmpp:" + contact.getJid().asBareJid().toString())); } private Intent getShortcutIntent(final Account account, final Uri uri) { diff --git a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java index 92ae9d9ec668533d097680e333ac58bb4bc33248..24aaf1f4279b04abd32fbf3da615283c0dda3a32 100644 --- a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java +++ b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java @@ -10,10 +10,8 @@ import android.os.Messenger; import android.os.RemoteException; import android.preference.PreferenceManager; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.Iterables; @@ -22,7 +20,6 @@ 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.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -34,7 +31,6 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import im.conversations.android.xmpp.model.stanza.Iq; import im.conversations.android.xmpp.model.stanza.Presence; - import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.List; @@ -92,7 +88,8 @@ public class UnifiedPushBroker { renewUnifiedPushEndpoints(null); } - public Optional renewUnifiedPushEndpoints(@Nullable final PushTargetMessenger pushTargetMessenger) { + public Optional renewUnifiedPushEndpoints( + @Nullable final PushTargetMessenger pushTargetMessenger) { final Optional transportOptional = getTransport(); if (transportOptional.isPresent()) { final Transport transport = transportOptional.get(); @@ -100,13 +97,13 @@ public class UnifiedPushBroker { renewUnifiedEndpoint(transportOptional.get(), pushTargetMessenger); } else { if (pushTargetMessenger != null && pushTargetMessenger.messenger != null) { - sendRegistrationDelayed(pushTargetMessenger.messenger,"account is disabled"); + sendRegistrationDelayed(pushTargetMessenger.messenger, "account is disabled"); } Log.d(Config.LOGTAG, "skipping UnifiedPush endpoint renewal. Account is disabled"); } } else { if (pushTargetMessenger != null && pushTargetMessenger.messenger != null) { - sendRegistrationDelayed(pushTargetMessenger.messenger,"no transport selected"); + sendRegistrationDelayed(pushTargetMessenger.messenger, "no transport selected"); } Log.d(Config.LOGTAG, "skipping UnifiedPush endpoint renewal. No transport selected"); } @@ -121,16 +118,16 @@ public class UnifiedPushBroker { try { messenger.send(message); } catch (final RemoteException e) { - Log.d(Config.LOGTAG,"unable to tell messenger of delayed registration",e); + Log.d(Config.LOGTAG, "unable to tell messenger of delayed registration", e); } } - private void renewUnifiedEndpoint(final Transport transport, final PushTargetMessenger pushTargetMessenger) { + private void renewUnifiedEndpoint( + final Transport transport, final PushTargetMessenger pushTargetMessenger) { final Account account = transport.account; final UnifiedPushDatabase unifiedPushDatabase = UnifiedPushDatabase.getInstance(service); final List renewals = - unifiedPushDatabase.getRenewals( - account.getUuid(), transport.transport.toEscapedString()); + unifiedPushDatabase.getRenewals(account.getUuid(), transport.transport.toString()); Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -142,7 +139,11 @@ public class UnifiedPushBroker { Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": try to renew UnifiedPush " + renewal); - UnifiedPushDistributor.quickLog(service,String.format("%s: try to renew UnifiedPush %s", account.getJid(), renewal.toString())); + UnifiedPushDistributor.quickLog( + service, + String.format( + "%s: try to renew UnifiedPush %s", + account.getJid(), renewal.toString())); final String hashedApplication = UnifiedPushDistributor.hash(account.getUuid(), renewal.application); final String hashedInstance = @@ -205,7 +206,7 @@ public class UnifiedPushBroker { unifiedPushDatabase.updateEndpoint( renewal.instance, transport.account.getUuid(), - transport.transport.toEscapedString(), + transport.transport.toString(), endpoint, expiration); if (modified) { @@ -231,15 +232,21 @@ public class UnifiedPushBroker { } } - private void sendEndpoint(final Messenger messenger, String instance, final UnifiedPushDatabase.ApplicationEndpoint applicationEndpoint) { + private void sendEndpoint( + final Messenger messenger, + String instance, + final UnifiedPushDatabase.ApplicationEndpoint applicationEndpoint) { if (messenger != null) { - Log.d(Config.LOGTAG,"using messenger instead of broadcast to communicate endpoint to "+applicationEndpoint.application); + Log.d( + Config.LOGTAG, + "using messenger instead of broadcast to communicate endpoint to " + + applicationEndpoint.application); final Message message = new Message(); message.obj = endpointIntent(instance, applicationEndpoint); try { messenger.send(message); } catch (final RemoteException e) { - Log.d(Config.LOGTAG,"messenger failed. falling back to broadcast"); + Log.d(Config.LOGTAG, "messenger failed. falling back to broadcast"); broadcastEndpoint(instance, applicationEndpoint); } } else { @@ -281,8 +288,7 @@ public class UnifiedPushBroker { future, new FutureCallback<>() { @Override - public void onSuccess( - final List pushTargets) { + public void onSuccess(final List pushTargets) { broadcastUnregistered(pushTargets); } @@ -290,19 +296,21 @@ public class UnifiedPushBroker { public void onFailure(@NonNull Throwable throwable) { Log.d( Config.LOGTAG, - "could not delete endpoints after UnifiedPushDistributor was disabled"); + "could not delete endpoints after UnifiedPushDistributor was" + + " disabled"); } }, MoreExecutors.directExecutor()); } private ListenableFuture> deletePushTargets() { - return Futures.submit(() -> UnifiedPushDatabase.getInstance(service).deletePushTargets(),SCHEDULER); + return Futures.submit( + () -> UnifiedPushDatabase.getInstance(service).deletePushTargets(), SCHEDULER); } private void broadcastUnregistered(final List pushTargets) { - for(final UnifiedPushDatabase.PushTarget pushTarget : pushTargets) { - Log.d(Config.LOGTAG,"sending unregistered to "+pushTarget); + for (final UnifiedPushDatabase.PushTarget pushTarget : pushTargets) { + Log.d(Config.LOGTAG, "sending unregistered to " + pushTarget); broadcastUnregistered(pushTarget); } } @@ -368,8 +376,8 @@ public class UnifiedPushBroker { final Jid transport; final Jid jid; try { - transport = Jid.ofEscaped(Strings.nullToEmpty(pushServerPreference).trim()); - jid = Jid.ofEscaped(Strings.nullToEmpty(accountPreference).trim()); + transport = Jid.of(Strings.nullToEmpty(pushServerPreference).trim()); + jid = Jid.of(Strings.nullToEmpty(accountPreference).trim()); } catch (final IllegalArgumentException e) { return Optional.absent(); } @@ -390,8 +398,7 @@ public class UnifiedPushBroker { } final String uuid = account.getUuid(); final List pushTargets = - UnifiedPushDatabase.getInstance(service) - .getPushTargets(uuid, transport.toEscapedString()); + UnifiedPushDatabase.getInstance(service).getPushTargets(uuid, transport.toString()); return Iterables.tryFind( pushTargets, pt -> @@ -422,7 +429,8 @@ public class UnifiedPushBroker { service.sendBroadcast(updateIntent); } - private Intent endpointIntent(final String instance, final UnifiedPushDatabase.ApplicationEndpoint endpoint) { + private Intent endpointIntent( + final String instance, final UnifiedPushDatabase.ApplicationEndpoint endpoint) { final Intent intent = new Intent(UnifiedPushDistributor.ACTION_NEW_ENDPOINT); intent.setPackage(endpoint.application); intent.putExtra("token", instance); @@ -449,13 +457,12 @@ public class UnifiedPushBroker { return intent; } - public void rebroadcastEndpoint(final Messenger messenger, final String instance, final Transport transport) { + public void rebroadcastEndpoint( + final Messenger messenger, final String instance, final Transport transport) { final UnifiedPushDatabase unifiedPushDatabase = UnifiedPushDatabase.getInstance(service); final UnifiedPushDatabase.ApplicationEndpoint endpoint = unifiedPushDatabase.getEndpoint( - transport.account.getUuid(), - transport.transport.toEscapedString(), - instance); + transport.account.getUuid(), transport.transport.toString(), instance); if (endpoint != null) { sendEndpoint(messenger, instance, endpoint); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index c9c116c3aaa08c492212d2f0237fd3cb096f2bee..9a5ea426f7bd80f1126ff2f204878eb5ceff15b5 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -120,7 +120,6 @@ import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.LocalizedContent; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnContactStatusChanged; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; @@ -2069,7 +2068,7 @@ public class XmppConnectionService extends Service { if (uri != null) { final EasyOnboardingInvite invite = new EasyOnboardingInvite( - jid.getDomain().toEscapedString(), uri, landingUrl); + jid.getDomain().toString(), uri, landingUrl); callback.inviteRequested(invite); return; } @@ -2145,7 +2144,7 @@ public class XmppConnectionService extends Service { public void processMdsItem(final Account account, final Element item) { final Jid jid = - item == null ? null : InvalidJid.getNullForInvalid(item.getAttributeAsJid("id")); + item == null ? null : Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("id")); if (jid == null) { return; } @@ -2306,7 +2305,7 @@ public class XmppConnectionService extends Service { account, Namespace.BOOKMARKS2, item, - bookmark.getJid().asBareJid().toEscapedString(), + bookmark.getJid().asBareJid().toString(), PublishOptions.persistentWhitelistAccessMaxItems()); } else if (connection.getFeatures().bookmarksConversion()) { pushBookmarksPep(account); @@ -2321,7 +2320,7 @@ public class XmppConnectionService extends Service { if (connection.getFeatures().bookmarks2()) { final Iq request = mIqGenerator.deleteItem( - Namespace.BOOKMARKS2, bookmark.getJid().asBareJid().toEscapedString()); + Namespace.BOOKMARKS2, bookmark.getJid().asBareJid().toString()); Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": removing bookmark via Bookmarks 2"); @@ -3027,10 +3026,10 @@ public class XmppConnectionService extends Service { } private void provisionAccount(final String address, final String password) { - final Jid jid = Jid.ofEscaped(address); + final Jid jid = Jid.of(address); final Account account = new Account(jid, password); account.setOption(Account.OPTION_DISABLED, true); - Log.d(Config.LOGTAG, jid.asBareJid().toEscapedString() + ": provisioning account"); + Log.d(Config.LOGTAG, jid.asBareJid().toString() + ": provisioning account"); createAccount(account); } @@ -5704,7 +5703,7 @@ public class XmppConnectionService extends Service { account, Namespace.MDS_DISPLAYED, item, - itemId.toEscapedString(), + itemId.toString(), PublishOptions.persistentWhitelistAccessMaxItems()); } @@ -5819,7 +5818,7 @@ public class XmppConnectionService extends Service { if (Config.QUICKSY_DOMAIN != null) { hosts.remove( Config.QUICKSY_DOMAIN - .toEscapedString()); // we only want to show this when we type a e164 + .toString()); // we only want to show this when we type a e164 // number } if (Config.MAGIC_CREATE_DOMAIN != null) { @@ -5835,7 +5834,7 @@ public class XmppConnectionService extends Service { mucServers.addAll(account.getXmppConnection().getMucServers()); for (final Bookmark bookmark : account.getBookmarks()) { final Jid jid = bookmark.getJid(); - final String s = jid == null ? null : jid.getDomain().toEscapedString(); + final String s = jid == null ? null : jid.getDomain().toString(); if (s != null) { mucServers.add(s); } diff --git a/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java index 04678c3c7260a448f93ec435277580fb3bfedc2f..195ca74f24ce8a6f724186cf16e0030b09bdd746 100644 --- a/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java @@ -2,13 +2,9 @@ package eu.siacs.conversations.ui; import android.view.View; import android.widget.Toast; - import androidx.annotation.StringRes; -import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.DialogBlockContactBinding; import eu.siacs.conversations.entities.Blockable; @@ -17,43 +13,56 @@ import eu.siacs.conversations.ui.util.JidDialog; public final class BlockContactDialog { - public static void show(final XmppActivity xmppActivity, final Blockable blockable) { - show(xmppActivity, blockable, null); - } - public static void show(final XmppActivity xmppActivity, final Blockable blockable, final String serverMsgId) { - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(xmppActivity); - final boolean isBlocked = blockable.isBlocked(); - builder.setNegativeButton(R.string.cancel, null); - DialogBlockContactBinding binding = DataBindingUtil.inflate(xmppActivity.getLayoutInflater(), R.layout.dialog_block_contact, null, false); - final boolean reporting = blockable.getAccount().getXmppConnection().getFeatures().spamReporting(); - if (reporting && !isBlocked) { - binding.reportSpam.setVisibility(View.VISIBLE); - if (serverMsgId != null) { - binding.reportSpam.setChecked(true); - binding.reportSpam.setEnabled(false); - } else { - binding.reportSpam.setEnabled(true); - } - } else { - binding.reportSpam.setVisibility(View.GONE); - } - builder.setView(binding.getRoot()); + public static void show(final XmppActivity xmppActivity, final Blockable blockable) { + show(xmppActivity, blockable, null); + } + + public static void show( + final XmppActivity xmppActivity, final Blockable blockable, final String serverMsgId) { + final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(xmppActivity); + final boolean isBlocked = blockable.isBlocked(); + builder.setNegativeButton(R.string.cancel, null); + DialogBlockContactBinding binding = + DataBindingUtil.inflate( + xmppActivity.getLayoutInflater(), + R.layout.dialog_block_contact, + null, + false); + final boolean reporting = + blockable.getAccount().getXmppConnection().getFeatures().spamReporting(); + if (reporting && !isBlocked) { + binding.reportSpam.setVisibility(View.VISIBLE); + if (serverMsgId != null) { + binding.reportSpam.setChecked(true); + binding.reportSpam.setEnabled(false); + } else { + binding.reportSpam.setEnabled(true); + } + } else { + binding.reportSpam.setVisibility(View.GONE); + } + builder.setView(binding.getRoot()); - final String value; - @StringRes int res; - if (blockable.getJid().isFullJid()) { - builder.setTitle(isBlocked ? R.string.action_unblock_participant : R.string.action_block_participant); - value = blockable.getJid().toEscapedString(); - res = isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text; - } else if (blockable.getJid().getLocal() == null || blockable.getAccount().isBlocked(blockable.getJid().getDomain())) { - builder.setTitle(isBlocked ? R.string.action_unblock_domain : R.string.action_block_domain); - value =blockable.getJid().getDomain().toEscapedString(); - res = isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text; - } else { - if (isBlocked) { - builder.setTitle(R.string.action_unblock_contact); - } else if (serverMsgId != null) { - builder.setTitle(R.string.report_spam_and_block); + final String value; + @StringRes int res; + if (blockable.getJid().isFullJid()) { + builder.setTitle( + isBlocked + ? R.string.action_unblock_participant + : R.string.action_block_participant); + value = blockable.getJid().toString(); + res = isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text; + } else if (blockable.getJid().getLocal() == null + || blockable.getAccount().isBlocked(blockable.getJid().getDomain())) { + builder.setTitle( + isBlocked ? R.string.action_unblock_domain : R.string.action_block_domain); + value = blockable.getJid().getDomain().toString(); + res = isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text; + } else { + if (isBlocked) { + builder.setTitle(R.string.action_unblock_contact); + } else if (serverMsgId != null) { + builder.setTitle(R.string.report_spam_and_block); } else { final int resBlockAction = blockable instanceof Conversation @@ -61,28 +70,39 @@ public final class BlockContactDialog { ? R.string.block_stranger : R.string.action_block_contact; builder.setTitle(resBlockAction); - } - value = blockable.getJid().asBareJid().toEscapedString(); - res = isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text; - } - binding.text.setText(JidDialog.style(xmppActivity, res, value)); - builder.setPositiveButton(isBlocked ? R.string.unblock : R.string.block, (dialog, which) -> { - if (isBlocked) { - xmppActivity.xmppConnectionService.sendUnblockRequest(blockable); - } else { - boolean toastShown = false; - if (xmppActivity.xmppConnectionService.sendBlockRequest(blockable, binding.reportSpam.isChecked(), serverMsgId)) { - Toast.makeText(xmppActivity, R.string.corresponding_chats_closed, Toast.LENGTH_SHORT).show(); - toastShown = true; - } - if (xmppActivity instanceof ContactDetailsActivity) { - if (!toastShown) { - Toast.makeText(xmppActivity, R.string.contact_blocked_past_tense, Toast.LENGTH_SHORT).show(); - } - xmppActivity.finish(); - } - } - }); - builder.create().show(); - } + } + value = blockable.getJid().asBareJid().toString(); + res = isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text; + } + binding.text.setText(JidDialog.style(xmppActivity, res, value)); + builder.setPositiveButton( + isBlocked ? R.string.unblock : R.string.block, + (dialog, which) -> { + if (isBlocked) { + xmppActivity.xmppConnectionService.sendUnblockRequest(blockable); + } else { + boolean toastShown = false; + if (xmppActivity.xmppConnectionService.sendBlockRequest( + blockable, binding.reportSpam.isChecked(), serverMsgId)) { + Toast.makeText( + xmppActivity, + R.string.corresponding_chats_closed, + Toast.LENGTH_SHORT) + .show(); + toastShown = true; + } + if (xmppActivity instanceof ContactDetailsActivity) { + if (!toastShown) { + Toast.makeText( + xmppActivity, + R.string.contact_blocked_past_tense, + Toast.LENGTH_SHORT) + .show(); + } + xmppActivity.finish(); + } + } + }); + builder.create().show(); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java index 0a59f53485b9c1b59b1f5b61dda6171b225c6c7a..71e1f25aa54d546c41ce3bab7a26bd1f454fda5e 100644 --- a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java @@ -3,12 +3,8 @@ package eu.siacs.conversations.ui; import android.os.Bundle; import android.text.Editable; import android.widget.Toast; - import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; - -import java.util.Collections; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; @@ -17,97 +13,107 @@ import eu.siacs.conversations.entities.RawBlockable; import eu.siacs.conversations.ui.interfaces.OnBackendConnected; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import java.util.Collections; -public class BlocklistActivity extends AbstractSearchableListItemActivity implements OnUpdateBlocklist { - - private Account account = null; +public class BlocklistActivity extends AbstractSearchableListItemActivity + implements OnUpdateBlocklist { - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getListView().setOnItemLongClickListener((parent, view, position, id) -> { - BlockContactDialog.show(BlocklistActivity.this, (Blockable) getListItems().get(position)); - return true; - }); - this.binding.fab.show(); - this.binding.fab.setOnClickListener((v)->showEnterJidDialog()); - } + private Account account = null; - @Override - public void onBackendConnected() { - for (final Account account : xmppConnectionService.getAccounts()) { - if (account.getJid().toEscapedString().equals(getIntent().getStringExtra(EXTRA_ACCOUNT))) { - this.account = account; - break; - } - } - filterContacts(); - Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG); - if (fragment instanceof OnBackendConnected) { - ((OnBackendConnected) fragment).onBackendConnected(); - } - } + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getListView() + .setOnItemLongClickListener( + (parent, view, position, id) -> { + BlockContactDialog.show( + BlocklistActivity.this, + (Blockable) getListItems().get(position)); + return true; + }); + this.binding.fab.show(); + this.binding.fab.setOnClickListener((v) -> showEnterJidDialog()); + } - @Override - protected void filterContacts(final String needle) { - getListItems().clear(); - if (account != null) { - for (final Jid jid : account.getBlocklist()) { - ListItem item; - if (jid.isFullJid()) { - item = new RawBlockable(account, jid); - } else { - item = account.getRoster().getContact(jid); - } - if (item.match(this, needle)) { - getListItems().add(item); - } - } - Collections.sort(getListItems()); - } - getListItemAdapter().notifyDataSetChanged(); - } + @Override + public void onBackendConnected() { + for (final Account account : xmppConnectionService.getAccounts()) { + if (account.getJid().toString().equals(getIntent().getStringExtra(EXTRA_ACCOUNT))) { + this.account = account; + break; + } + } + filterContacts(); + Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG); + if (fragment instanceof OnBackendConnected) { + ((OnBackendConnected) fragment).onBackendConnected(); + } + } - protected void showEnterJidDialog() { - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog"); - if (prev != null) { - ft.remove(prev); - } - ft.addToBackStack(null); - EnterJidDialog dialog = EnterJidDialog.newInstance( - null, - getString(R.string.block_jabber_id), - getString(R.string.block), - null, - account.getJid().asBareJid().toEscapedString(), - true, - false - ); + @Override + protected void filterContacts(final String needle) { + getListItems().clear(); + if (account != null) { + for (final Jid jid : account.getBlocklist()) { + ListItem item; + if (jid.isFullJid()) { + item = new RawBlockable(account, jid); + } else { + item = account.getRoster().getContact(jid); + } + if (item.match(this, needle)) { + getListItems().add(item); + } + } + Collections.sort(getListItems()); + } + getListItemAdapter().notifyDataSetChanged(); + } - dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> { - Blockable blockable = new RawBlockable(account, contactJid); - if (xmppConnectionService.sendBlockRequest(blockable, false, null)) { - Toast.makeText(BlocklistActivity.this, R.string.corresponding_chats_closed, Toast.LENGTH_SHORT).show(); - } - return true; - }); + protected void showEnterJidDialog() { + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog"); + if (prev != null) { + ft.remove(prev); + } + ft.addToBackStack(null); + EnterJidDialog dialog = + EnterJidDialog.newInstance( + null, + getString(R.string.block_jabber_id), + getString(R.string.block), + null, + account.getJid().asBareJid().toString(), + true, + false); - dialog.show(ft, "dialog"); - } + dialog.setOnEnterJidDialogPositiveListener( + (accountJid, contactJid) -> { + Blockable blockable = new RawBlockable(account, contactJid); + if (xmppConnectionService.sendBlockRequest(blockable, false, null)) { + Toast.makeText( + BlocklistActivity.this, + R.string.corresponding_chats_closed, + Toast.LENGTH_SHORT) + .show(); + } + return true; + }); - protected void refreshUiReal() { - final Editable editable = getSearchEditText().getText(); - if (editable != null) { - filterContacts(editable.toString()); - } else { - filterContacts(); - } - } + dialog.show(ft, "dialog"); + } - @Override - public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { - refreshUi(); - } + protected void refreshUiReal() { + final Editable editable = getSearchEditText().getText(); + if (editable != null) { + filterContacts(editable.toString()); + } else { + filterContacts(); + } + } + @Override + public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { + refreshUi(); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java index 420494d4d3c745047bc0569ca5b2440509083dc5..a133247306a046bf9a49ad4a75c89a4d564a2b76 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java @@ -294,7 +294,7 @@ public class ChannelDiscoveryActivity extends XmppActivity } public void joinChannelSearchResult(final String selectedAccount, final Room result) { - final Jid jid = Jid.ofEscaped(selectedAccount); + final Jid jid = Jid.of(selectedAccount); final Account account = xmppConnectionService.findAccountByJid(jid); final Conversation conversation = xmppConnectionService.findOrCreateConversation( diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java index 71662589ca3739f9a6a86ad83cefb828488fc6d5..ff60e6419b2abde89bbc53b6fce4417bb90bef7b 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java @@ -4,14 +4,11 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.widget.Toast; - import androidx.databinding.DataBindingUtil; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityManageAccountsBinding; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.ui.adapter.AccountAdapter; - import java.util.ArrayList; import java.util.List; @@ -29,16 +26,18 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final ActivityManageAccountsBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_manage_accounts); + final ActivityManageAccountsBinding binding = + DataBindingUtil.setContentView(this, R.layout.activity_manage_accounts); Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); setSupportActionBar(binding.toolbar); configureActionBar(getSupportActionBar(), false); this.mAccountAdapter = new AccountAdapter(this, accountList, false); binding.accountList.setAdapter(this.mAccountAdapter); - binding.accountList.setOnItemClickListener((arg0, view, position, arg3) -> { - final Account account = accountList.get(position); - goToProfilePictureActivity(account); - }); + binding.accountList.setOnItemClickListener( + (arg0, view, position, arg3) -> { + final Account account = accountList.get(position); + goToProfilePictureActivity(account); + }); } @Override @@ -58,7 +57,7 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity { private void loadEnabledAccounts() { accountList.clear(); - for(Account account : xmppConnectionService.getAccounts()) { + for (Account account : xmppConnectionService.getAccounts()) { if (account.isEnabled()) { accountList.add(account); } @@ -70,13 +69,17 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity { final Uri uri = startIntent == null ? null : startIntent.getData(); if (uri != null) { Intent intent = new Intent(this, PublishProfilePictureActivity.class); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); intent.setData(uri); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); try { startActivity(intent); } catch (SecurityException e) { - Toast.makeText(this, R.string.sharing_application_not_grant_permission, Toast.LENGTH_SHORT).show(); + Toast.makeText( + this, + R.string.sharing_application_not_grant_permission, + Toast.LENGTH_SHORT) + .show(); return; } } diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java index b9ebb413dd4f11344d2f04fc774861690e634fa3..b6bcafbbc33327a8b75d39347991cd3ef9356ef6 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java @@ -16,23 +16,12 @@ import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.ListView; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; - import com.google.common.base.Strings; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; @@ -44,8 +33,15 @@ import eu.siacs.conversations.ui.util.ActivityResult; import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; -public class ChooseContactActivity extends AbstractSearchableListItemActivity implements MultiChoiceModeListener, AdapterView.OnItemClickListener { +public class ChooseContactActivity extends AbstractSearchableListItemActivity + implements MultiChoiceModeListener, AdapterView.OnItemClickListener { public static final String EXTRA_TITLE_RES_ID = "extra_title_res_id"; public static final String EXTRA_GROUP_CHAT_NAME = "extra_group_chat_name"; public static final String EXTRA_SELECT_MULTIPLE = "extra_select_multiple"; @@ -75,11 +71,11 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im } else { contacts.add(conversation.getJid().asBareJid().toString()); } - intent.putExtra(EXTRA_FILTERED_CONTACTS, contacts.toArray(new String[contacts.size()])); + intent.putExtra(EXTRA_FILTERED_CONTACTS, contacts.toArray(new String[0])); intent.putExtra(EXTRA_CONVERSATION, conversation.getUuid()); intent.putExtra(EXTRA_SELECT_MULTIPLE, true); intent.putExtra(EXTRA_SHOW_ENTER_JID, true); - intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString()); return intent; } @@ -135,8 +131,11 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im } final SharedPreferences preferences = getPreferences(); - this.startSearching = intent.getBooleanExtra("direct_search", false) && preferences.getBoolean("start_searching", getResources().getBoolean(R.bool.start_searching)); - + this.startSearching = + intent.getBooleanExtra("direct_search", false) + && preferences.getBoolean( + "start_searching", + getResources().getBoolean(R.bool.start_searching)); } private void onFabClicked(View v) { @@ -159,9 +158,11 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im binding.fab.setImageResource(R.drawable.ic_navigate_next_24dp); binding.fab.show(); final View view = getSearchEditText(); - final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + final InputMethodManager imm = + (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (view != null && imm != null) { - imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); + imm.hideSoftInputFromWindow( + getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); } return true; } @@ -226,11 +227,14 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im } } - public @StringRes - int getTitleFromIntent() { + public @StringRes int getTitleFromIntent() { final Intent intent = getIntent(); boolean multiple = intent != null && intent.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false); - @StringRes int fallback = multiple ? R.string.title_activity_choose_contacts : R.string.title_activity_choose_contact; + @StringRes + int fallback = + multiple + ? R.string.title_activity_choose_contacts + : R.string.title_activity_choose_contact; return intent != null ? intent.getIntExtra(EXTRA_TITLE_RES_ID, fallback) : fallback; } @@ -239,7 +243,8 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im super.onCreateOptionsMenu(menu); final Intent i = getIntent(); boolean showEnterJid = i != null && i.getBooleanExtra(EXTRA_SHOW_ENTER_JID, false); - menu.findItem(R.id.action_scan_qr_code).setVisible(isCameraFeatureAvailable() && showEnterJid); + menu.findItem(R.id.action_scan_qr_code) + .setVisible(isCameraFeatureAvailable() && showEnterJid); MenuItem mMenuSearchView = menu.findItem(R.id.action_search); if (startSearching) { mMenuSearchView.expandActionView(); @@ -276,8 +281,8 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im for (final Account account : xmppConnectionService.getAccounts()) { if (account.isEnabled()) { for (final Contact contact : account.getRoster().getContacts()) { - if (contact.showInContactList() && - !filterContacts.contains(contact.getJid().asBareJid().toString()) + if (contact.showInContactList() + && !filterContacts.contains(contact.getJid().asBareJid().toString()) && contact.match(this, needle)) { getListItems().add(contact); } @@ -293,7 +298,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im } public void refreshUiReal() { - //nothing to do. This Activity doesn't implement any listeners + // nothing to do. This Activity doesn't implement any listeners } @Override @@ -314,28 +319,29 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im } ft.addToBackStack(null); Jid jid = uri == null ? null : uri.getJid(); - EnterJidDialog dialog = EnterJidDialog.newInstance( - mActivatedAccounts, - getString(R.string.enter_contact), - getString(R.string.select), - jid == null ? null : jid.asBareJid().toString(), - getIntent().getStringExtra(EXTRA_ACCOUNT), - true, - false - ); - - dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> { - final Intent request = getIntent(); - final Intent data = new Intent(); - data.putExtra("contact", contactJid.toString()); - data.putExtra(EXTRA_ACCOUNT, accountJid.toEscapedString()); - data.putExtra(EXTRA_SELECT_MULTIPLE, false); - copy(request, data); - setResult(RESULT_OK, data); - finish(); - - return true; - }); + EnterJidDialog dialog = + EnterJidDialog.newInstance( + mActivatedAccounts, + getString(R.string.enter_contact), + getString(R.string.select), + jid == null ? null : jid.asBareJid().toString(), + getIntent().getStringExtra(EXTRA_ACCOUNT), + true, + false); + + dialog.setOnEnterJidDialogPositiveListener( + (accountJid, contactJid) -> { + final Intent request = getIntent(); + final Intent data = new Intent(); + data.putExtra("contact", contactJid.toString()); + data.putExtra(EXTRA_ACCOUNT, accountJid.toString()); + data.putExtra(EXTRA_SELECT_MULTIPLE, false); + copy(request, data); + setResult(RESULT_OK, data); + finish(); + + return true; + }); dialog.show(ft, "dialog"); } @@ -352,7 +358,8 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im } private void handleActivityResult(ActivityResult activityResult) { - if (activityResult.resultCode == RESULT_OK && activityResult.requestCode == ScanActivity.REQUEST_SCAN_QR_CODE) { + if (activityResult.resultCode == RESULT_OK + && activityResult.requestCode == ScanActivity.REQUEST_SCAN_QR_CODE) { String result = activityResult.data.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT); XmppUri uri = new XmppUri(Strings.nullToEmpty(result)); if (uri.isValidJid()) { @@ -367,21 +374,23 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im this.mActivatedAccounts.clear(); for (final Account account : xmppConnectionService.getAccounts()) { if (account.isEnabled()) { - this.mActivatedAccounts.add(account.getJid().asBareJid().toEscapedString()); + this.mActivatedAccounts.add(account.getJid().asBareJid().toString()); } } ActivityResult activityResult = this.postponedActivityResult.pop(); if (activityResult != null) { handleActivityResult(activityResult); } - final Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG); + final Fragment fragment = + getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG); if (fragment instanceof OnBackendConnected) { ((OnBackendConnected) fragment).onBackendConnected(); } } @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + public void onRequestPermissionsResult( + int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); ScanActivity.onRequestPermissionResult(this, requestCode, grantResults); } @@ -393,8 +402,10 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im getListView().setItemChecked(position, true); return; } - 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); final ListItem mListItem = getListItems().get(position); onListItemClicked(mListItem); } @@ -405,7 +416,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im data.putExtra("contact", item.getJid().toString()); String account = request.getStringExtra(EXTRA_ACCOUNT); if (account == null && item instanceof Contact) { - account = ((Contact) item).getAccount().getJid().asBareJid().toEscapedString(); + account = ((Contact) item).getAccount().getJid().asBareJid().toString(); } data.putExtra(EXTRA_ACCOUNT, account); data.putExtra(EXTRA_SELECT_MULTIPLE, false); diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 8a1a0a1335c07c87541babff4d506ddeeb55b167..bc526d8c216137adfc423809539a077c686d6187 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -405,8 +405,7 @@ public class ConferenceDetailsActivity extends XmppActivity if (mConversation != null) { if (http) { return "https://conversations.im/j/" - + XmppUri.lameUrlEncode( - mConversation.getJid().asBareJid().toEscapedString()); + + XmppUri.lameUrlEncode(mConversation.getJid().asBareJid().toString()); } else { return "xmpp:" + mConversation.getJid().asBareJid() + "?join"; } @@ -522,7 +521,7 @@ public class ConferenceDetailsActivity extends XmppActivity } final MucOptions mucOptions = mConversation.getMucOptions(); final User self = mucOptions.getSelf(); - final String account = mConversation.getAccount().getJid().asBareJid().toEscapedString(); + final String account = mConversation.getAccount().getJid().asBareJid().toString(); setTitle( mucOptions.isPrivateAndNonAnonymous() ? R.string.action_muc_details @@ -537,7 +536,7 @@ public class ConferenceDetailsActivity extends XmppActivity this.binding.jid.setText( getString(R.string.hosted_on, mConversation.getJid().getDomain())); } else { - this.binding.jid.setText(mConversation.getJid().asBareJid().toEscapedString()); + this.binding.jid.setText(mConversation.getJid().asBareJid().toString()); } AvatarWorkerTask.loadAvatar( mConversation, binding.yourPhoto, R.dimen.avatar_on_details_screen_size); @@ -682,7 +681,7 @@ public class ConferenceDetailsActivity extends XmppActivity @Override public void onAffiliationChangeFailed(Jid jid, int resId) { - displayToast(getString(resId, jid.asBareJid().toEscapedString())); + displayToast(getString(resId, jid.asBareJid().toString())); } @Override diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 69e78124f9c881d9bb2725a0934c41bfcedcc4c2..3738f86415f9c6c2a9160a1efc4d575015f411b7 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -172,7 +172,7 @@ public class ContactDetailsActivity extends OmemoActivity if (quicksyContact) { value = PhoneNumberUtilWrapper.toFormattedPhoneNumber(this, jid); } else { - value = jid.toEscapedString(); + value = jid.toString(); } final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); builder.setTitle(getString(R.string.action_add_phone_book)); @@ -233,9 +233,9 @@ public class ContactDetailsActivity extends OmemoActivity protected String getShareableUri(boolean http) { if (http) { return "https://conversations.im/i/" - + XmppUri.lameUrlEncode(contact.getJid().asBareJid().toEscapedString()); + + XmppUri.lameUrlEncode(contact.getJid().asBareJid().toString()); } else { - return "xmpp:" + contact.getJid().asBareJid().toEscapedString(); + return "xmpp:" + contact.getJid().asBareJid().toString(); } } @@ -247,11 +247,11 @@ public class ContactDetailsActivity extends OmemoActivity && savedInstanceState.getBoolean("show_inactive_omemo", false); if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) { try { - this.accountJid = Jid.ofEscaped(getIntent().getExtras().getString(EXTRA_ACCOUNT)); + this.accountJid = Jid.of(getIntent().getExtras().getString(EXTRA_ACCOUNT)); } catch (final IllegalArgumentException ignored) { } try { - this.contactJid = Jid.ofEscaped(getIntent().getExtras().getString("contact")); + this.contactJid = Jid.of(getIntent().getExtras().getString("contact")); } catch (final IllegalArgumentException ignored) { } } @@ -328,7 +328,7 @@ public class ContactDetailsActivity extends OmemoActivity JidDialog.style( this, R.string.remove_contact_text, - contact.getJid().toEscapedString())) + contact.getJid().toString())) .setPositiveButton(getString(R.string.delete), removeFromRoster) .create() .show(); @@ -506,7 +506,7 @@ public class ContactDetailsActivity extends OmemoActivity } binding.detailsContactjid.setText(IrregularUnicodeDetector.style(this, contact.getJid())); - final String account = contact.getAccount().getJid().asBareJid().toEscapedString(); + final String account = contact.getAccount().getJid().asBareJid().toString(); binding.detailsAccount.setText(getString(R.string.using_account, account)); AvatarWorkerTask.loadAvatar( contact, binding.detailsContactBadge, R.dimen.avatar_on_details_screen_size); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index c3b0bcd9d9253374f98fe671e3e73074fd93fd19..20ed93c058fa244fd90ba8a61eb08c4940ec7ce9 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -902,8 +902,7 @@ public class ConversationFragment extends XmppFragment } intent.putExtra("contacts", contacts); intent.putExtra( - EXTRA_ACCOUNT, - conversation.getAccount().getJid().asBareJid().toEscapedString()); + EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString()); intent.putExtra("conversation", conversation.getUuid()); startActivityForResult(intent, requestCode); return true; @@ -1562,8 +1561,8 @@ public class ConversationFragment extends XmppFragment intent.setAction(Intent.ACTION_VIEW); intent.putExtra( RtpSessionActivity.EXTRA_ACCOUNT, - id.getAccount().getJid().asBareJid().toEscapedString()); - intent.putExtra(RtpSessionActivity.EXTRA_WITH, id.getWith().toEscapedString()); + id.getAccount().getJid().asBareJid().toString()); + intent.putExtra(RtpSessionActivity.EXTRA_WITH, id.getWith().toString()); if (id instanceof AbstractJingleConnection) { intent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.getSessionId()); startActivity(intent); @@ -3570,7 +3569,7 @@ public class ConversationFragment extends XmppFragment + message.getContact() .getJid() .asBareJid() - .toEscapedString()); + .toString()); break; } return true; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java index 59776a0abeaedab0cb98e30e988b74cfbd8f54db..04853ddd2781e49aaac500c8e543921f15953fba 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java @@ -29,12 +29,13 @@ package eu.siacs.conversations.ui; +import static androidx.recyclerview.widget.ItemTouchHelper.LEFT; +import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT; + import android.app.Activity; -import android.app.AlertDialog; import android.app.Fragment; import android.content.Intent; import android.graphics.Canvas; -import android.graphics.Paint; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -44,22 +45,14 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; - -import com.google.android.material.color.MaterialColors; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; import com.google.common.collect.Collections2; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -77,227 +70,281 @@ import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.ui.util.ScrollState; import eu.siacs.conversations.utils.AccountUtils; import eu.siacs.conversations.utils.EasyOnboardingInvite; - -import static androidx.recyclerview.widget.ItemTouchHelper.LEFT; -import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; public class ConversationsOverviewFragment extends XmppFragment { - private static final String STATE_SCROLL_POSITION = ConversationsOverviewFragment.class.getName()+".scroll_state"; - - private final List conversations = new ArrayList<>(); - private final PendingItem swipedConversation = new PendingItem<>(); - private final PendingItem pendingScrollState = new PendingItem<>(); - private FragmentConversationsOverviewBinding binding; - private ConversationAdapter conversationsAdapter; - private XmppActivity activity; - private final PendingActionHelper pendingActionHelper = new PendingActionHelper(); - - private final ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0,LEFT|RIGHT) { - @Override - public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { - return false; - } - - @Override - public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, - float dX, float dY, int actionState, boolean isCurrentlyActive) { - if (viewHolder instanceof ConversationAdapter.ConversationViewHolder conversationViewHolder) { - getDefaultUIUtil().onDraw(c,recyclerView,conversationViewHolder.binding.frame,dX,dY,actionState,isCurrentlyActive); - } - } - - @Override - public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { - if (viewHolder instanceof ConversationAdapter.ConversationViewHolder conversationViewHolder) { - getDefaultUIUtil().clearView(conversationViewHolder.binding.frame); - } - } - - @Override - public float getSwipeEscapeVelocity(final float defaultEscapeVelocity) { - return 32 * defaultEscapeVelocity; - } - - @Override - public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int direction) { - pendingActionHelper.execute(); - int position = viewHolder.getLayoutPosition(); - try { - swipedConversation.push(conversations.get(position)); - } catch (IndexOutOfBoundsException e) { - return; - } - conversationsAdapter.remove(swipedConversation.peek(), position); - activity.xmppConnectionService.markRead(swipedConversation.peek()); - - if (position == 0 && conversationsAdapter.getItemCount() == 0) { - final Conversation c = swipedConversation.pop(); - activity.xmppConnectionService.archiveConversation(c); - return; - } - final boolean formerlySelected = ConversationFragment.getConversation(getActivity()) == swipedConversation.peek(); - if (activity instanceof OnConversationArchived) { - ((OnConversationArchived) activity).onConversationArchived(swipedConversation.peek()); - } - final Conversation c = swipedConversation.peek(); - final int title; - if (c.getMode() == Conversational.MODE_MULTI) { - if (c.getMucOptions().isPrivateAndNonAnonymous()) { - title = R.string.title_undo_swipe_out_group_chat; - } else { - title = R.string.title_undo_swipe_out_channel; - } - } else { - title = R.string.title_undo_swipe_out_chat; - } - - final Snackbar snackbar = Snackbar.make(binding.list, title, 5000) - .setAction(R.string.undo, v -> { - pendingActionHelper.undo(); - Conversation conversation = swipedConversation.pop(); - conversationsAdapter.insert(conversation, position); - if (formerlySelected) { - if (activity instanceof OnConversationSelected) { - ((OnConversationSelected) activity).onConversationSelected(c); - } - } - LinearLayoutManager layoutManager = (LinearLayoutManager) binding.list.getLayoutManager(); - if (position > layoutManager.findLastVisibleItemPosition()) { - binding.list.smoothScrollToPosition(position); - } - }) - .addCallback(new Snackbar.Callback() { - @Override - public void onDismissed(Snackbar transientBottomBar, int event) { - switch (event) { - case DISMISS_EVENT_SWIPE: - case DISMISS_EVENT_TIMEOUT: - pendingActionHelper.execute(); - break; - } - } - }); - - pendingActionHelper.push(() -> { - if (snackbar.isShownOrQueued()) { - snackbar.dismiss(); - } - final Conversation conversation = swipedConversation.pop(); - if(conversation != null){ - if (!conversation.isRead() && conversation.getMode() == Conversation.MODE_SINGLE) { - return; - } - activity.xmppConnectionService.archiveConversation(c); - } - }); - snackbar.show(); - } - }; - - private ItemTouchHelper touchHelper; - - public static Conversation getSuggestion(Activity activity) { - final Conversation exception; - Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); - if (fragment instanceof ConversationsOverviewFragment) { - exception = ((ConversationsOverviewFragment) fragment).swipedConversation.peek(); - } else { - exception = null; - } - return getSuggestion(activity, exception); - } - - public static Conversation getSuggestion(Activity activity, Conversation exception) { - Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); - if (fragment instanceof ConversationsOverviewFragment) { - List conversations = ((ConversationsOverviewFragment) fragment).conversations; - if (conversations.size() > 0) { - Conversation suggestion = conversations.get(0); - if (suggestion == exception) { - if (conversations.size() > 1) { - return conversations.get(1); - } - } else { - return suggestion; - } - } - } - return null; - - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if (savedInstanceState == null) { - return; - } - pendingScrollState.push(savedInstanceState.getParcelable(STATE_SCROLL_POSITION)); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - if (activity instanceof XmppActivity) { - this.activity = (XmppActivity) activity; - } else { - throw new IllegalStateException("Trying to attach fragment to activity that is not an XmppActivity"); - } - } - @Override - public void onDestroyView() { - Log.d(Config.LOGTAG,"ConversationsOverviewFragment.onDestroyView()"); - super.onDestroyView(); - this.binding = null; - this.conversationsAdapter = null; - this.touchHelper = null; - } - @Override - public void onDestroy() { - Log.d(Config.LOGTAG,"ConversationsOverviewFragment.onDestroy()"); - super.onDestroy(); - - } - @Override - public void onPause() { - Log.d(Config.LOGTAG,"ConversationsOverviewFragment.onPause()"); - pendingActionHelper.execute(); - super.onPause(); - } - - @Override - public void onDetach() { - super.onDetach(); - this.activity = null; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - this.binding = DataBindingUtil.inflate(inflater, R.layout.fragment_conversations_overview, container, false); - this.binding.fab.setOnClickListener((view) -> StartConversationActivity.launch(getActivity())); - - this.conversationsAdapter = new ConversationAdapter(this.activity, this.conversations); - this.conversationsAdapter.setConversationClickListener((view, conversation) -> { - if (activity instanceof OnConversationSelected) { - ((OnConversationSelected) activity).onConversationSelected(conversation); - } else { - Log.w(ConversationsOverviewFragment.class.getCanonicalName(), "Activity does not implement OnConversationSelected"); - } - }); - this.binding.list.setAdapter(this.conversationsAdapter); - this.binding.list.setLayoutManager(new LinearLayoutManager(getActivity(),LinearLayoutManager.VERTICAL,false)); - this.binding.list.addOnScrollListener(ExtendedFabSizeChanger.of(binding.fab)); - this.touchHelper = new ItemTouchHelper(this.callback); - this.touchHelper.attachToRecyclerView(this.binding.list); - return binding.getRoot(); - } + private static final String STATE_SCROLL_POSITION = + ConversationsOverviewFragment.class.getName() + ".scroll_state"; + + private final List conversations = new ArrayList<>(); + private final PendingItem swipedConversation = new PendingItem<>(); + private final PendingItem pendingScrollState = new PendingItem<>(); + private FragmentConversationsOverviewBinding binding; + private ConversationAdapter conversationsAdapter; + private XmppActivity activity; + private final PendingActionHelper pendingActionHelper = new PendingActionHelper(); + + private final ItemTouchHelper.SimpleCallback callback = + new ItemTouchHelper.SimpleCallback(0, LEFT | RIGHT) { + @Override + public boolean onMove( + @NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + @NonNull RecyclerView.ViewHolder target) { + return false; + } + + @Override + public void onChildDraw( + @NonNull Canvas c, + @NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + float dX, + float dY, + int actionState, + boolean isCurrentlyActive) { + if (viewHolder + instanceof + ConversationAdapter.ConversationViewHolder conversationViewHolder) { + getDefaultUIUtil() + .onDraw( + c, + recyclerView, + conversationViewHolder.binding.frame, + dX, + dY, + actionState, + isCurrentlyActive); + } + } + + @Override + public void clearView( + @NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder) { + if (viewHolder + instanceof + ConversationAdapter.ConversationViewHolder conversationViewHolder) { + getDefaultUIUtil().clearView(conversationViewHolder.binding.frame); + } + } + + @Override + public float getSwipeEscapeVelocity(final float defaultEscapeVelocity) { + return 32 * defaultEscapeVelocity; + } + + @Override + public void onSwiped( + final RecyclerView.ViewHolder viewHolder, final int direction) { + pendingActionHelper.execute(); + int position = viewHolder.getLayoutPosition(); + try { + swipedConversation.push(conversations.get(position)); + } catch (IndexOutOfBoundsException e) { + return; + } + conversationsAdapter.remove(swipedConversation.peek(), position); + activity.xmppConnectionService.markRead(swipedConversation.peek()); + + if (position == 0 && conversationsAdapter.getItemCount() == 0) { + final Conversation c = swipedConversation.pop(); + activity.xmppConnectionService.archiveConversation(c); + return; + } + final boolean formerlySelected = + ConversationFragment.getConversation(getActivity()) + == swipedConversation.peek(); + if (activity instanceof OnConversationArchived) { + ((OnConversationArchived) activity) + .onConversationArchived(swipedConversation.peek()); + } + final Conversation c = swipedConversation.peek(); + final int title; + if (c.getMode() == Conversational.MODE_MULTI) { + if (c.getMucOptions().isPrivateAndNonAnonymous()) { + title = R.string.title_undo_swipe_out_group_chat; + } else { + title = R.string.title_undo_swipe_out_channel; + } + } else { + title = R.string.title_undo_swipe_out_chat; + } + + final Snackbar snackbar = + Snackbar.make(binding.list, title, 5000) + .setAction( + R.string.undo, + v -> { + pendingActionHelper.undo(); + Conversation conversation = + swipedConversation.pop(); + conversationsAdapter.insert(conversation, position); + if (formerlySelected) { + if (activity + instanceof OnConversationSelected) { + ((OnConversationSelected) activity) + .onConversationSelected(c); + } + } + LinearLayoutManager layoutManager = + (LinearLayoutManager) + binding.list.getLayoutManager(); + if (position + > layoutManager + .findLastVisibleItemPosition()) { + binding.list.smoothScrollToPosition(position); + } + }) + .addCallback( + new Snackbar.Callback() { + @Override + public void onDismissed( + Snackbar transientBottomBar, int event) { + switch (event) { + case DISMISS_EVENT_SWIPE: + case DISMISS_EVENT_TIMEOUT: + pendingActionHelper.execute(); + break; + } + } + }); + + pendingActionHelper.push( + () -> { + if (snackbar.isShownOrQueued()) { + snackbar.dismiss(); + } + final Conversation conversation = swipedConversation.pop(); + if (conversation != null) { + if (!conversation.isRead() + && conversation.getMode() == Conversation.MODE_SINGLE) { + return; + } + activity.xmppConnectionService.archiveConversation(c); + } + }); + snackbar.show(); + } + }; + + private ItemTouchHelper touchHelper; + + public static Conversation getSuggestion(Activity activity) { + final Conversation exception; + Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); + if (fragment instanceof ConversationsOverviewFragment) { + exception = ((ConversationsOverviewFragment) fragment).swipedConversation.peek(); + } else { + exception = null; + } + return getSuggestion(activity, exception); + } + + public static Conversation getSuggestion(Activity activity, Conversation exception) { + Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); + if (fragment instanceof ConversationsOverviewFragment) { + List conversations = + ((ConversationsOverviewFragment) fragment).conversations; + if (conversations.size() > 0) { + Conversation suggestion = conversations.get(0); + if (suggestion == exception) { + if (conversations.size() > 1) { + return conversations.get(1); + } + } else { + return suggestion; + } + } + } + return null; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (savedInstanceState == null) { + return; + } + pendingScrollState.push(savedInstanceState.getParcelable(STATE_SCROLL_POSITION)); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + if (activity instanceof XmppActivity) { + this.activity = (XmppActivity) activity; + } else { + throw new IllegalStateException( + "Trying to attach fragment to activity that is not an XmppActivity"); + } + } + + @Override + public void onDestroyView() { + Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onDestroyView()"); + super.onDestroyView(); + this.binding = null; + this.conversationsAdapter = null; + this.touchHelper = null; + } + + @Override + public void onDestroy() { + Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onDestroy()"); + super.onDestroy(); + } + + @Override + public void onPause() { + Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onPause()"); + pendingActionHelper.execute(); + super.onPause(); + } + + @Override + public void onDetach() { + super.onDetach(); + this.activity = null; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public View onCreateView( + final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + this.binding = + DataBindingUtil.inflate( + inflater, R.layout.fragment_conversations_overview, container, false); + this.binding.fab.setOnClickListener( + (view) -> StartConversationActivity.launch(getActivity())); + + this.conversationsAdapter = new ConversationAdapter(this.activity, this.conversations); + this.conversationsAdapter.setConversationClickListener( + (view, conversation) -> { + if (activity instanceof OnConversationSelected) { + ((OnConversationSelected) activity).onConversationSelected(conversation); + } else { + Log.w( + ConversationsOverviewFragment.class.getCanonicalName(), + "Activity does not implement OnConversationSelected"); + } + }); + this.binding.list.setAdapter(this.conversationsAdapter); + this.binding.list.setLayoutManager( + new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); + this.binding.list.addOnScrollListener(ExtendedFabSizeChanger.of(binding.fab)); + this.touchHelper = new ItemTouchHelper(this.callback); + this.touchHelper.attachToRecyclerView(this.binding.list); + return binding.getRoot(); + } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { @@ -313,114 +360,131 @@ public class ConversationsOverviewFragment extends XmppFragment { && QuickConversationsService.isPlayStoreFlavor()); } - @Override - public void onBackendConnected() { - refresh(); - } - - @Override - public void onSaveInstanceState(Bundle bundle) { - super.onSaveInstanceState(bundle); - ScrollState scrollState = getScrollState(); - if (scrollState != null) { - bundle.putParcelable(STATE_SCROLL_POSITION, scrollState); - } - } - - private ScrollState getScrollState() { - if (this.binding == null) { - return null; - } - LinearLayoutManager layoutManager = (LinearLayoutManager) this.binding.list.getLayoutManager(); - int position = layoutManager.findFirstVisibleItemPosition(); - final View view = this.binding.list.getChildAt(0); - if (view != null) { - return new ScrollState(position,view.getTop()); - } else { - return new ScrollState(position, 0); - } - } - - @Override - public void onStart() { - super.onStart(); - Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onStart()"); - if (activity.xmppConnectionService != null) { - refresh(); - } - } - - @Override - public void onResume() { - super.onResume(); - Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onResume()"); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - if (MenuDoubleTabUtil.shouldIgnoreTap()) { - return false; - } - switch (item.getItemId()) { - case R.id.action_search: - startActivity(new Intent(getActivity(), SearchActivity.class)); - return true; - case R.id.action_easy_invite: - selectAccountToStartEasyInvite(); - return true; - } - return super.onOptionsItemSelected(item); - } - - private void selectAccountToStartEasyInvite() { - final List accounts = EasyOnboardingInvite.getSupportingAccounts(activity.xmppConnectionService); - if (accounts.isEmpty()) { - //This can technically happen if opening the menu item races with accounts reconnecting or something - Toast.makeText(getActivity(),R.string.no_active_accounts_support_this, Toast.LENGTH_LONG).show(); - } else if (accounts.size() == 1) { - openEasyInviteScreen(accounts.get(0)); - } else { - final AtomicReference selectedAccount = new AtomicReference<>(accounts.get(0)); - final MaterialAlertDialogBuilder alertDialogBuilder = new MaterialAlertDialogBuilder(activity); - alertDialogBuilder.setTitle(R.string.choose_account); - final String[] asStrings = Collections2.transform(accounts, a -> a.getJid().asBareJid().toEscapedString()).toArray(new String[0]); - alertDialogBuilder.setSingleChoiceItems(asStrings, 0, (dialog, which) -> selectedAccount.set(accounts.get(which))); - alertDialogBuilder.setNegativeButton(R.string.cancel, null); - alertDialogBuilder.setPositiveButton(R.string.ok, (dialog, which) -> openEasyInviteScreen(selectedAccount.get())); - alertDialogBuilder.create().show(); - } - } - - private void openEasyInviteScreen(final Account account) { - EasyOnboardingInviteActivity.launch(account, activity); - } - - @Override - void refresh() { - if (this.binding == null || this.activity == null) { - Log.d(Config.LOGTAG,"ConversationsOverviewFragment.refresh() skipped updated because view binding or activity was null"); - return; - } - this.activity.xmppConnectionService.populateWithOrderedConversations(this.conversations); - Conversation removed = this.swipedConversation.peek(); - if (removed != null) { - if (removed.isRead()) { - this.conversations.remove(removed); - } else { - pendingActionHelper.execute(); - } - } - this.conversationsAdapter.notifyDataSetChanged(); - ScrollState scrollState = pendingScrollState.pop(); - if (scrollState != null) { - setScrollPosition(scrollState); - } - } - - private void setScrollPosition(ScrollState scrollPosition) { - if (scrollPosition != null) { - LinearLayoutManager layoutManager = (LinearLayoutManager) binding.list.getLayoutManager(); - layoutManager.scrollToPositionWithOffset(scrollPosition.position, scrollPosition.offset); - } - } + @Override + public void onBackendConnected() { + refresh(); + } + + @Override + public void onSaveInstanceState(Bundle bundle) { + super.onSaveInstanceState(bundle); + ScrollState scrollState = getScrollState(); + if (scrollState != null) { + bundle.putParcelable(STATE_SCROLL_POSITION, scrollState); + } + } + + private ScrollState getScrollState() { + if (this.binding == null) { + return null; + } + LinearLayoutManager layoutManager = + (LinearLayoutManager) this.binding.list.getLayoutManager(); + int position = layoutManager.findFirstVisibleItemPosition(); + final View view = this.binding.list.getChildAt(0); + if (view != null) { + return new ScrollState(position, view.getTop()); + } else { + return new ScrollState(position, 0); + } + } + + @Override + public void onStart() { + super.onStart(); + Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onStart()"); + if (activity.xmppConnectionService != null) { + refresh(); + } + } + + @Override + public void onResume() { + super.onResume(); + Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onResume()"); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (MenuDoubleTabUtil.shouldIgnoreTap()) { + return false; + } + switch (item.getItemId()) { + case R.id.action_search: + startActivity(new Intent(getActivity(), SearchActivity.class)); + return true; + case R.id.action_easy_invite: + selectAccountToStartEasyInvite(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void selectAccountToStartEasyInvite() { + final List accounts = + EasyOnboardingInvite.getSupportingAccounts(activity.xmppConnectionService); + if (accounts.isEmpty()) { + // This can technically happen if opening the menu item races with accounts reconnecting + // or something + Toast.makeText( + getActivity(), + R.string.no_active_accounts_support_this, + Toast.LENGTH_LONG) + .show(); + } else if (accounts.size() == 1) { + openEasyInviteScreen(accounts.get(0)); + } else { + final AtomicReference selectedAccount = new AtomicReference<>(accounts.get(0)); + final MaterialAlertDialogBuilder alertDialogBuilder = + new MaterialAlertDialogBuilder(activity); + alertDialogBuilder.setTitle(R.string.choose_account); + final String[] asStrings = + Collections2.transform(accounts, a -> a.getJid().asBareJid().toString()) + .toArray(new String[0]); + alertDialogBuilder.setSingleChoiceItems( + asStrings, 0, (dialog, which) -> selectedAccount.set(accounts.get(which))); + alertDialogBuilder.setNegativeButton(R.string.cancel, null); + alertDialogBuilder.setPositiveButton( + R.string.ok, (dialog, which) -> openEasyInviteScreen(selectedAccount.get())); + alertDialogBuilder.create().show(); + } + } + + private void openEasyInviteScreen(final Account account) { + EasyOnboardingInviteActivity.launch(account, activity); + } + + @Override + void refresh() { + if (this.binding == null || this.activity == null) { + Log.d( + Config.LOGTAG, + "ConversationsOverviewFragment.refresh() skipped updated because view binding" + + " or activity was null"); + return; + } + this.activity.xmppConnectionService.populateWithOrderedConversations(this.conversations); + Conversation removed = this.swipedConversation.peek(); + if (removed != null) { + if (removed.isRead()) { + this.conversations.remove(removed); + } else { + pendingActionHelper.execute(); + } + } + this.conversationsAdapter.notifyDataSetChanged(); + ScrollState scrollState = pendingScrollState.pop(); + if (scrollState != null) { + setScrollPosition(scrollState); + } + } + + private void setScrollPosition(ScrollState scrollPosition) { + if (scrollPosition != null) { + LinearLayoutManager layoutManager = + (LinearLayoutManager) binding.list.getLayoutManager(); + layoutManager.scrollToPositionWithOffset( + scrollPosition.position, scrollPosition.offset); + } + } } diff --git a/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java b/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java index b20db451d74501c0cfc5c4fac6895b17fc356029..b12aba750cb0accb3a0ace23dccf6000425ba759 100644 --- a/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java @@ -11,18 +11,11 @@ import android.text.TextWatcher; import android.view.View; import android.widget.AdapterView; import android.widget.Button; - 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.Collection; -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.DialogCreatePublicChannelBinding; import eu.siacs.conversations.entities.Account; @@ -33,10 +26,14 @@ import eu.siacs.conversations.ui.util.DelayedHintHelper; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; public class CreatePublicChannelDialog extends DialogFragment implements OnBackendConnected { - private static final char[] FORBIDDEN = new char[]{'\u0022','&','\'','/',':','<','>','@'}; + private static final char[] FORBIDDEN = + new char[] {'\u0022', '&', '\'', '/', ':', '<', '>', '@'}; private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list"; private CreatePublicChannelDialogListener mListener; @@ -62,48 +59,56 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - jidWasModified = savedInstanceState != null && savedInstanceState.getBoolean("jid_was_modified_false", false); - nameEntered = savedInstanceState != null && savedInstanceState.getBoolean("name_entered", false); - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); + jidWasModified = + savedInstanceState != null + && savedInstanceState.getBoolean("jid_was_modified_false", false); + nameEntered = + savedInstanceState != null && savedInstanceState.getBoolean("name_entered", false); + final MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(requireActivity()); builder.setTitle(R.string.create_public_channel); - final DialogCreatePublicChannelBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.dialog_create_public_channel, null, false); - binding.account.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - updateJidSuggestion(binding); - } - - @Override - public void onNothingSelected(AdapterView parent) { - - } - }); - binding.jid.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - - } - - @Override - public void afterTextChanged(Editable s) { - if (skipTetxWatcher) { - return; - } - if (jidWasModified) { - jidWasModified = !TextUtils.isEmpty(s); - } else { - jidWasModified = !s.toString().equals(getJidSuggestion(binding)); - } - } - }); - updateInputs(binding,false); + final DialogCreatePublicChannelBinding binding = + DataBindingUtil.inflate( + getActivity().getLayoutInflater(), + R.layout.dialog_create_public_channel, + null, + false); + binding.account.setOnItemSelectedListener( + new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected( + AdapterView parent, View view, int position, long id) { + updateJidSuggestion(binding); + } + + @Override + public void onNothingSelected(AdapterView parent) {} + }); + binding.jid.addTextChangedListener( + new TextWatcher() { + @Override + public void beforeTextChanged( + CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + if (skipTetxWatcher) { + return; + } + if (jidWasModified) { + jidWasModified = !TextUtils.isEmpty(s); + } else { + jidWasModified = !s.toString().equals(getJidSuggestion(binding)); + } + } + }); + updateInputs(binding, 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(nameEntered ? R.string.create : R.string.next, null); builder.setNegativeButton(nameEntered ? R.string.back : R.string.cancel, null); @@ -111,14 +116,18 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.item_autocomplete); binding.jid.setAdapter(knownHostsAdapter); final AlertDialog dialog = builder.create(); - binding.groupChatName.setOnEditorActionListener((v, actionId, event) -> { - submit(dialog, binding); - return true; - }); - dialog.setOnShowListener(dialogInterface -> { - dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> goBack(dialog, binding)); - dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> submit(dialog, binding)); - }); + binding.groupChatName.setOnEditorActionListener( + (v, actionId, event) -> { + submit(dialog, binding); + return true; + }); + dialog.setOnShowListener( + dialogInterface -> { + dialog.getButton(DialogInterface.BUTTON_NEGATIVE) + .setOnClickListener(v -> goBack(dialog, binding)); + dialog.getButton(DialogInterface.BUTTON_POSITIVE) + .setOnClickListener(v -> submit(dialog, binding)); + }); return dialog; } @@ -134,13 +143,15 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke @Override public void onSaveInstanceState(Bundle outState) { - outState.putBoolean("jid_was_modified",jidWasModified); + outState.putBoolean("jid_was_modified", jidWasModified); outState.putBoolean("name_entered", nameEntered); super.onSaveInstanceState(outState); } private static String getJidSuggestion(final DialogCreatePublicChannelBinding binding) { - final Account account = StartConversationActivity.getSelectedAccount(binding.getRoot().getContext(), binding.account); + final Account account = + StartConversationActivity.getSelectedAccount( + binding.getRoot().getContext(), binding.account); final XmppConnection connection = account == null ? null : account.getXmppConnection(); if (connection == null) { return ""; @@ -156,18 +167,18 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke return ""; } else { try { - return Jid.of(localpart, domain, null).toEscapedString(); + return Jid.of(localpart, domain, null).toString(); } catch (IllegalArgumentException e) { - return Jid.of(CryptoHelper.pronounceable(), domain, null).toEscapedString(); + return Jid.of(CryptoHelper.pronounceable(), domain, null).toString(); } } } private static String clean(String name) { - for(char c : FORBIDDEN) { - name = name.replace(String.valueOf(c),""); + for (char c : FORBIDDEN) { + name = name.replace(String.valueOf(c), ""); } - return name.replaceAll("\\s+","-"); + return name.replaceAll("\\s+", "-"); } private void goBack(AlertDialog dialog, DialogCreatePublicChannelBinding binding) { @@ -189,22 +200,26 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke if (nameEntered) { binding.nameLayout.setError(null); if (address.isEmpty()) { - binding.xmppAddressLayout.setError(context.getText(R.string.please_enter_xmpp_address)); + binding.xmppAddressLayout.setError( + context.getText(R.string.please_enter_xmpp_address)); } else { final Jid jid; try { - jid = Jid.ofEscaped(address); - } catch (IllegalArgumentException e) { + jid = Jid.ofUserInput(address); + } catch (final IllegalArgumentException e) { binding.xmppAddressLayout.setError(context.getText(R.string.invalid_jid)); return; } - final Account account = StartConversationActivity.getSelectedAccount(context, binding.account); + final Account account = + StartConversationActivity.getSelectedAccount(context, binding.account); if (account == null) { return; } - final XmppConnectionService service = ((XmppActivity )context).xmppConnectionService; + final XmppConnectionService service = + ((XmppActivity) context).xmppConnectionService; if (service != null && service.findFirstMuc(jid) != null) { - binding.xmppAddressLayout.setError(context.getString(R.string.channel_already_exists)); + binding.xmppAddressLayout.setError( + context.getString(R.string.channel_already_exists)); return; } mListener.onCreatePublicChannel(account, name, jid); @@ -214,7 +229,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke binding.xmppAddressLayout.setError(null); if (name.isEmpty()) { binding.nameLayout.setError(context.getText(R.string.please_enter_name)); - } else if (StartConversationActivity.isValidJid(name)){ + } else if (StartConversationActivity.isValidJid(name)) { binding.nameLayout.setError(context.getText(R.string.this_is_an_xmpp_address)); } else { binding.nameLayout.setError(null); @@ -227,8 +242,8 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke } } - - private void updateInputs(final DialogCreatePublicChannelBinding binding, final boolean requestFocus) { + private void updateInputs( + final DialogCreatePublicChannelBinding binding, final boolean requestFocus) { binding.xmppAddressLayout.setVisibility(nameEntered ? View.VISIBLE : View.GONE); binding.nameLayout.setVisibility(nameEntered ? View.GONE : View.VISIBLE); if (!requestFocus) { @@ -256,7 +271,8 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke private void refreshKnownHosts() { Activity activity = getActivity(); if (activity instanceof XmppActivity) { - Collection hosts = ((XmppActivity) activity).xmppConnectionService.getKnownConferenceHosts(); + Collection hosts = + ((XmppActivity) activity).xmppConnectionService.getKnownConferenceHosts(); this.knownHostsAdapter.refresh(hosts); } } @@ -271,8 +287,8 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke try { mListener = (CreatePublicChannelDialogListener) context; } catch (ClassCastException e) { - throw new ClassCastException(context.toString() - + " must implement CreateConferenceDialogListener"); + throw new ClassCastException( + context.toString() + " must implement CreateConferenceDialogListener"); } } @@ -280,7 +296,8 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke public void onStart() { super.onStart(); final Activity activity = getActivity(); - if (activity instanceof XmppActivity && ((XmppActivity) activity).xmppConnectionService != null) { + if (activity instanceof XmppActivity + && ((XmppActivity) activity).xmppConnectionService != null) { refreshKnownHosts(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 61c76eb6e9fbf5387ea0d0a92b87f25d80b96f84..14f73295716b1f14bdbf859f9112c7000f506abd 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -29,19 +29,16 @@ import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; import androidx.lifecycle.Lifecycle; - import com.google.android.material.color.MaterialColors; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputLayout; import com.google.common.base.CharMatcher; import com.google.common.base.Strings; - import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -79,15 +76,12 @@ import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.XmppConnection.Features; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.pep.Avatar; - -import okhttp3.HttpUrl; - -import org.openintents.openpgp.util.OpenPgpUtils; - import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import okhttp3.HttpUrl; +import org.openintents.openpgp.util.OpenPgpUtils; public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, @@ -144,8 +138,7 @@ public class EditAccountActivity extends OmemoActivity new Intent( getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra( - EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString()); startActivity(intent); } } @@ -259,12 +252,12 @@ public class EditAccountActivity extends OmemoActivity try { if (mUsernameMode) { jid = - Jid.ofEscaped( + Jid.of( binding.accountJid.getText().toString(), getUserModeDomain(), null); } else { - jid = Jid.ofEscaped(binding.accountJid.getText().toString()); + jid = Jid.ofUserInput(binding.accountJid.getText().toString()); Resolver.checkDomain(jid); } } catch (final NullPointerException | IllegalArgumentException e) { @@ -539,15 +532,13 @@ public class EditAccountActivity extends OmemoActivity if (wasFirstAccount) { intent.putExtra("init", true); } - intent.putExtra( - EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString()); } else { intent = new Intent( getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra( - EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString()); intent.putExtra("setup", true); } if (wasFirstAccount) { @@ -700,9 +691,9 @@ public class EditAccountActivity extends OmemoActivity protected boolean jidEdited() { final String unmodified; if (mUsernameMode) { - unmodified = this.mAccount.getJid().getEscapedLocal(); + unmodified = this.mAccount.getJid().getLocal(); } else { - unmodified = this.mAccount.getJid().asBareJid().toEscapedString(); + unmodified = this.mAccount.getJid().asBareJid().toString(); } return !unmodified.equals(this.binding.accountJid.getText().toString()); } @@ -824,7 +815,7 @@ public class EditAccountActivity extends OmemoActivity final Intent intent = getIntent(); if (intent != null) { try { - this.jidToEdit = Jid.ofEscaped(intent.getStringExtra("jid")); + this.jidToEdit = Jid.of(intent.getStringExtra("jid")); } catch (final IllegalArgumentException | NullPointerException ignored) { this.jidToEdit = null; } @@ -929,8 +920,7 @@ public class EditAccountActivity extends OmemoActivity @Override public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) { if (mAccount != null) { - savedInstanceState.putString( - "account", mAccount.getJid().asBareJid().toEscapedString()); + savedInstanceState.putString("account", mAccount.getJid().asBareJid().toString()); savedInstanceState.putBoolean("initMode", mInitMode); savedInstanceState.putBoolean( "showMoreTable", binding.serverInfoMore.getVisibility() == View.VISIBLE); @@ -943,8 +933,7 @@ public class EditAccountActivity extends OmemoActivity if (mSavedInstanceAccount != null) { try { this.mAccount = - xmppConnectionService.findAccountByJid( - Jid.ofEscaped(mSavedInstanceAccount)); + xmppConnectionService.findAccountByJid(Jid.of(mSavedInstanceAccount)); this.mInitMode = mSavedInstanceInit; init = false; } catch (IllegalArgumentException e) { @@ -1010,7 +999,7 @@ public class EditAccountActivity extends OmemoActivity break; case R.id.action_show_block_list: final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class); - showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toEscapedString()); + showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString()); startActivity(showBlocklistIntent); break; case R.id.action_server_info_show_more: @@ -1070,7 +1059,7 @@ public class EditAccountActivity extends OmemoActivity private void gotoChangePassword() { final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class); - changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toEscapedString()); + changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString()); startActivity(changePasswordIntent); } @@ -1167,15 +1156,12 @@ public class EditAccountActivity extends OmemoActivity if (init) { this.binding.accountJid.getEditableText().clear(); if (mUsernameMode) { - this.binding - .accountJid - .getEditableText() - .append(this.mAccount.getJid().getEscapedLocal()); + this.binding.accountJid.getEditableText().append(this.mAccount.getJid().getLocal()); } else { this.binding .accountJid .getEditableText() - .append(this.mAccount.getJid().asBareJid().toEscapedString()); + .append(this.mAccount.getJid().asBareJid().toString()); } this.binding.accountPassword.getEditableText().clear(); this.binding.accountPassword.getEditableText().append(this.mAccount.getPassword()); diff --git a/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java b/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java index 787203f0c42972bfb46ede0c5b89723caa8c366c..bcd2432da3096e20936ada500aa4684a3e6efa52 100644 --- a/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java @@ -7,21 +7,12 @@ import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.widget.ArrayAdapter; - 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.common.base.Strings; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.DialogEnterJidBinding; import eu.siacs.conversations.services.XmppConnectionService; @@ -29,6 +20,11 @@ import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; import eu.siacs.conversations.ui.interfaces.OnBackendConnected; import eu.siacs.conversations.ui.util.DelayedHintHelper; import eu.siacs.conversations.xmpp.Jid; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; public class EnterJidDialog extends DialogFragment implements OnBackendConnected, TextWatcher { @@ -95,10 +91,15 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { final var arguments = getArguments(); - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); + final MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(requireActivity()); builder.setTitle(arguments.getString(TITLE_KEY)); binding = - DataBindingUtil.inflate(requireActivity().getLayoutInflater(), R.layout.dialog_enter_jid, null, false); + DataBindingUtil.inflate( + requireActivity().getLayoutInflater(), + R.layout.dialog_enter_jid, + null, + false); this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.item_autocomplete); binding.jid.setAdapter(this.knownHostsAdapter); binding.jid.addTextChangedListener(this); @@ -124,7 +125,8 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected binding.account); } else { final ArrayAdapter adapter = - new ArrayAdapter<>(requireActivity(), R.layout.item_autocomplete, new String[] {account}); + new ArrayAdapter<>( + requireActivity(), R.layout.item_autocomplete, new String[] {account}); binding.account.setText(account); binding.account.setEnabled(false); adapter.setDropDownViewResource(R.layout.item_autocomplete); @@ -136,8 +138,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected builder.setPositiveButton(getArguments().getString(POSITIVE_BUTTON_KEY), null); this.dialog = builder.create(); - View.OnClickListener dialogOnClick = - v -> handleEnter(binding, account); + View.OnClickListener dialogOnClick = v -> handleEnter(binding, account); binding.jid.setOnEditorActionListener( (v, actionId, event) -> { @@ -156,13 +157,13 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected return; } try { - accountJid = Jid.ofEscaped((String) binding.account.getEditableText().toString()); + accountJid = Jid.of(binding.account.getEditableText().toString()); } catch (final IllegalArgumentException e) { return; } final Jid contactJid; try { - contactJid = Jid.ofEscaped(binding.jid.getText().toString().trim()); + contactJid = Jid.ofUserInput(binding.jid.getText().toString().trim()); } catch (final IllegalArgumentException e) { binding.jidLayout.setError(getActivity().getString(R.string.invalid_jid)); return; @@ -176,7 +177,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected issuedWarning = true; return; } - if (suspiciousSubDomain(contactJid.getDomain().toEscapedString())) { + if (suspiciousSubDomain(contactJid.getDomain().toString())) { binding.jidLayout.setError( getActivity().getString(R.string.this_looks_like_channel)); dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway); diff --git a/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java b/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java index ac5b07e77dcb543a5a511de2b81c3605834dd280..58d46fd76712840aa5d3537423e5aa5259ea4a2a 100644 --- a/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java @@ -3,11 +3,7 @@ package eu.siacs.conversations.ui; import android.content.Context; import android.content.Intent; import android.os.Bundle; - import androidx.databinding.DataBindingUtil; - -import java.util.List; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityMediaBrowserBinding; import eu.siacs.conversations.entities.Account; @@ -18,6 +14,7 @@ import eu.siacs.conversations.ui.interfaces.OnMediaLoaded; import eu.siacs.conversations.ui.util.Attachment; import eu.siacs.conversations.ui.util.GridManager; import eu.siacs.conversations.xmpp.Jid; +import java.util.List; public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded { @@ -28,20 +25,17 @@ public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - this.binding = DataBindingUtil.setContentView(this,R.layout.activity_media_browser); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_media_browser); Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); setSupportActionBar(binding.toolbar); configureActionBar(getSupportActionBar()); mMediaAdapter = new MediaAdapter(this, R.dimen.media_size); this.binding.media.setAdapter(mMediaAdapter); GridManager.setupLayoutManager(this, this.binding.media, R.dimen.browser_media_size); - } @Override - protected void refreshUiReal() { - - } + protected void refreshUiReal() {} @Override protected void onBackendConnected() { @@ -49,29 +43,30 @@ public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded String account = intent == null ? null : intent.getStringExtra("account"); String jid = intent == null ? null : intent.getStringExtra("jid"); if (account != null && jid != null) { - xmppConnectionService.getAttachments(account, Jid.ofEscaped(jid), 0, this); + xmppConnectionService.getAttachments(account, Jid.of(jid), 0, this); } } public static void launch(Context context, Contact contact) { - launch(context, contact.getAccount(), contact.getJid().asBareJid().toEscapedString()); + launch(context, contact.getAccount(), contact.getJid().asBareJid().toString()); } public static void launch(Context context, Conversation conversation) { - launch(context, conversation.getAccount(), conversation.getJid().asBareJid().toEscapedString()); + launch(context, conversation.getAccount(), conversation.getJid().asBareJid().toString()); } private static void launch(Context context, Account account, String jid) { final Intent intent = new Intent(context, MediaBrowserActivity.class); - intent.putExtra("account",account.getUuid()); - intent.putExtra("jid",jid); + intent.putExtra("account", account.getUuid()); + intent.putExtra("jid", jid); context.startActivity(intent); } @Override public void onMediaLoaded(List attachments) { - runOnUiThread(()->{ - mMediaAdapter.setAttachments(attachments); - }); + runOnUiThread( + () -> { + mMediaAdapter.setAttachments(attachments); + }); } } diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index dafbbabfc5564c94cdeb9c15d013726900b5b70f..ebc8225d80ee0140657b660a0e1f202e25346e44 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -60,8 +60,7 @@ public class PublishProfilePictureActivity extends XmppActivity getApplicationContext(), StartConversationActivity.class); StartConversationActivity.addInviteUri(intent, getIntent()); intent.putExtra("init", true); - intent.putExtra( - EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); startActivity(intent); } Toast.makeText( @@ -118,8 +117,7 @@ public class PublishProfilePictureActivity extends XmppActivity } StartConversationActivity.addInviteUri(intent, getIntent()); if (account != null) { - intent.putExtra( - EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); } startActivity(intent); } diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 89068306de6d976b59aab276854c9d0a40d34b20..8435a4da1804df5887555be80a7d6b310984f077 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -1,7 +1,6 @@ package eu.siacs.conversations.ui; import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied; - import static java.util.Arrays.asList; import android.Manifest; @@ -25,13 +24,11 @@ import android.view.MenuItem; import android.view.View; import android.view.WindowManager; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; import androidx.databinding.DataBindingUtil; - import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.common.base.Optional; import com.google.common.base.Preconditions; @@ -41,7 +38,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityRtpSessionBinding; @@ -65,16 +61,14 @@ import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession; import eu.siacs.conversations.xmpp.jingle.RtpCapability; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; - -import org.webrtc.RendererCommon; -import org.webrtc.SurfaceViewRenderer; -import org.webrtc.VideoTrack; - import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; +import org.webrtc.RendererCommon; +import org.webrtc.SurfaceViewRenderer; +import org.webrtc.VideoTrack; public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate, @@ -300,7 +294,7 @@ public class RtpSessionActivity extends XmppActivity final String action = intent.getAction(); final String lastAction = intent.getStringExtra(EXTRA_LAST_ACTION); final Account account = extractAccount(intent); - final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH)); + final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH)); final String state = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE); if (!Intent.ACTION_VIEW.equals(action) || state == null @@ -504,7 +498,7 @@ public class RtpSessionActivity extends XmppActivity Log.d(Config.LOGTAG, "initializeWithIntent(" + event + "," + action + ")"); final Account account = extractAccount(intent); final var extraWith = intent.getStringExtra(EXTRA_WITH); - final Jid with = Strings.isNullOrEmpty(extraWith) ? null : Jid.ofEscaped(extraWith); + final Jid with = Strings.isNullOrEmpty(extraWith) ? null : Jid.of(extraWith); if (with == null || account == null) { Log.e(Config.LOGTAG, "intent is missing extras (account or with)"); return; @@ -573,7 +567,7 @@ public class RtpSessionActivity extends XmppActivity binding.with.setText(contact.getDisplayName()); if (Arrays.asList(RtpEndUserState.INCOMING_CALL, RtpEndUserState.ACCEPTING_CALL) .contains(state)) { - binding.withJid.setText(contact.getJid().asBareJid().toEscapedString()); + binding.withJid.setText(contact.getJid().asBareJid().toString()); binding.withJid.setVisibility(View.VISIBLE); } else { binding.withJid.setVisibility(View.GONE); @@ -776,7 +770,8 @@ public class RtpSessionActivity extends XmppActivity .getTerminalSessionState(with, sessionId); if (terminatedRtpSession == null) { throw new IllegalStateException( - "failed to initialize activity with running rtp session. session not found"); + "failed to initialize activity with running rtp session. session not" + + " found"); } initializeWithTerminatedSessionState(account, with, terminatedRtpSession); return true; @@ -837,8 +832,8 @@ public class RtpSessionActivity extends XmppActivity private void resetIntent(final Account account, final Jid with, final String sessionId) { final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString()); - intent.putExtra(EXTRA_WITH, with.toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().toString()); + intent.putExtra(EXTRA_WITH, with.toString()); intent.putExtra(EXTRA_SESSION_ID, sessionId); setIntent(intent); } @@ -895,10 +890,12 @@ public class RtpSessionActivity extends XmppActivity case RETRACTED -> setTitle(R.string.rtp_state_retracted); case APPLICATION_ERROR -> setTitle(R.string.rtp_state_application_failure); case SECURITY_ERROR -> setTitle(R.string.rtp_state_security_error); - case ENDED -> throw new IllegalStateException( - "Activity should have called finishAndReleaseWakeLock();"); - default -> throw new IllegalStateException( - String.format("State %s has not been handled in UI", state)); + case ENDED -> + throw new IllegalStateException( + "Activity should have called finishAndReleaseWakeLock();"); + default -> + throw new IllegalStateException( + String.format("State %s has not been handled in UI", state)); } } @@ -932,9 +929,7 @@ public class RtpSessionActivity extends XmppActivity final Account account = contact == null ? getWith().getAccount() : contact.getAccount(); binding.usingAccount.setVisibility(View.VISIBLE); binding.usingAccount.setText( - getString( - R.string.using_account, - account.getJid().asBareJid().toEscapedString())); + getString(R.string.using_account, account.getJid().asBareJid().toString())); } else { binding.usingAccount.setVisibility(View.GONE); binding.contactPhoto.setVisibility(View.GONE); @@ -1430,12 +1425,12 @@ public class RtpSessionActivity extends XmppActivity private void retry(final View view) { final Intent intent = getIntent(); final Account account = extractAccount(intent); - final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH)); + final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH)); final String lastAction = intent.getStringExtra(EXTRA_LAST_ACTION); final String action = intent.getAction(); final Set media = actionToMedia(lastAction == null ? action : lastAction); this.rtpConnectionReference = null; - Log.d(Config.LOGTAG, "attempting retry with " + with.toEscapedString()); + Log.d(Config.LOGTAG, "attempting retry with " + with.toString()); CallIntegrationConnectionService.placeCall(xmppConnectionService, account, with, media); } @@ -1446,7 +1441,7 @@ public class RtpSessionActivity extends XmppActivity private void recordVoiceMail(final View view) { final Intent intent = getIntent(); final Account account = extractAccount(intent); - final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH)); + final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH)); final Conversation conversation = xmppConnectionService.findOrCreateConversation(account, with, false, true); final Intent launchIntent = new Intent(this, ConversationsActivity.class); @@ -1611,7 +1606,7 @@ public class RtpSessionActivity extends XmppActivity return; } final Set media = actionToMedia(currentIntent.getStringExtra(EXTRA_LAST_ACTION)); - if (Jid.ofEscaped(withExtra).asBareJid().equals(with)) { + if (Jid.of(withExtra).asBareJid().equals(with)) { runOnUiThread( () -> { updateVerifiedShield(false); @@ -1634,11 +1629,11 @@ public class RtpSessionActivity extends XmppActivity private void resetIntent( final Account account, Jid with, final RtpEndUserState state, final Set media) { final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().toString()); if (RtpCapability.jmiSupport(account.getRoster().getContact(with))) { - intent.putExtra(EXTRA_WITH, with.asBareJid().toEscapedString()); + intent.putExtra(EXTRA_WITH, with.asBareJid().toString()); } else { - intent.putExtra(EXTRA_WITH, with.toEscapedString()); + intent.putExtra(EXTRA_WITH, with.toString()); } intent.putExtra(EXTRA_LAST_REPORTED_STATE, state.toString()); intent.putExtra( diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index 1f83d1f4546f8a027b4eec96fcc2335710aa4cc4..a081f3755a7db211a1a9334a58efe7831431afe7 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -216,7 +216,7 @@ public class ShareWithActivity extends XmppActivity final Conversation conversation; Account account; try { - account = xmppConnectionService.findAccountByJid(Jid.ofEscaped(share.account)); + account = xmppConnectionService.findAccountByJid(Jid.of(share.account)); } catch (final IllegalArgumentException e) { account = null; } diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index a8a0cb05886823fceeafec4f9005d92f204a1c64..a2ec5fe949feecda5381f086e489acc5fda5c969 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -36,7 +36,6 @@ import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; - import androidx.annotation.MenuRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -52,7 +51,6 @@ import androidx.fragment.app.FragmentTransaction; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; - import com.google.android.material.color.MaterialColors; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.textfield.TextInputLayout; @@ -60,7 +58,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.leinardi.android.speeddial.SpeedDialActionItem; import com.leinardi.android.speeddial.SpeedDialView; - import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -87,7 +84,6 @@ import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.XmppConnection; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -359,7 +355,7 @@ public class StartConversationActivity extends XmppActivity mSearchEditText != null ? mSearchEditText.getText().toString() : null; final String prefilled; if (isValidJid(searchString)) { - prefilled = Jid.ofEscaped(searchString).toEscapedString(); + prefilled = Jid.of(searchString).toString(); } else { prefilled = null; } @@ -418,14 +414,15 @@ public class StartConversationActivity extends XmppActivity .create(); speedDialView.addActionItem(actionItem); } - speedDialView.setContentDescription(getString(R.string.add_contact_or_create_or_join_group_chat)); + speedDialView.setContentDescription( + getString(R.string.add_contact_or_create_or_join_group_chat)); } - public static boolean isValidJid(String input) { + public static boolean isValidJid(final String input) { try { - Jid jid = Jid.ofEscaped(input); + final Jid jid = Jid.ofUserInput(input); return !jid.isDomainJid(); - } catch (IllegalArgumentException e) { + } catch (final IllegalArgumentException e) { return false; } } @@ -505,7 +502,7 @@ public class StartConversationActivity extends XmppActivity protected void shareBookmarkUri(int position) { Bookmark bookmark = (Bookmark) conferences.get(position); - shareAsChannel(this, bookmark.getJid().asBareJid().toEscapedString()); + shareAsChannel(this, bookmark.getJid().asBareJid().toString()); } public static void shareAsChannel(final Context context, final String address) { @@ -549,7 +546,7 @@ public class StartConversationActivity extends XmppActivity protected void showQrForContact() { int position = contact_context_id; Contact contact = (Contact) contacts.get(position); - showQrCode("xmpp:" + contact.getJid().asBareJid().toEscapedString()); + showQrCode("xmpp:" + contact.getJid().asBareJid().toString()); } protected void toggleContactBlock() { @@ -564,8 +561,7 @@ public class StartConversationActivity extends XmppActivity builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.action_delete_contact); builder.setMessage( - JidDialog.style( - this, R.string.remove_contact_text, contact.getJid().toEscapedString())); + JidDialog.style(this, R.string.remove_contact_text, contact.getJid().toString())); builder.setPositiveButton( R.string.delete, (dialog, which) -> { @@ -588,11 +584,10 @@ public class StartConversationActivity extends XmppActivity JidDialog.style( this, R.string.remove_bookmark_and_close, - bookmark.getJid().toEscapedString())); + bookmark.getJid().toString())); } else { builder.setMessage( - JidDialog.style( - this, R.string.remove_bookmark, bookmark.getJid().toEscapedString())); + JidDialog.style(this, R.string.remove_bookmark, bookmark.getJid().toString())); } builder.setPositiveButton( hasConversation ? R.string.delete_and_close : R.string.delete, @@ -710,7 +705,7 @@ public class StartConversationActivity extends XmppActivity if (context instanceof XmppActivity) { final Jid jid; try { - jid = Jid.ofEscaped(spinner.getText().toString()); + jid = Jid.of(spinner.getText().toString()); } catch (final IllegalArgumentException e) { return null; } @@ -1078,11 +1073,11 @@ public class StartConversationActivity extends XmppActivity switchToConversationDoNotAppend(muc, invite.getBody()); return true; } else { - showJoinConferenceDialog(invite.getJid().asBareJid().toEscapedString()); + showJoinConferenceDialog(invite.getJid().asBareJid().toString()); return false; } - } else if (contacts.size() == 0) { - showCreateContactDialog(invite.getJid().toEscapedString(), invite); + } else if (contacts.isEmpty()) { + showCreateContactDialog(invite.getJid().toString(), invite); return false; } else if (contacts.size() == 1) { Contact contact = contacts.get(0); @@ -1106,10 +1101,10 @@ public class StartConversationActivity extends XmppActivity if (mMenuSearchView != null) { mMenuSearchView.expandActionView(); mSearchEditText.setText(""); - mSearchEditText.append(invite.getJid().toEscapedString()); - filter(invite.getJid().toEscapedString()); + mSearchEditText.append(invite.getJid().toString()); + filter(invite.getJid().toString()); } else { - mInitialSearchValue.push(invite.getJid().toEscapedString()); + mInitialSearchValue.push(invite.getJid().toString()); } return true; } @@ -1125,7 +1120,7 @@ public class StartConversationActivity extends XmppActivity JidDialog.style( this, R.string.verifying_omemo_keys_trusted_source, - contact.getJid().asBareJid().toEscapedString(), + contact.getJid().asBareJid().toString(), contact.getDisplayName())); builder.setView(view); builder.setPositiveButton( @@ -1237,8 +1232,7 @@ public class StartConversationActivity extends XmppActivity intent.putExtra(ChooseContactActivity.EXTRA_SELECT_MULTIPLE, true); intent.putExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME, name.trim()); intent.putExtra( - ChooseContactActivity.EXTRA_ACCOUNT, - account.getJid().asBareJid().toEscapedString()); + ChooseContactActivity.EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants); startActivityForResult(intent, REQUEST_CREATE_CONFERENCE); } @@ -1259,13 +1253,13 @@ public class StartConversationActivity extends XmppActivity final String input = jid.getText().toString().trim(); Jid conferenceJid; try { - conferenceJid = Jid.ofEscaped(input); + conferenceJid = Jid.ofUserInput(input); } catch (final IllegalArgumentException e) { final XmppUri xmppUri = new XmppUri(input); if (xmppUri.isValidJid() && xmppUri.isAction(XmppUri.ACTION_JOIN)) { final Editable editable = jid.getEditableText(); editable.clear(); - editable.append(xmppUri.getJid().toEscapedString()); + editable.append(xmppUri.getJid().toString()); conferenceJid = xmppUri.getJid(); } else { layout.setError(getString(R.string.invalid_jid)); diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index ba069b9a573a0027f9d9cfd4c8448ff238701c5f..c1a5ef08c9649c23acd2e35b301783e2675e4924 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.ui; -import android.app.AlertDialog; import android.content.Intent; import android.os.Bundle; import android.util.Log; @@ -10,12 +9,9 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; - import androidx.appcompat.app.ActionBar; import androidx.databinding.DataBindingUtil; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.OmemoSetting; @@ -32,429 +28,515 @@ import eu.siacs.conversations.utils.IrregularUnicodeDetector; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; - -import org.whispersystems.libsignal.IdentityKey; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import org.whispersystems.libsignal.IdentityKey; public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdated { - private final Map ownKeysToTrust = new HashMap<>(); - private final Map> foreignKeysToTrust = new HashMap<>(); - private final OnClickListener mCancelButtonListener = v -> { - setResult(RESULT_CANCELED); - finish(); - }; - private List contactJids; - private Account mAccount; - private Conversation mConversation; - private final OnClickListener mSaveButtonListener = v -> { - commitTrusts(); - finishOk(false); - }; - private final AtomicBoolean mUseCameraHintShown = new AtomicBoolean(false); - private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS; - private Toast mUseCameraHintToast = null; - private ActivityTrustKeysBinding binding; - - @Override - protected void refreshUiReal() { - invalidateOptionsMenu(); - populateView(); - } - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - this.binding = DataBindingUtil.setContentView(this, R.layout.activity_trust_keys); - Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); - this.contactJids = new ArrayList<>(); - final var intent = getIntent(); - final String[] contacts = intent == null ? null : intent.getStringArrayExtra("contacts"); - for (final String jid : (contacts == null ? new String[0] : contacts)) { - try { - this.contactJids.add(Jid.of(jid)); - } catch (final IllegalArgumentException ignored) { - } - } - - binding.cancelButton.setOnClickListener(mCancelButtonListener); - binding.saveButton.setOnClickListener(mSaveButtonListener); - - setSupportActionBar(binding.toolbar); - configureActionBar(getSupportActionBar()); - - if (savedInstanceState != null) { - mUseCameraHintShown.set(savedInstanceState.getBoolean("camera_hint_shown", false)); - } - } - - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - savedInstanceState.putBoolean("camera_hint_shown", mUseCameraHintShown.get()); - super.onSaveInstanceState(savedInstanceState); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.trust_keys, menu); - MenuItem scanQrCode = menu.findItem(R.id.action_scan_qr_code); - scanQrCode.setVisible((!ownKeysToTrust.isEmpty() || foreignActuallyHasKeys()) && isCameraFeatureAvailable()); - return super.onCreateOptionsMenu(menu); - } - - private void showCameraToast() { - mUseCameraHintToast = Toast.makeText(this, R.string.use_camera_icon_to_scan_barcode, Toast.LENGTH_LONG); - ActionBar actionBar = getSupportActionBar(); - mUseCameraHintToast.setGravity(Gravity.TOP | Gravity.END, 0, actionBar == null ? 0 : actionBar.getHeight()); - mUseCameraHintToast.show(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_scan_qr_code: - if (hasPendingKeyFetches()) { - Toast.makeText(this, R.string.please_wait_for_keys_to_be_fetched, Toast.LENGTH_SHORT).show(); - } else { - ScanActivity.scan(this); - //new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE")); - return true; - } - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void onStop() { - super.onStop(); - if (mUseCameraHintToast != null) { - mUseCameraHintToast.cancel(); - } - } - - @Override - protected void processFingerprintVerification(XmppUri uri) { - if (mConversation != null - && mAccount != null - && uri.hasFingerprints() - && mAccount.getAxolotlService().getCryptoTargets(mConversation).contains(uri.getJid())) { - boolean performedVerification = xmppConnectionService.verifyFingerprints(mAccount.getRoster().getContact(uri.getJid()), uri.getFingerprints()); - boolean keys = reloadFingerprints(); - if (performedVerification && !keys && !hasNoOtherTrustedKeys() && !hasPendingKeyFetches()) { - Toast.makeText(this, R.string.all_omemo_keys_have_been_verified, Toast.LENGTH_SHORT).show(); - finishOk(false); - return; - } else if (performedVerification) { - Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show(); - } - } else { - reloadFingerprints(); - Log.d(Config.LOGTAG, "xmpp uri was: " + uri.getJid() + " has Fingerprints: " + uri.hasFingerprints()); - Toast.makeText(this, R.string.barcode_does_not_contain_fingerprints_for_this_chat, Toast.LENGTH_SHORT).show(); - } - populateView(); - } - - private void populateView() { - setTitle(getString(R.string.trust_omemo_fingerprints)); - binding.ownKeysDetails.removeAllViews(); - binding.foreignKeys.removeAllViews(); - boolean hasOwnKeys = false; - boolean hasForeignKeys = false; - for (final String fingerprint : ownKeysToTrust.keySet()) { - hasOwnKeys = true; - addFingerprintRowWithListeners(binding.ownKeysDetails, mAccount, fingerprint, false, - FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)), false, false, - (buttonView, isChecked) -> { - ownKeysToTrust.put(fingerprint, isChecked); - // own fingerprints have no impact on locked status. - } - ); - } - - synchronized (this.foreignKeysToTrust) { - for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { - hasForeignKeys = true; - KeysCardBinding keysCardBinding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.keys_card, binding.foreignKeys, false); - final Jid jid = entry.getKey(); - keysCardBinding.foreignKeysTitle.setText(IrregularUnicodeDetector.style(this, jid)); - keysCardBinding.foreignKeysTitle.setOnClickListener(v -> switchToContactDetails(mAccount.getRoster().getContact(jid))); - final Map fingerprints = entry.getValue(); - for (final String fingerprint : fingerprints.keySet()) { - addFingerprintRowWithListeners(keysCardBinding.foreignKeysDetails, mAccount, fingerprint, false, - FingerprintStatus.createActive(fingerprints.get(fingerprint)), false, false, - (buttonView, isChecked) -> { - fingerprints.put(fingerprint, isChecked); - lockOrUnlockAsNeeded(); - } - ); - } - if (fingerprints.isEmpty()) { - keysCardBinding.noKeysToAccept.setVisibility(View.VISIBLE); - if (hasNoOtherTrustedKeys(jid)) { - if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) { - keysCardBinding.noKeysToAccept.setText(R.string.error_no_keys_to_trust_presence); - } else { - keysCardBinding.noKeysToAccept.setText(R.string.error_no_keys_to_trust_server_error); - } - } else { - keysCardBinding.noKeysToAccept.setText(getString(R.string.no_keys_just_confirm, mAccount.getRoster().getContact(jid).getDisplayName())); - } - } else { - keysCardBinding.noKeysToAccept.setVisibility(View.GONE); - } - binding.foreignKeys.addView(keysCardBinding.foreignKeysCard); - } - } - - if ((hasOwnKeys || foreignActuallyHasKeys()) && isCameraFeatureAvailable() && mUseCameraHintShown.compareAndSet(false, true)) { - showCameraToast(); - } - - binding.ownKeysTitle.setText(mAccount.getJid().asBareJid().toEscapedString()); - binding.ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE); - binding.foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE); - if (hasPendingKeyFetches()) { - setFetching(); - lock(); - } else { - if (!hasForeignKeys && hasNoOtherTrustedKeys()) { - binding.keyErrorMessageCard.setVisibility(View.VISIBLE); - boolean lastReportWasError = lastFetchReport == AxolotlService.FetchStatus.ERROR; - boolean errorFetchingBundle = mAccount.getAxolotlService().fetchMapHasErrors(contactJids); - boolean errorFetchingDeviceList = mAccount.getAxolotlService().hasErrorFetchingDeviceList(contactJids); - boolean anyWithoutMutualPresenceSubscription = anyWithoutMutualPresenceSubscription(contactJids); - if (errorFetchingDeviceList) { - binding.keyErrorMessage.setVisibility(View.VISIBLE); - binding.keyErrorMessage.setText(R.string.error_trustkey_device_list); - } else if (errorFetchingBundle || lastReportWasError) { - binding.keyErrorMessage.setVisibility(View.VISIBLE); - binding.keyErrorMessage.setText(R.string.error_trustkey_bundle); - } else { - binding.keyErrorMessage.setVisibility(View.GONE); - } - this.binding.keyErrorHintMutual.setVisibility(anyWithoutMutualPresenceSubscription ? View.VISIBLE : View.GONE); - Contact contact = mAccount.getRoster().getContact(contactJids.get(0)); - binding.keyErrorGeneral.setText(getString(R.string.error_trustkey_general, getString(R.string.app_name), contact.getDisplayName())); - binding.ownKeysDetails.removeAllViews(); - if (OmemoSetting.isAlways()) { - binding.disableButton.setVisibility(View.GONE); - } else { - binding.disableButton.setVisibility(View.VISIBLE); - binding.disableButton.setOnClickListener(this::disableEncryptionDialog); - } - binding.ownKeysCard.setVisibility(View.GONE); - binding.foreignKeys.removeAllViews(); - binding.foreignKeys.setVisibility(View.GONE); - } - lockOrUnlockAsNeeded(); - setDone(); - } - } - - private void disableEncryptionDialog(final View view) { - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(R.string.disable_encryption); - builder.setMessage(R.string.disable_encryption_message); - builder.setPositiveButton(R.string.disable_now, (dialog, which) -> { - mConversation.setNextEncryption(Message.ENCRYPTION_NONE); - xmppConnectionService.updateConversation(mConversation); - finishOk(true); - }); - builder.setNegativeButton(R.string.cancel, null); - builder.create().show(); - } - - private boolean anyWithoutMutualPresenceSubscription(List contactJids) { - for (Jid jid : contactJids) { - if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) { - return true; - } - } - return false; - } - - private boolean foreignActuallyHasKeys() { - synchronized (this.foreignKeysToTrust) { - for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { - if (!entry.getValue().isEmpty()) { - return true; - } - } - } - return false; - } - - private boolean reloadFingerprints() { - List acceptedTargets = mConversation == null ? new ArrayList<>() : mConversation.getAcceptedCryptoTargets(); - ownKeysToTrust.clear(); - if (this.mAccount == null) { - return false; - } - AxolotlService service = this.mAccount.getAxolotlService(); - Set ownKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided()); - for (final IdentityKey identityKey : ownKeysSet) { - final String fingerprint = CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()); - if (!ownKeysToTrust.containsKey(fingerprint)) { - ownKeysToTrust.put(fingerprint, false); - } - } - synchronized (this.foreignKeysToTrust) { - foreignKeysToTrust.clear(); - for (Jid jid : contactJids) { - Set foreignKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), jid); - if (hasNoOtherTrustedKeys(jid) && ownKeysSet.isEmpty()) { - foreignKeysSet.addAll(service.getKeysWithTrust(FingerprintStatus.createActive(false), jid)); - } - Map foreignFingerprints = new HashMap<>(); - for (final IdentityKey identityKey : foreignKeysSet) { - final String fingerprint = CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()); - if (!foreignFingerprints.containsKey(fingerprint)) { - foreignFingerprints.put(fingerprint, false); - } - } - if (!foreignFingerprints.isEmpty() || !acceptedTargets.contains(jid)) { - foreignKeysToTrust.put(jid, foreignFingerprints); - } - } - } - return ownKeysSet.size() + foreignKeysToTrust.size() > 0; - } - - public void onBackendConnected() { - Intent intent = getIntent(); - this.mAccount = extractAccount(intent); - if (this.mAccount != null && intent != null) { - String uuid = intent.getStringExtra("conversation"); - this.mConversation = xmppConnectionService.findConversationByUuid(uuid); - if (this.mPendingFingerprintVerificationUri != null) { - processFingerprintVerification(this.mPendingFingerprintVerificationUri); - this.mPendingFingerprintVerificationUri = null; - } else { - final boolean keysToTrust = reloadFingerprints(); - if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { - populateView(); - invalidateOptionsMenu(); - } else { - finishOk(false); - } - } - } - } - - private boolean hasNoOtherTrustedKeys() { - return mAccount == null || mAccount.getAxolotlService().anyTargetHasNoTrustedKeys(contactJids); - } - - private boolean hasNoOtherTrustedKeys(Jid contact) { - return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0; - } - - private boolean hasPendingKeyFetches() { - return mAccount != null && mAccount.getAxolotlService().hasPendingKeyFetches(contactJids); - } - - - @Override - public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) { - final boolean keysToTrust = reloadFingerprints(); - if (report != null) { - lastFetchReport = report; - runOnUiThread(() -> { - if (mUseCameraHintToast != null && !keysToTrust) { - mUseCameraHintToast.cancel(); - } - switch (report) { - case ERROR: - Toast.makeText(TrustKeysActivity.this, R.string.error_fetching_omemo_key, Toast.LENGTH_SHORT).show(); - break; - case SUCCESS_TRUSTED: - Toast.makeText(TrustKeysActivity.this, R.string.blindly_trusted_omemo_keys, Toast.LENGTH_LONG).show(); - break; - case SUCCESS_VERIFIED: - Toast.makeText(TrustKeysActivity.this, - Config.X509_VERIFICATION ? R.string.verified_omemo_key_with_certificate : R.string.all_omemo_keys_have_been_verified, - Toast.LENGTH_LONG).show(); - break; - } - }); - - } - if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { - refreshUi(); - } else { - runOnUiThread(() -> finishOk(false)); - - } - } - - private void finishOk(boolean disabled) { - Intent data = new Intent(); - data.putExtra("choice", getIntent().getIntExtra("choice", ConversationFragment.ATTACHMENT_CHOICE_INVALID)); - data.putExtra("disabled", disabled); - setResult(RESULT_OK, data); - finish(); - } - - private void commitTrusts() { - for (final String fingerprint : ownKeysToTrust.keySet()) { - mAccount.getAxolotlService().setFingerprintTrust( - fingerprint, - FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint))); - } - List acceptedTargets = mConversation == null ? new ArrayList<>() : mConversation.getAcceptedCryptoTargets(); - synchronized (this.foreignKeysToTrust) { - for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { - Jid jid = entry.getKey(); - Map value = entry.getValue(); - if (!acceptedTargets.contains(jid)) { - acceptedTargets.add(jid); - } - for (final String fingerprint : value.keySet()) { - mAccount.getAxolotlService().setFingerprintTrust( - fingerprint, - FingerprintStatus.createActive(value.get(fingerprint))); - } - } - } - if (mConversation != null && mConversation.getMode() == Conversation.MODE_MULTI) { - mConversation.setAcceptedCryptoTargets(acceptedTargets); - xmppConnectionService.updateConversation(mConversation); - } - } - - private void unlock() { - binding.saveButton.setEnabled(true); - } - - private void lock() { - binding.saveButton.setEnabled(false); - } - - private void lockOrUnlockAsNeeded() { - synchronized (this.foreignKeysToTrust) { - for (Jid jid : contactJids) { - Map fingerprints = foreignKeysToTrust.get(jid); - if (hasNoOtherTrustedKeys(jid) && (fingerprints == null || !fingerprints.containsValue(true))) { - lock(); - return; - } - } - } - unlock(); - - } - - private void setDone() { - binding.saveButton.setText(getString(R.string.done)); - } - - private void setFetching() { - binding.saveButton.setText(getString(R.string.fetching_keys)); - } + private final Map ownKeysToTrust = new HashMap<>(); + private final Map> foreignKeysToTrust = new HashMap<>(); + private final OnClickListener mCancelButtonListener = + v -> { + setResult(RESULT_CANCELED); + finish(); + }; + private List contactJids; + private Account mAccount; + private Conversation mConversation; + private final OnClickListener mSaveButtonListener = + v -> { + commitTrusts(); + finishOk(false); + }; + private final AtomicBoolean mUseCameraHintShown = new AtomicBoolean(false); + private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS; + private Toast mUseCameraHintToast = null; + private ActivityTrustKeysBinding binding; + + @Override + protected void refreshUiReal() { + invalidateOptionsMenu(); + populateView(); + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_trust_keys); + Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); + this.contactJids = new ArrayList<>(); + final var intent = getIntent(); + final String[] contacts = intent == null ? null : intent.getStringArrayExtra("contacts"); + for (final String jid : (contacts == null ? new String[0] : contacts)) { + try { + this.contactJids.add(Jid.of(jid)); + } catch (final IllegalArgumentException ignored) { + } + } + + binding.cancelButton.setOnClickListener(mCancelButtonListener); + binding.saveButton.setOnClickListener(mSaveButtonListener); + + setSupportActionBar(binding.toolbar); + configureActionBar(getSupportActionBar()); + + if (savedInstanceState != null) { + mUseCameraHintShown.set(savedInstanceState.getBoolean("camera_hint_shown", false)); + } + } + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + savedInstanceState.putBoolean("camera_hint_shown", mUseCameraHintShown.get()); + super.onSaveInstanceState(savedInstanceState); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.trust_keys, menu); + MenuItem scanQrCode = menu.findItem(R.id.action_scan_qr_code); + scanQrCode.setVisible( + (!ownKeysToTrust.isEmpty() || foreignActuallyHasKeys()) + && isCameraFeatureAvailable()); + return super.onCreateOptionsMenu(menu); + } + + private void showCameraToast() { + mUseCameraHintToast = + Toast.makeText(this, R.string.use_camera_icon_to_scan_barcode, Toast.LENGTH_LONG); + ActionBar actionBar = getSupportActionBar(); + mUseCameraHintToast.setGravity( + Gravity.TOP | Gravity.END, 0, actionBar == null ? 0 : actionBar.getHeight()); + mUseCameraHintToast.show(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_scan_qr_code: + if (hasPendingKeyFetches()) { + Toast.makeText( + this, + R.string.please_wait_for_keys_to_be_fetched, + Toast.LENGTH_SHORT) + .show(); + } else { + ScanActivity.scan(this); + // new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE")); + return true; + } + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onStop() { + super.onStop(); + if (mUseCameraHintToast != null) { + mUseCameraHintToast.cancel(); + } + } + + @Override + protected void processFingerprintVerification(XmppUri uri) { + if (mConversation != null + && mAccount != null + && uri.hasFingerprints() + && mAccount.getAxolotlService() + .getCryptoTargets(mConversation) + .contains(uri.getJid())) { + boolean performedVerification = + xmppConnectionService.verifyFingerprints( + mAccount.getRoster().getContact(uri.getJid()), uri.getFingerprints()); + boolean keys = reloadFingerprints(); + if (performedVerification + && !keys + && !hasNoOtherTrustedKeys() + && !hasPendingKeyFetches()) { + Toast.makeText(this, R.string.all_omemo_keys_have_been_verified, Toast.LENGTH_SHORT) + .show(); + finishOk(false); + return; + } else if (performedVerification) { + Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show(); + } + } else { + reloadFingerprints(); + Log.d( + Config.LOGTAG, + "xmpp uri was: " + + uri.getJid() + + " has Fingerprints: " + + uri.hasFingerprints()); + Toast.makeText( + this, + R.string.barcode_does_not_contain_fingerprints_for_this_chat, + Toast.LENGTH_SHORT) + .show(); + } + populateView(); + } + + private void populateView() { + setTitle(getString(R.string.trust_omemo_fingerprints)); + binding.ownKeysDetails.removeAllViews(); + binding.foreignKeys.removeAllViews(); + boolean hasOwnKeys = false; + boolean hasForeignKeys = false; + for (final String fingerprint : ownKeysToTrust.keySet()) { + hasOwnKeys = true; + addFingerprintRowWithListeners( + binding.ownKeysDetails, + mAccount, + fingerprint, + false, + FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)), + false, + false, + (buttonView, isChecked) -> { + ownKeysToTrust.put(fingerprint, isChecked); + // own fingerprints have no impact on locked status. + }); + } + + synchronized (this.foreignKeysToTrust) { + for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { + hasForeignKeys = true; + KeysCardBinding keysCardBinding = + DataBindingUtil.inflate( + getLayoutInflater(), + R.layout.keys_card, + binding.foreignKeys, + false); + final Jid jid = entry.getKey(); + keysCardBinding.foreignKeysTitle.setText(IrregularUnicodeDetector.style(this, jid)); + keysCardBinding.foreignKeysTitle.setOnClickListener( + v -> switchToContactDetails(mAccount.getRoster().getContact(jid))); + final Map fingerprints = entry.getValue(); + for (final String fingerprint : fingerprints.keySet()) { + addFingerprintRowWithListeners( + keysCardBinding.foreignKeysDetails, + mAccount, + fingerprint, + false, + FingerprintStatus.createActive(fingerprints.get(fingerprint)), + false, + false, + (buttonView, isChecked) -> { + fingerprints.put(fingerprint, isChecked); + lockOrUnlockAsNeeded(); + }); + } + if (fingerprints.isEmpty()) { + keysCardBinding.noKeysToAccept.setVisibility(View.VISIBLE); + if (hasNoOtherTrustedKeys(jid)) { + if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) { + keysCardBinding.noKeysToAccept.setText( + R.string.error_no_keys_to_trust_presence); + } else { + keysCardBinding.noKeysToAccept.setText( + R.string.error_no_keys_to_trust_server_error); + } + } else { + keysCardBinding.noKeysToAccept.setText( + getString( + R.string.no_keys_just_confirm, + mAccount.getRoster().getContact(jid).getDisplayName())); + } + } else { + keysCardBinding.noKeysToAccept.setVisibility(View.GONE); + } + binding.foreignKeys.addView(keysCardBinding.foreignKeysCard); + } + } + + if ((hasOwnKeys || foreignActuallyHasKeys()) + && isCameraFeatureAvailable() + && mUseCameraHintShown.compareAndSet(false, true)) { + showCameraToast(); + } + + binding.ownKeysTitle.setText(mAccount.getJid().asBareJid().toString()); + binding.ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE); + binding.foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE); + if (hasPendingKeyFetches()) { + setFetching(); + lock(); + } else { + if (!hasForeignKeys && hasNoOtherTrustedKeys()) { + binding.keyErrorMessageCard.setVisibility(View.VISIBLE); + boolean lastReportWasError = lastFetchReport == AxolotlService.FetchStatus.ERROR; + boolean errorFetchingBundle = + mAccount.getAxolotlService().fetchMapHasErrors(contactJids); + boolean errorFetchingDeviceList = + mAccount.getAxolotlService().hasErrorFetchingDeviceList(contactJids); + boolean anyWithoutMutualPresenceSubscription = + anyWithoutMutualPresenceSubscription(contactJids); + if (errorFetchingDeviceList) { + binding.keyErrorMessage.setVisibility(View.VISIBLE); + binding.keyErrorMessage.setText(R.string.error_trustkey_device_list); + } else if (errorFetchingBundle || lastReportWasError) { + binding.keyErrorMessage.setVisibility(View.VISIBLE); + binding.keyErrorMessage.setText(R.string.error_trustkey_bundle); + } else { + binding.keyErrorMessage.setVisibility(View.GONE); + } + this.binding.keyErrorHintMutual.setVisibility( + anyWithoutMutualPresenceSubscription ? View.VISIBLE : View.GONE); + Contact contact = mAccount.getRoster().getContact(contactJids.get(0)); + binding.keyErrorGeneral.setText( + getString( + R.string.error_trustkey_general, + getString(R.string.app_name), + contact.getDisplayName())); + binding.ownKeysDetails.removeAllViews(); + if (OmemoSetting.isAlways()) { + binding.disableButton.setVisibility(View.GONE); + } else { + binding.disableButton.setVisibility(View.VISIBLE); + binding.disableButton.setOnClickListener(this::disableEncryptionDialog); + } + binding.ownKeysCard.setVisibility(View.GONE); + binding.foreignKeys.removeAllViews(); + binding.foreignKeys.setVisibility(View.GONE); + } + lockOrUnlockAsNeeded(); + setDone(); + } + } + + private void disableEncryptionDialog(final View view) { + final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(R.string.disable_encryption); + builder.setMessage(R.string.disable_encryption_message); + builder.setPositiveButton( + R.string.disable_now, + (dialog, which) -> { + mConversation.setNextEncryption(Message.ENCRYPTION_NONE); + xmppConnectionService.updateConversation(mConversation); + finishOk(true); + }); + builder.setNegativeButton(R.string.cancel, null); + builder.create().show(); + } + + private boolean anyWithoutMutualPresenceSubscription(List contactJids) { + for (Jid jid : contactJids) { + if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) { + return true; + } + } + return false; + } + + private boolean foreignActuallyHasKeys() { + synchronized (this.foreignKeysToTrust) { + for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { + if (!entry.getValue().isEmpty()) { + return true; + } + } + } + return false; + } + + private boolean reloadFingerprints() { + List acceptedTargets = + mConversation == null + ? new ArrayList<>() + : mConversation.getAcceptedCryptoTargets(); + ownKeysToTrust.clear(); + if (this.mAccount == null) { + return false; + } + AxolotlService service = this.mAccount.getAxolotlService(); + Set ownKeysSet = + service.getKeysWithTrust(FingerprintStatus.createActiveUndecided()); + for (final IdentityKey identityKey : ownKeysSet) { + final String fingerprint = + CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()); + if (!ownKeysToTrust.containsKey(fingerprint)) { + ownKeysToTrust.put(fingerprint, false); + } + } + synchronized (this.foreignKeysToTrust) { + foreignKeysToTrust.clear(); + for (Jid jid : contactJids) { + Set foreignKeysSet = + service.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), jid); + if (hasNoOtherTrustedKeys(jid) && ownKeysSet.isEmpty()) { + foreignKeysSet.addAll( + service.getKeysWithTrust(FingerprintStatus.createActive(false), jid)); + } + Map foreignFingerprints = new HashMap<>(); + for (final IdentityKey identityKey : foreignKeysSet) { + final String fingerprint = + CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()); + if (!foreignFingerprints.containsKey(fingerprint)) { + foreignFingerprints.put(fingerprint, false); + } + } + if (!foreignFingerprints.isEmpty() || !acceptedTargets.contains(jid)) { + foreignKeysToTrust.put(jid, foreignFingerprints); + } + } + } + return ownKeysSet.size() + foreignKeysToTrust.size() > 0; + } + + public void onBackendConnected() { + Intent intent = getIntent(); + this.mAccount = extractAccount(intent); + if (this.mAccount != null && intent != null) { + String uuid = intent.getStringExtra("conversation"); + this.mConversation = xmppConnectionService.findConversationByUuid(uuid); + if (this.mPendingFingerprintVerificationUri != null) { + processFingerprintVerification(this.mPendingFingerprintVerificationUri); + this.mPendingFingerprintVerificationUri = null; + } else { + final boolean keysToTrust = reloadFingerprints(); + if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { + populateView(); + invalidateOptionsMenu(); + } else { + finishOk(false); + } + } + } + } + + private boolean hasNoOtherTrustedKeys() { + return mAccount == null + || mAccount.getAxolotlService().anyTargetHasNoTrustedKeys(contactJids); + } + + private boolean hasNoOtherTrustedKeys(Jid contact) { + return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0; + } + + private boolean hasPendingKeyFetches() { + return mAccount != null && mAccount.getAxolotlService().hasPendingKeyFetches(contactJids); + } + + @Override + public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) { + final boolean keysToTrust = reloadFingerprints(); + if (report != null) { + lastFetchReport = report; + runOnUiThread( + () -> { + if (mUseCameraHintToast != null && !keysToTrust) { + mUseCameraHintToast.cancel(); + } + switch (report) { + case ERROR: + Toast.makeText( + TrustKeysActivity.this, + R.string.error_fetching_omemo_key, + Toast.LENGTH_SHORT) + .show(); + break; + case SUCCESS_TRUSTED: + Toast.makeText( + TrustKeysActivity.this, + R.string.blindly_trusted_omemo_keys, + Toast.LENGTH_LONG) + .show(); + break; + case SUCCESS_VERIFIED: + Toast.makeText( + TrustKeysActivity.this, + Config.X509_VERIFICATION + ? R.string + .verified_omemo_key_with_certificate + : R.string + .all_omemo_keys_have_been_verified, + Toast.LENGTH_LONG) + .show(); + break; + } + }); + } + if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { + refreshUi(); + } else { + runOnUiThread(() -> finishOk(false)); + } + } + + private void finishOk(boolean disabled) { + Intent data = new Intent(); + data.putExtra( + "choice", + getIntent().getIntExtra("choice", ConversationFragment.ATTACHMENT_CHOICE_INVALID)); + data.putExtra("disabled", disabled); + setResult(RESULT_OK, data); + finish(); + } + + private void commitTrusts() { + for (final String fingerprint : ownKeysToTrust.keySet()) { + mAccount.getAxolotlService() + .setFingerprintTrust( + fingerprint, + FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint))); + } + List acceptedTargets = + mConversation == null + ? new ArrayList<>() + : mConversation.getAcceptedCryptoTargets(); + synchronized (this.foreignKeysToTrust) { + for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { + Jid jid = entry.getKey(); + Map value = entry.getValue(); + if (!acceptedTargets.contains(jid)) { + acceptedTargets.add(jid); + } + for (final String fingerprint : value.keySet()) { + mAccount.getAxolotlService() + .setFingerprintTrust( + fingerprint, + FingerprintStatus.createActive(value.get(fingerprint))); + } + } + } + if (mConversation != null && mConversation.getMode() == Conversation.MODE_MULTI) { + mConversation.setAcceptedCryptoTargets(acceptedTargets); + xmppConnectionService.updateConversation(mConversation); + } + } + + private void unlock() { + binding.saveButton.setEnabled(true); + } + + private void lock() { + binding.saveButton.setEnabled(false); + } + + private void lockOrUnlockAsNeeded() { + synchronized (this.foreignKeysToTrust) { + for (Jid jid : contactJids) { + Map fingerprints = foreignKeysToTrust.get(jid); + if (hasNoOtherTrustedKeys(jid) + && (fingerprints == null || !fingerprints.containsValue(true))) { + lock(); + return; + } + } + } + unlock(); + } + + private void setDone() { + binding.saveButton.setText(getString(R.string.done)); + } + + private void setFetching() { + binding.saveButton.setText(getString(R.string.fetching_keys)); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java index 55bf8b3adacd481fbbbf5a5ca6688ecae6b01ddd..d4c63412b77f2892debae48cc31d1cea3d1be4d4 100644 --- a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java @@ -9,14 +9,11 @@ import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.core.content.ContextCompat; import androidx.databinding.DataBindingUtil; - import com.google.common.base.Strings; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityUriHandlerBinding; @@ -27,18 +24,16 @@ import eu.siacs.conversations.utils.ProvisioningUtils; import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; - +import java.io.IOException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import okhttp3.Call; import okhttp3.Callback; import okhttp3.HttpUrl; import okhttp3.Request; import okhttp3.Response; -import java.io.IOException; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - public class UriHandlerActivity extends BaseActivity { public static final String ACTION_SCAN_QR_CODE = "scan_qr_code"; @@ -56,7 +51,8 @@ public class UriHandlerActivity extends BaseActivity { } public static void scan(final Activity activity, final boolean provisioning) { - if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) + == PackageManager.PERMISSION_GRANTED) { final Intent intent = new Intent(activity, UriHandlerActivity.class); intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE); if (provisioning) { @@ -128,7 +124,7 @@ public class UriHandlerActivity extends BaseActivity { final String preAuth = xmppUri.getParameter(XmppUri.PARAMETER_PRE_AUTH); final Jid jid = xmppUri.getJid(); if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) { - if (jid.getEscapedLocal() != null && accounts.contains(jid.asBareJid())) { + if (jid.getLocal() != null && accounts.contains(jid.asBareJid())) { showError(R.string.account_already_exists); return false; } @@ -172,13 +168,13 @@ public class UriHandlerActivity extends BaseActivity { final Class clazz = findShareViaAccountClass(); if (clazz != null) { intent = new Intent(this, clazz); - intent.putExtra("contact", jid.toEscapedString()); + intent.putExtra("contact", jid.toString()); intent.putExtra("body", body); } else { intent = new Intent(this, StartConversationActivity.class); intent.setAction(Intent.ACTION_VIEW); intent.setData(uri); - intent.putExtra("account", accounts.get(0).toEscapedString()); + intent.putExtra("account", accounts.get(0).toString()); } } else { intent = new Intent(this, ShareWithActivity.class); @@ -209,8 +205,8 @@ public class UriHandlerActivity extends BaseActivity { private void checkForLinkHeader(final HttpUrl url) { Log.d(Config.LOGTAG, "checking for link header on " + url); this.call = - HttpConnectionManager.okHttpClient(this).newCall( - new Request.Builder().url(url).head().build()); + HttpConnectionManager.okHttpClient(this) + .newCall(new Request.Builder().url(url).head().build()); this.call.enqueue( new Callback() { @Override @@ -252,7 +248,7 @@ public class UriHandlerActivity extends BaseActivity { } private void showErrorOnUiThread(@StringRes int error) { - runOnUiThread(()-> showError(error)); + runOnUiThread(() -> showError(error)); } private static Class findShareViaAccountClass() { @@ -350,4 +346,4 @@ public class UriHandlerActivity extends BaseActivity { final String trimmed = Strings.nullToEmpty(input).trim(); return trimmed.charAt(0) == '{' && trimmed.charAt(trimmed.length() - 1) == '}'; } -} \ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index fa304e810fb2f4cff824448cc0266ce4208d7292..8a3fc8a543f6fdd4f72a765029b2f89f8b8a9b15 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -733,8 +733,8 @@ public abstract class XmppActivity extends ActionBarActivity { public void switchToContactDetails(Contact contact, String messageFingerprint) { Intent intent = new Intent(this, ContactDetailsActivity.class); intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); - intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().asBareJid().toEscapedString()); - intent.putExtra("contact", contact.getJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().asBareJid().toString()); + intent.putExtra("contact", contact.getJid().toString()); intent.putExtra("fingerprint", messageFingerprint); startActivity(intent); } @@ -749,7 +749,7 @@ public abstract class XmppActivity extends ActionBarActivity { public void switchToAccount(Account account, boolean init, String fingerprint) { Intent intent = new Intent(this, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().asBareJid().toEscapedString()); + intent.putExtra("jid", account.getJid().asBareJid().toString()); intent.putExtra("init", init); if (init) { intent.setFlags( @@ -1141,7 +1141,7 @@ public abstract class XmppActivity extends ActionBarActivity { protected Account extractAccount(Intent intent) { final String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null; try { - return jid != null ? xmppConnectionService.findAccountByJid(Jid.ofEscaped(jid)) : null; + return jid != null ? xmppConnectionService.findAccountByJid(Jid.of(jid)) : null; } catch (IllegalArgumentException e) { return null; } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java index 88475c5800c2c7fc36658a29108b3b846db88e9c..b52280181c71bde8928b32aea05f55962b960c21 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -4,19 +4,14 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; - import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; - import com.google.android.material.color.MaterialColors; - -import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ItemAccountBinding; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.ui.util.AvatarWorkerTask; - import java.util.List; public class AccountAdapter extends ArrayAdapter { @@ -42,27 +37,42 @@ public class AccountAdapter extends ArrayAdapter { final Account account = getItem(position); final ViewHolder viewHolder; if (view == null) { - ItemAccountBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_account, parent, false); + ItemAccountBinding binding = + DataBindingUtil.inflate( + LayoutInflater.from(parent.getContext()), + R.layout.item_account, + parent, + false); view = binding.getRoot(); viewHolder = new ViewHolder(binding); view.setTag(viewHolder); } else { viewHolder = (ViewHolder) view.getTag(); } - viewHolder.binding.accountJid.setText(account.getJid().asBareJid().toEscapedString()); + viewHolder.binding.accountJid.setText(account.getJid().asBareJid().toString()); AvatarWorkerTask.loadAvatar(account, viewHolder.binding.accountImage, R.dimen.avatar); - viewHolder.binding.accountStatus.setText(getContext().getString(account.getStatus().getReadableId())); + viewHolder.binding.accountStatus.setText( + getContext().getString(account.getStatus().getReadableId())); switch (account.getStatus()) { case ONLINE: - viewHolder.binding.accountStatus.setTextColor(MaterialColors.getColor(viewHolder.binding.accountStatus, com.google.android.material.R.attr.colorPrimary)); + viewHolder.binding.accountStatus.setTextColor( + MaterialColors.getColor( + viewHolder.binding.accountStatus, + com.google.android.material.R.attr.colorPrimary)); break; case DISABLED: case LOGGED_OUT: case CONNECTING: - viewHolder.binding.accountStatus.setTextColor(MaterialColors.getColor(viewHolder.binding.accountStatus, com.google.android.material.R.attr.colorOnSurfaceVariant)); + viewHolder.binding.accountStatus.setTextColor( + MaterialColors.getColor( + viewHolder.binding.accountStatus, + com.google.android.material.R.attr.colorOnSurfaceVariant)); break; default: - viewHolder.binding.accountStatus.setTextColor(MaterialColors.getColor(viewHolder.binding.accountStatus, com.google.android.material.R.attr.colorError)); + viewHolder.binding.accountStatus.setTextColor( + MaterialColors.getColor( + viewHolder.binding.accountStatus, + com.google.android.material.R.attr.colorError)); break; } final boolean isDisabled = (account.getStatus() == Account.State.DISABLED); @@ -73,11 +83,12 @@ public class AccountAdapter extends ArrayAdapter { } else { viewHolder.binding.tglAccountStatus.setVisibility(View.GONE); } - viewHolder.binding.tglAccountStatus.setOnCheckedChangeListener((compoundButton, b) -> { - if (b == isDisabled && activity instanceof OnTglAccountState) { - ((OnTglAccountState) activity).onClickTglAccountState(account, b); - } - }); + viewHolder.binding.tglAccountStatus.setOnCheckedChangeListener( + (compoundButton, b) -> { + if (b == isDisabled && activity instanceof OnTglAccountState) { + ((OnTglAccountState) activity).onClickTglAccountState(account, b); + } + }); return view; } @@ -89,10 +100,7 @@ public class AccountAdapter extends ArrayAdapter { } } - - public interface OnTglAccountState { void onClickTglAccountState(Account account, boolean state); } - } 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 b085cb964430dcb3c2a6b86231dbc02a7d3e096a..88f7636bfa612fecfe44661104fd717af37896e6 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java @@ -3,14 +3,10 @@ package eu.siacs.conversations.ui.adapter; import android.content.Context; import android.widget.ArrayAdapter; import android.widget.Filter; - import androidx.annotation.NonNull; - import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; - import eu.siacs.conversations.Config; - import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -22,61 +18,66 @@ public class KnownHostsAdapter extends ArrayAdapter { private static final Pattern E164_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$"); private List domains; - private final Filter domainFilter = new Filter() { + private final Filter domainFilter = + new Filter() { - @Override - protected FilterResults performFiltering(final CharSequence constraint) { - final ImmutableList.Builder builder = new ImmutableList.Builder<>(); - final String[] split = constraint == null ? new String[0] : constraint.toString().split("@"); - if (split.length == 1) { - final String local = split[0].toLowerCase(Locale.ENGLISH); - if (Config.QUICKSY_DOMAIN != null && E164_PATTERN.matcher(local).matches()) { - builder.add(local + '@' + Config.QUICKSY_DOMAIN.toEscapedString()); - } else { - for (String domain : domains) { - builder.add(local + '@' + domain); - } - } - } else if (split.length == 2) { - final String localPart = split[0].toLowerCase(Locale.ENGLISH); - final String domainPart = split[1].toLowerCase(Locale.ENGLISH); - if (domains.contains(domainPart)) { - return new FilterResults(); - } - for (final String domain : domains) { - if (domain.contains(domainPart)) { - builder.add(localPart + "@" + domain); + @Override + protected FilterResults performFiltering(final CharSequence constraint) { + final ImmutableList.Builder builder = new ImmutableList.Builder<>(); + final String[] split = + constraint == null ? new String[0] : constraint.toString().split("@"); + if (split.length == 1) { + 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()); + } else { + for (String domain : domains) { + builder.add(local + '@' + domain); + } + } + } else if (split.length == 2) { + final String localPart = split[0].toLowerCase(Locale.ENGLISH); + final String domainPart = split[1].toLowerCase(Locale.ENGLISH); + if (domains.contains(domainPart)) { + return new FilterResults(); + } + for (final String domain : domains) { + if (domain.contains(domainPart)) { + builder.add(localPart + "@" + domain); + } + } + } else { + return new FilterResults(); } + final var suggestions = builder.build(); + final FilterResults filterResults = new FilterResults(); + filterResults.values = suggestions; + filterResults.count = suggestions.size(); + return filterResults; } - } else { - return new FilterResults(); - } - final var suggestions = builder.build(); - final FilterResults filterResults = new FilterResults(); - filterResults.values = suggestions; - filterResults.count = suggestions.size(); - return filterResults; - } - @Override - protected void publishResults(final CharSequence constraint, final FilterResults results) { - final ImmutableList.Builder suggestions = new ImmutableList.Builder<>(); - if (results.values instanceof Collection collection) { - for(final Object item : collection) { - if (item instanceof String string) { - suggestions.add(string); + @Override + protected void publishResults( + final CharSequence constraint, final FilterResults results) { + final ImmutableList.Builder suggestions = new ImmutableList.Builder<>(); + if (results.values instanceof Collection collection) { + for (final Object item : collection) { + if (item instanceof String string) { + suggestions.add(string); + } + } } + clear(); + addAll(suggestions.build()); + notifyDataSetChanged(); } - } - clear(); - addAll(suggestions.build()); - notifyDataSetChanged(); - } - }; + }; - public KnownHostsAdapter(final Context context, final int viewResourceId, final Collection knownHosts) { + public KnownHostsAdapter( + final Context context, final int viewResourceId, final Collection knownHosts) { super(context, viewResourceId, new ArrayList<>()); - domains = Ordering.natural().sortedCopy(knownHosts); + domains = Ordering.natural().sortedCopy(knownHosts); } public KnownHostsAdapter(final Context context, final int viewResourceId) { diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/UpSettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/UpSettingsFragment.java index 771acbbe1daa949a52d13f0e79bf27f3787562a9..2b279c905440b28d4a67c7b4590121758ae99238 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/UpSettingsFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/UpSettingsFragment.java @@ -2,20 +2,16 @@ package eu.siacs.conversations.ui.fragment.settings; import android.os.Bundle; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.EditTextPreference; import androidx.preference.ListPreference; - import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; - import eu.siacs.conversations.R; import eu.siacs.conversations.receiver.UnifiedPushDistributor; import eu.siacs.conversations.xmpp.Jid; - import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; @@ -31,29 +27,38 @@ public class UpSettingsFragment extends XmppPreferenceFragment { @Override public void onBackendConnected() { final ListPreference upAccounts = findPreference(UnifiedPushDistributor.PREFERENCE_ACCOUNT); - final EditTextPreference pushServer = findPreference(UnifiedPushDistributor.PREFERENCE_PUSH_SERVER); + final EditTextPreference pushServer = + findPreference(UnifiedPushDistributor.PREFERENCE_PUSH_SERVER); if (upAccounts == null || pushServer == null) { throw new IllegalStateException(); } - pushServer.setOnPreferenceChangeListener((preference, newValue) -> { - if (newValue instanceof String string) { - if (Strings.isNullOrEmpty(string) || isJidInvalid(string) || isHttpUri(string)) { - Toast.makeText(requireActivity(),R.string.invalid_jid,Toast.LENGTH_LONG).show(); - return false; - } else { - return true; - } - } else { - Toast.makeText(requireActivity(),R.string.invalid_jid,Toast.LENGTH_LONG).show(); - return false; - } - }); + pushServer.setOnPreferenceChangeListener( + (preference, newValue) -> { + if (newValue instanceof String string) { + if (Strings.isNullOrEmpty(string) + || isJidInvalid(string) + || isHttpUri(string)) { + Toast.makeText( + requireActivity(), + R.string.invalid_jid, + Toast.LENGTH_LONG) + .show(); + return false; + } else { + return true; + } + } else { + Toast.makeText(requireActivity(), R.string.invalid_jid, Toast.LENGTH_LONG) + .show(); + return false; + } + }); reconfigureUpAccountPreference(upAccounts); } private static boolean isJidInvalid(final String input) { try { - final var jid = Jid.ofEscaped(input); + final var jid = Jid.ofUserInput(input); return !jid.isBareJid(); } catch (final IllegalArgumentException e) { return true; @@ -67,16 +72,15 @@ public class UpSettingsFragment extends XmppPreferenceFragment { } catch (final URISyntaxException e) { return false; } - return Arrays.asList("http","https").contains(uri.getScheme()); + return Arrays.asList("http", "https").contains(uri.getScheme()); } - private void reconfigureUpAccountPreference(final ListPreference listPreference) { final List accounts = ImmutableList.copyOf( Lists.transform( requireService().getAccounts(), - a -> a.getJid().asBareJid().toEscapedString())); + a -> a.getJid().asBareJid().toString())); final ImmutableList.Builder entries = new ImmutableList.Builder<>(); final ImmutableList.Builder entryValues = new ImmutableList.Builder<>(); entries.add(getString(R.string.no_account_deactivated)); diff --git a/src/main/java/eu/siacs/conversations/utils/AccountUtils.java b/src/main/java/eu/siacs/conversations/utils/AccountUtils.java index 4b2c2957f62aaa1e1a2e6e814312a4bb7b136843..bcfffe8c18b294cd03725e98452c584e85e1d45c 100644 --- a/src/main/java/eu/siacs/conversations/utils/AccountUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/AccountUtils.java @@ -5,20 +5,16 @@ import android.content.Intent; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; - import com.google.common.primitives.Bytes; import com.google.common.primitives.Longs; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.XmppActivity; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; public class AccountUtils { @@ -50,9 +46,7 @@ public class AccountUtils { public static UUID createUuid4(long mostSigBits, long leastSigBits) { final byte[] bytes = - Bytes.concat( - Longs.toByteArray(mostSigBits), - Longs.toByteArray(leastSigBits)); + Bytes.concat(Longs.toByteArray(mostSigBits), Longs.toByteArray(leastSigBits)); bytes[6] &= 0x0f; /* clear version */ bytes[6] |= 0x40; /* set to version 4 */ bytes[8] &= 0x3f; /* clear variant */ @@ -65,7 +59,7 @@ public class AccountUtils { final ArrayList accounts = new ArrayList<>(); for (final Account account : service.getAccounts()) { if (account.isEnabled()) { - accounts.add(account.getJid().asBareJid().toEscapedString()); + accounts.add(account.getJid().asBareJid().toString()); } } return accounts; diff --git a/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java b/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java index 3b536c27a89c5f874ae7181ea9cd20b10dc860fb..21ffeadda61feb8703ec87dfd96d5b90a54a25a1 100644 --- a/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java +++ b/src/main/java/eu/siacs/conversations/utils/BackupFileHeader.java @@ -1,13 +1,11 @@ package eu.siacs.conversations.utils; import androidx.annotation.NonNull; - +import eu.siacs.conversations.xmpp.Jid; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import eu.siacs.conversations.xmpp.Jid; - public class BackupFileHeader { private static final int VERSION = 2; @@ -18,17 +16,22 @@ public class BackupFileHeader { private final byte[] iv; private final byte[] salt; - @NonNull @Override public String toString() { - return "BackupFileHeader{" + - "app='" + app + '\'' + - ", jid=" + jid + - ", timestamp=" + timestamp + - ", iv=" + CryptoHelper.bytesToHex(iv) + - ", salt=" + CryptoHelper.bytesToHex(salt) + - '}'; + return "BackupFileHeader{" + + "app='" + + app + + '\'' + + ", jid=" + + jid + + ", timestamp=" + + timestamp + + ", iv=" + + CryptoHelper.bytesToHex(iv) + + ", salt=" + + CryptoHelper.bytesToHex(salt) + + '}'; } public BackupFileHeader(String app, Jid jid, long timestamp, byte[] iv, byte[] salt) { @@ -42,7 +45,7 @@ public class BackupFileHeader { public void write(DataOutputStream dataOutputStream) throws IOException { dataOutputStream.writeInt(VERSION); dataOutputStream.writeUTF(app); - dataOutputStream.writeUTF(jid.asBareJid().toEscapedString()); + dataOutputStream.writeUTF(jid.asBareJid().toString()); dataOutputStream.writeLong(timestamp); dataOutputStream.write(iv); dataOutputStream.write(salt); @@ -61,10 +64,13 @@ public class BackupFileHeader { throw new OutdatedBackupFileVersion(); } if (version != VERSION) { - throw new IllegalArgumentException("Backup File version was " + version + " but app only supports version " + VERSION); + throw new IllegalArgumentException( + "Backup File version was " + + version + + " but app only supports version " + + VERSION); } return new BackupFileHeader(app, Jid.of(jid), timestamp, iv, salt); - } public byte[] getSalt() { @@ -87,7 +93,5 @@ public class BackupFileHeader { return timestamp; } - public static class OutdatedBackupFileVersion extends RuntimeException { - - } + public static class OutdatedBackupFileVersion extends RuntimeException {} } diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java index 34f5caccfed409fb2d6cb52d154e830ba98857bc..6d6232b0b8c7932fb634e9ffc7760d72e42df3df 100644 --- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java @@ -5,14 +5,12 @@ import static eu.siacs.conversations.utils.Random.SECURE_RANDOM; import android.os.Bundle; import android.util.Base64; import android.util.Pair; - import androidx.annotation.StringRes; - -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x500.style.BCStyle; -import org.bouncycastle.asn1.x500.style.IETFUtils; -import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; - +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.xmpp.Jid; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -28,22 +26,22 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.regex.Pattern; - -import eu.siacs.conversations.Config; -import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Message; -import eu.siacs.conversations.xmpp.Jid; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; public final class CryptoHelper { - public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"); - final public static byte[] ONE = new byte[]{0, 0, 0, 1}; - private static final char[] CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz123456789+-/#$!?".toCharArray(); + public static final Pattern UUID_PATTERN = + Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"); + public static final byte[] ONE = new byte[] {0, 0, 0, 1}; + private static final char[] CHARS = + "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz123456789+-/#$!?".toCharArray(); private static final int PW_LENGTH = 12; private static final char[] VOWELS = "aeiou".toCharArray(); private static final char[] CONSONANTS = "bcfghjklmnpqrstvwxyz".toCharArray(); - private final static char[] hexArray = "0123456789abcdef".toCharArray(); + private static final char[] hexArray = "0123456789abcdef".toCharArray(); public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; @@ -68,7 +66,10 @@ public final class CryptoHelper { char[] output = new char[rand * 2 + (5 - rand)]; boolean vowel = SECURE_RANDOM.nextBoolean(); for (int i = 0; i < output.length; ++i) { - output[i] = vowel ? VOWELS[SECURE_RANDOM.nextInt(VOWELS.length)] : CONSONANTS[SECURE_RANDOM.nextInt(CONSONANTS.length)]; + output[i] = + vowel + ? VOWELS[SECURE_RANDOM.nextInt(VOWELS.length)] + : CONSONANTS[SECURE_RANDOM.nextInt(CONSONANTS.length)]; vowel = !vowel; } return String.valueOf(output); @@ -78,8 +79,10 @@ public final class CryptoHelper { int len = hexString.length(); byte[] array = new byte[len / 2]; for (int i = 0; i < len; i += 2) { - array[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character - .digit(hexString.charAt(i + 1), 16)); + array[i / 2] = + (byte) + ((Character.digit(hexString.charAt(i), 16) << 4) + + Character.digit(hexString.charAt(i + 1), 16)); } return array; } @@ -95,9 +98,7 @@ public final class CryptoHelper { return result; } - /** - * Escapes usernames or passwords for SASL. - */ + /** Escapes usernames or passwords for SASL. */ public static String saslEscape(final String s) { final StringBuilder sb = new StringBuilder((int) (s.length() * 1.1)); for (int i = 0; i < s.length(); i++) { @@ -149,7 +150,8 @@ public final class CryptoHelper { } public static String[] getOrderedCipherSuites(final String[] platformSupportedCipherSuites) { - final Collection cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS)); + final Collection cipherSuites = + new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS)); final List platformCiphers = Arrays.asList(platformSupportedCipherSuites); cipherSuites.retainAll(platformCiphers); cipherSuites.addAll(platformCiphers); @@ -172,7 +174,10 @@ public final class CryptoHelper { } } - public static Pair extractJidAndName(X509Certificate certificate) throws CertificateEncodingException, IllegalArgumentException, CertificateParsingException { + public static Pair extractJidAndName(X509Certificate certificate) + throws CertificateEncodingException, + IllegalArgumentException, + CertificateParsingException { Collection> alternativeNames = certificate.getSubjectAlternativeNames(); List emails = new ArrayList<>(); if (alternativeNames != null) { @@ -185,9 +190,15 @@ public final class CryptoHelper { } X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); if (emails.size() == 0 && x500name.getRDNs(BCStyle.EmailAddress).length > 0) { - emails.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue())); + emails.add( + IETFUtils.valueToString( + x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue())); } - String name = x500name.getRDNs(BCStyle.CN).length > 0 ? IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue()) : null; + String name = + x500name.getRDNs(BCStyle.CN).length > 0 + ? IETFUtils.valueToString( + x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue()) + : null; if (emails.size() >= 1) { return new Pair<>(Jid.of(emails.get(0)), name); } else if (name != null) { @@ -209,26 +220,33 @@ public final class CryptoHelper { JcaX509CertificateHolder holder = new JcaX509CertificateHolder(certificate); X500Name subject = holder.getSubject(); try { - information.putString("subject_cn", subject.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); + information.putString( + "subject_cn", + subject.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); } catch (Exception e) { - //ignored + // ignored } try { - information.putString("subject_o", subject.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); + information.putString( + "subject_o", + subject.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); } catch (Exception e) { - //ignored + // ignored } X500Name issuer = holder.getIssuer(); try { - information.putString("issuer_cn", issuer.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); + information.putString( + "issuer_cn", + issuer.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); } catch (Exception e) { - //ignored + // ignored } try { - information.putString("issuer_o", issuer.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); + information.putString( + "issuer_o", issuer.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); } catch (Exception e) { - //ignored + // ignored } try { information.putString("sha1", getFingerprintCert(certificate.getEncoded())); @@ -248,7 +266,7 @@ public final class CryptoHelper { } public static String getFingerprint(Jid jid, String androidId) { - return getFingerprint(jid.toEscapedString() + "\00" + androidId); + return getFingerprint(jid.toString() + "\00" + androidId); } public static String getAccountFingerprint(Account account, String androidId) { @@ -268,8 +286,9 @@ public final class CryptoHelper { return switch (encryption) { case Message.ENCRYPTION_OTR -> R.string.encryption_choice_otr; case Message.ENCRYPTION_AXOLOTL, - Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, - Message.ENCRYPTION_AXOLOTL_FAILED -> R.string.encryption_choice_omemo; + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, + Message.ENCRYPTION_AXOLOTL_FAILED -> + R.string.encryption_choice_omemo; case Message.ENCRYPTION_PGP -> R.string.encryption_choice_pgp; default -> R.string.encryption_choice_unencrypted; }; @@ -280,6 +299,8 @@ public final class CryptoHelper { return false; } final String u = url.toLowerCase(); - return !u.contains(" ") && (u.startsWith("https://") || u.startsWith("http://") || u.startsWith("p1s3://")) && u.endsWith(".pgp"); + return !u.contains(" ") + && (u.startsWith("https://") || u.startsWith("http://") || u.startsWith("p1s3://")) + && u.endsWith(".pgp"); } } diff --git a/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java b/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java index 5cc0932493a9e7c65b7d35cdb083e4adeda8ba3b..9dcdbb15e11cfb90154392a20409d814d61de376 100644 --- a/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java +++ b/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java @@ -37,11 +37,9 @@ import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; import android.util.LruCache; - import androidx.annotation.ColorInt; - import com.google.android.material.color.MaterialColors; - +import eu.siacs.conversations.xmpp.Jid; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -54,224 +52,234 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import eu.siacs.conversations.R; -import eu.siacs.conversations.xmpp.Jid; - public class IrregularUnicodeDetector { - private static final Map NORMALIZATION_MAP; - private static final LruCache CACHE = new LruCache<>(4096); - private static final List AMBIGUOUS_CYRILLIC = Arrays.asList("а","г","е","ѕ","і","ј","ķ","ԛ","о","р","с","у","х"); - - static { - Map temp = new HashMap<>(); - temp.put(Character.UnicodeBlock.LATIN_1_SUPPLEMENT, Character.UnicodeBlock.BASIC_LATIN); - NORMALIZATION_MAP = Collections.unmodifiableMap(temp); - } + private static final Map NORMALIZATION_MAP; + private static final LruCache CACHE = new LruCache<>(4096); + private static final List AMBIGUOUS_CYRILLIC = + Arrays.asList("а", "г", "е", "ѕ", "і", "ј", "ķ", "ԛ", "о", "р", "с", "у", "х"); - private static Character.UnicodeBlock normalize(Character.UnicodeBlock in) { - if (NORMALIZATION_MAP.containsKey(in)) { - return NORMALIZATION_MAP.get(in); - } else { - return in; - } - } + static { + Map temp = new HashMap<>(); + temp.put(Character.UnicodeBlock.LATIN_1_SUPPLEMENT, Character.UnicodeBlock.BASIC_LATIN); + NORMALIZATION_MAP = Collections.unmodifiableMap(temp); + } - public static Spannable style(final Context context, Jid jid) { - return style(jid, MaterialColors.getColor(context, com.google.android.material.R.attr.colorError,"colorError not found")); - } + private static Character.UnicodeBlock normalize(Character.UnicodeBlock in) { + if (NORMALIZATION_MAP.containsKey(in)) { + return NORMALIZATION_MAP.get(in); + } else { + return in; + } + } - private static Spannable style(Jid jid, @ColorInt int color) { - PatternTuple patternTuple = find(jid); - SpannableStringBuilder builder = new SpannableStringBuilder(); - if (jid.getEscapedLocal() != null && patternTuple.local != null) { - SpannableString local = new SpannableString(jid.getEscapedLocal()); - colorize(local, patternTuple.local, color); - builder.append(local); - builder.append('@'); - } - if (jid.getDomain() != null) { - String[] labels = jid.getDomain().toEscapedString().split("\\."); - for (int i = 0; i < labels.length; ++i) { - SpannableString spannableString = new SpannableString(labels[i]); - colorize(spannableString, patternTuple.domain.get(i), color); - if (i != 0) { - builder.append('.'); - } - builder.append(spannableString); - } - } - if (builder.length() != 0 && jid.getResource() != null) { - builder.append('/'); - builder.append(jid.getResource()); - } - return builder; - } + public static Spannable style(final Context context, Jid jid) { + return style( + jid, + MaterialColors.getColor( + context, + com.google.android.material.R.attr.colorError, + "colorError not found")); + } - private static void colorize(SpannableString spannableString, Pattern pattern, @ColorInt int color) { - Matcher matcher = pattern.matcher(spannableString); - while (matcher.find()) { - if (matcher.start() < matcher.end()) { - spannableString.setSpan(new ForegroundColorSpan(color), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - } + private static Spannable style(Jid jid, @ColorInt int color) { + PatternTuple patternTuple = find(jid); + SpannableStringBuilder builder = new SpannableStringBuilder(); + if (jid.getLocal() != null && patternTuple.local != null) { + SpannableString local = new SpannableString(jid.getLocal()); + colorize(local, patternTuple.local, color); + builder.append(local); + builder.append('@'); + } + if (jid.getDomain() != null) { + String[] labels = jid.getDomain().toString().split("\\."); + for (int i = 0; i < labels.length; ++i) { + SpannableString spannableString = new SpannableString(labels[i]); + colorize(spannableString, patternTuple.domain.get(i), color); + if (i != 0) { + builder.append('.'); + } + builder.append(spannableString); + } + } + if (builder.length() != 0 && jid.getResource() != null) { + builder.append('/'); + builder.append(jid.getResource()); + } + return builder; + } - private static Map> mapCompat(String word) { - Map> map = new HashMap<>(); - final int length = word.length(); - for (int offset = 0; offset < length; ) { - final int codePoint = word.codePointAt(offset); - offset += Character.charCount(codePoint); - if (!Character.isLetter(codePoint)) { - continue; - } - Character.UnicodeBlock block = normalize(Character.UnicodeBlock.of(codePoint)); - List codePoints; - if (map.containsKey(block)) { - codePoints = map.get(block); - } else { - codePoints = new ArrayList<>(); - map.put(block, codePoints); - } - codePoints.add(String.copyValueOf(Character.toChars(codePoint))); - } - return map; - } + private static void colorize( + SpannableString spannableString, Pattern pattern, @ColorInt int color) { + Matcher matcher = pattern.matcher(spannableString); + while (matcher.find()) { + if (matcher.start() < matcher.end()) { + spannableString.setSpan( + new ForegroundColorSpan(color), + matcher.start(), + matcher.end(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } - @TargetApi(Build.VERSION_CODES.N) - private static Map> map(String word) { - Map> map = new HashMap<>(); - final int length = word.length(); - for (int offset = 0; offset < length; ) { - final int codePoint = word.codePointAt(offset); - Character.UnicodeScript script = Character.UnicodeScript.of(codePoint); - if (script != Character.UnicodeScript.COMMON) { - List codePoints; - if (map.containsKey(script)) { - codePoints = map.get(script); - } else { - codePoints = new ArrayList<>(); - map.put(script, codePoints); - } - codePoints.add(String.copyValueOf(Character.toChars(codePoint))); - } - offset += Character.charCount(codePoint); - } - return map; - } + private static Map> mapCompat(String word) { + Map> map = new HashMap<>(); + final int length = word.length(); + for (int offset = 0; offset < length; ) { + final int codePoint = word.codePointAt(offset); + offset += Character.charCount(codePoint); + if (!Character.isLetter(codePoint)) { + continue; + } + Character.UnicodeBlock block = normalize(Character.UnicodeBlock.of(codePoint)); + List codePoints; + if (map.containsKey(block)) { + codePoints = map.get(block); + } else { + codePoints = new ArrayList<>(); + map.put(block, codePoints); + } + codePoints.add(String.copyValueOf(Character.toChars(codePoint))); + } + return map; + } - private static Set eliminateFirstAndGetCodePointsCompat(Map> map) { - return eliminateFirstAndGetCodePoints(map, Character.UnicodeBlock.BASIC_LATIN); - } + @TargetApi(Build.VERSION_CODES.N) + private static Map> map(String word) { + Map> map = new HashMap<>(); + final int length = word.length(); + for (int offset = 0; offset < length; ) { + final int codePoint = word.codePointAt(offset); + Character.UnicodeScript script = Character.UnicodeScript.of(codePoint); + if (script != Character.UnicodeScript.COMMON) { + List codePoints; + if (map.containsKey(script)) { + codePoints = map.get(script); + } else { + codePoints = new ArrayList<>(); + map.put(script, codePoints); + } + codePoints.add(String.copyValueOf(Character.toChars(codePoint))); + } + offset += Character.charCount(codePoint); + } + return map; + } - @TargetApi(Build.VERSION_CODES.N) - private static Set eliminateFirstAndGetCodePoints(Map> map) { - return eliminateFirstAndGetCodePoints(map, Character.UnicodeScript.COMMON); - } + private static Set eliminateFirstAndGetCodePointsCompat( + Map> map) { + return eliminateFirstAndGetCodePoints(map, Character.UnicodeBlock.BASIC_LATIN); + } - private static Set eliminateFirstAndGetCodePoints(Map> map, T defaultPick) { - T pick = defaultPick; - int size = 0; - for (Map.Entry> entry : map.entrySet()) { - if (entry.getValue().size() > size) { - size = entry.getValue().size(); - pick = entry.getKey(); - } - } - map.remove(pick); - Set all = new HashSet<>(); - for (List codePoints : map.values()) { - all.addAll(codePoints); - } - return all; - } + @TargetApi(Build.VERSION_CODES.N) + private static Set eliminateFirstAndGetCodePoints( + Map> map) { + return eliminateFirstAndGetCodePoints(map, Character.UnicodeScript.COMMON); + } - private static Set findIrregularCodePoints(String word) { - Set codePoints; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - final Map> map = mapCompat(word); - final Set set = asSet(map); - if (containsOnlyAmbiguousCyrillic(set)) { - return set; - } - codePoints = eliminateFirstAndGetCodePointsCompat(map); - } else { - final Map> map = map(word); - final Set set = asSet(map); - if (containsOnlyAmbiguousCyrillic(set)) { - return set; - } - codePoints = eliminateFirstAndGetCodePoints(map); - } - return codePoints; - } + private static Set eliminateFirstAndGetCodePoints( + Map> map, T defaultPick) { + T pick = defaultPick; + int size = 0; + for (Map.Entry> entry : map.entrySet()) { + if (entry.getValue().size() > size) { + size = entry.getValue().size(); + pick = entry.getKey(); + } + } + map.remove(pick); + Set all = new HashSet<>(); + for (List codePoints : map.values()) { + all.addAll(codePoints); + } + return all; + } - private static Set asSet(Map> map) { - final Set flat = new HashSet<>(); - for(List value : map.values()) { - flat.addAll(value); - } - return flat; - } + private static Set findIrregularCodePoints(String word) { + Set codePoints; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + final Map> map = mapCompat(word); + final Set set = asSet(map); + if (containsOnlyAmbiguousCyrillic(set)) { + return set; + } + codePoints = eliminateFirstAndGetCodePointsCompat(map); + } else { + final Map> map = map(word); + final Set set = asSet(map); + if (containsOnlyAmbiguousCyrillic(set)) { + return set; + } + codePoints = eliminateFirstAndGetCodePoints(map); + } + return codePoints; + } + private static Set asSet(Map> map) { + final Set flat = new HashSet<>(); + for (List value : map.values()) { + flat.addAll(value); + } + return flat; + } - private static boolean containsOnlyAmbiguousCyrillic(Collection codePoints) { - for (String codePoint : codePoints) { - if (!AMBIGUOUS_CYRILLIC.contains(codePoint)) { - return false; - } - } - return true; - } + private static boolean containsOnlyAmbiguousCyrillic(Collection codePoints) { + for (String codePoint : codePoints) { + if (!AMBIGUOUS_CYRILLIC.contains(codePoint)) { + return false; + } + } + return true; + } - private static PatternTuple find(Jid jid) { - synchronized (CACHE) { - PatternTuple pattern = CACHE.get(jid); - if (pattern != null) { - return pattern; - } + private static PatternTuple find(Jid jid) { + synchronized (CACHE) { + PatternTuple pattern = CACHE.get(jid); + if (pattern != null) { + return pattern; + } pattern = PatternTuple.of(jid); - CACHE.put(jid, pattern); - return pattern; - } - } + CACHE.put(jid, pattern); + return pattern; + } + } - private static Pattern create(Set codePoints) { - final StringBuilder pattern = new StringBuilder(); - for (String codePoint : codePoints) { - if (pattern.length() != 0) { - pattern.append('|'); - } - pattern.append(Pattern.quote(codePoint)); - } - return Pattern.compile(pattern.toString()); - } + private static Pattern create(Set codePoints) { + final StringBuilder pattern = new StringBuilder(); + for (String codePoint : codePoints) { + if (pattern.length() != 0) { + pattern.append('|'); + } + pattern.append(Pattern.quote(codePoint)); + } + return Pattern.compile(pattern.toString()); + } - private static class PatternTuple { - private final Pattern local; - private final List domain; + private static class PatternTuple { + private final Pattern local; + private final List domain; - private PatternTuple(Pattern local, List domain) { - this.local = local; - this.domain = domain; - } + private PatternTuple(Pattern local, List domain) { + this.local = local; + this.domain = domain; + } - private static PatternTuple of(Jid jid) { - final Pattern localPattern; - if (jid.getEscapedLocal() != null) { - localPattern = create(findIrregularCodePoints(jid.getEscapedLocal())); - } else { - localPattern = null; - } - String domain = jid.getDomain().toEscapedString(); - final List domainPatterns = new ArrayList<>(); - if (domain != null) { - for (String label : domain.split("\\.")) { - domainPatterns.add(create(findIrregularCodePoints(label))); - } - } - return new PatternTuple(localPattern, domainPatterns); - } - } + private static PatternTuple of(Jid jid) { + final Pattern localPattern; + if (jid.getLocal() != null) { + localPattern = create(findIrregularCodePoints(jid.getLocal())); + } else { + localPattern = null; + } + String domain = jid.getDomain().toString(); + final List domainPatterns = new ArrayList<>(); + if (domain != null) { + for (String label : domain.split("\\.")) { + domainPatterns.add(create(findIrregularCodePoints(label))); + } + } + return new PatternTuple(localPattern, domainPatterns); + } + } } diff --git a/src/main/java/eu/siacs/conversations/utils/JidHelper.java b/src/main/java/eu/siacs/conversations/utils/JidHelper.java index e7443c8bb08bae1851d09f02e331d8b09763dc40..aac7d156b1c105702205ba47b30f32718d529903 100644 --- a/src/main/java/eu/siacs/conversations/utils/JidHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/JidHelper.java @@ -29,22 +29,19 @@ package eu.siacs.conversations.utils; - +import eu.siacs.conversations.Config; +import eu.siacs.conversations.xmpp.Jid; import java.util.Arrays; import java.util.List; import java.util.Locale; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.xmpp.InvalidJid; -import eu.siacs.conversations.xmpp.Jid; - public class JidHelper { private static final List LOCAL_PART_BLACKLIST = Arrays.asList("xmpp", "jabber", "me"); public static String localPartOrFallback(Jid jid) { if (LOCAL_PART_BLACKLIST.contains(jid.getLocal().toLowerCase(Locale.ENGLISH))) { - final String domain = jid.getDomain().toEscapedString(); + final String domain = jid.getDomain().toString(); final int index = domain.indexOf('.'); return index > 1 ? domain.substring(0, index) : domain; } else { @@ -52,16 +49,7 @@ public class JidHelper { } } - public static Jid parseOrFallbackToInvalid(String jid) { - try { - return Jid.of(jid); - } catch (IllegalArgumentException e) { - return InvalidJid.of(jid, true); - } - } - public static boolean isQuicksyDomain(final Jid jid) { return Config.QUICKSY_DOMAIN != null && Config.QUICKSY_DOMAIN.equals(jid.getDomain()); } - } diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java index 6c3075be9b7ec1194d979d961c6bc3096aae76cf..b6022cb694cebc2eb67808c26f20d11733c4cd23 100644 --- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java +++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java @@ -1,14 +1,12 @@ package eu.siacs.conversations.utils; import android.net.Uri; - import androidx.annotation.NonNull; - import com.google.common.base.CharMatcher; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; - +import eu.siacs.conversations.xmpp.Jid; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; @@ -18,8 +16,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import eu.siacs.conversations.xmpp.Jid; - public class XmppUri { public static final String ACTION_JOIN = "join"; @@ -42,8 +38,8 @@ public class XmppUri { parse(Uri.parse(uri)); } catch (IllegalArgumentException e) { try { - jid = Jid.ofEscaped(uri).asBareJid().toEscapedString(); - } catch (IllegalArgumentException e2) { + jid = Jid.of(uri).asBareJid().toString(); + } catch (final IllegalArgumentException e2) { jid = null; } } @@ -60,7 +56,8 @@ public class XmppUri { private static Map parseParameters(final String query, final char seperator) { final ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); - final String[] pairs = query == null ? new String[0] : query.split(String.valueOf(seperator)); + final String[] pairs = + query == null ? new String[0] : query.split(String.valueOf(seperator)); for (String pair : pairs) { final String[] parts = pair.split("=", 2); if (parts.length == 0) { @@ -94,7 +91,7 @@ public class XmppUri { final int id = Integer.parseInt(key.substring(OMEMO_URI_PARAM.length())); builder.add(new Fingerprint(FingerprintType.OMEMO, value, id)); } catch (Exception e) { - //ignoring invalid device id + // ignoring invalid device id } } else if ("omemo".equals(key)) { builder.add(new Fingerprint(FingerprintType.OMEMO, value, 0)); @@ -103,7 +100,8 @@ public class XmppUri { return builder.build(); } - public static String getFingerprintUri(final String base, final List fingerprints, char separator) { + public static String getFingerprintUri( + final String base, final List fingerprints, char separator) { final StringBuilder builder = new StringBuilder(base); builder.append('?'); for (int i = 0; i < fingerprints.size(); ++i) { @@ -145,8 +143,8 @@ public class XmppUri { if (segments.size() >= 2 && segments.get(1).contains("@")) { // sample : https://conversations.im/i/foo@bar.com try { - jid = Jid.ofEscaped(lameUrlDecode(segments.get(1))).toEscapedString(); - } catch (Exception e) { + jid = Jid.of(lameUrlDecode(segments.get(1))).toString(); + } catch (final Exception e) { jid = null; } } else if (segments.size() >= 3) { @@ -172,7 +170,8 @@ public class XmppUri { } } this.fingerprints = parseFingerprints(parameters); - } else if ("imto".equalsIgnoreCase(scheme) && Arrays.asList("xmpp", "jabber").contains(uri.getHost())) { + } else if ("imto".equalsIgnoreCase(scheme) + && Arrays.asList("xmpp", "jabber").contains(uri.getHost())) { // sample: imto://xmpp/foo@bar.com try { jid = URLDecoder.decode(uri.getEncodedPath(), "UTF-8").split("/")[1].trim(); @@ -195,15 +194,18 @@ public class XmppUri { public boolean isAction(final String action) { return Collections2.transform( - parameters.keySet(), - s -> CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('A', 'Z')).retainFrom(s) - ).contains(action); + parameters.keySet(), + s -> + CharMatcher.inRange('a', 'z') + .or(CharMatcher.inRange('A', 'Z')) + .retainFrom(s)) + .contains(action); } public Jid getJid() { try { - return this.jid == null ? null : Jid.ofEscaped(this.jid); - } catch (IllegalArgumentException e) { + return this.jid == null ? null : Jid.ofUserInput(this.jid); + } catch (final IllegalArgumentException e) { return null; } } @@ -213,9 +215,9 @@ public class XmppUri { return false; } try { - Jid.ofEscaped(jid); + Jid.ofUserInput(jid); return true; - } catch (IllegalArgumentException e) { + } catch (final IllegalArgumentException e) { return false; } } @@ -237,7 +239,7 @@ public class XmppUri { } public boolean hasFingerprints() { - return fingerprints.size() > 0; + return !fingerprints.isEmpty(); } public enum FingerprintType { diff --git a/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java b/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java index 02fc1632689a8c7be38b80e76b34d40e0a5d4b79..d848ca54ec2d8a4c1282160d251ad46e0bf67c14 100644 --- a/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java +++ b/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java @@ -13,19 +13,16 @@ import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.SystemClock; import android.util.Log; - import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import androidx.work.ForegroundInfo; import androidx.work.WorkManager; import androidx.work.Worker; import androidx.work.WorkerParameters; - import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.gson.stream.JsonWriter; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; @@ -36,7 +33,6 @@ import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.utils.BackupFileHeader; import eu.siacs.conversations.utils.Compatibility; - import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -56,7 +52,6 @@ import java.util.Date; import java.util.List; import java.util.Locale; import java.util.zip.GZIPOutputStream; - import javax.crypto.Cipher; import javax.crypto.CipherOutputStream; import javax.crypto.NoSuchPaddingException; @@ -162,7 +157,8 @@ public class ExportBackupWorker extends Worker { Log.d( Config.LOGTAG, String.format( - "skipping backup for %s because password is empty. unable to encrypt", + "skipping backup for %s because password is empty. unable to" + + " encrypt", account.getJid().asBareJid())); count++; continue; @@ -170,7 +166,7 @@ public class ExportBackupWorker extends Worker { final String filename = String.format( "%s.%s.ceb", - account.getJid().asBareJid().toEscapedString(), + account.getJid().asBareJid().toString(), DATE_FORMAT.format(new Date())); final File file = new File(FileBackend.getBackupDirectory(context), filename); try { @@ -379,7 +375,9 @@ public class ExportBackupWorker extends Worker { getApplicationContext().getSystemService(NotificationManager.class); try (final Cursor cursor = db.rawQuery( - "select messages.* from messages join conversations on conversations.uuid=messages.conversationUuid where conversations.accountUuid=?", + "select messages.* from messages join conversations on" + + " conversations.uuid=messages.conversationUuid where" + + " conversations.accountUuid=?", new String[] {uuid})) { final int size = cursor != null ? cursor.getCount() : 0; Log.d(Config.LOGTAG, "exporting " + size + " messages for account " + uuid); diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index 20db7f8782e9ea96d8583b68996a1542b00629b0..1b91eeaae84da4639502453e7044c3d730ee39ac 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -1,20 +1,16 @@ package eu.siacs.conversations.xml; import androidx.annotation.NonNull; - import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; - -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.List; - import eu.siacs.conversations.utils.XmlHelper; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import im.conversations.android.xmpp.model.stanza.Message; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; public class Element { private final String name; @@ -133,7 +129,7 @@ public class Element { public Element setAttribute(String name, Jid value) { if (name != null && value != null) { - this.attributes.put(name, value.toEscapedString()); + this.attributes.put(name, value.toString()); } return this; } @@ -172,16 +168,12 @@ public class Element { return Optional.fromNullable(Ints.tryParse(value)); } - public Jid getAttributeAsJid(String name) { + public Jid getAttributeAsJid(final String name) { final String jid = this.getAttribute(name); - if (jid != null && !jid.isEmpty()) { - try { - return Jid.ofEscaped(jid); - } catch (final IllegalArgumentException e) { - return InvalidJid.of(jid, this instanceof Message); - } + if (Strings.isNullOrEmpty(jid)) { + return null; } - return null; + return Jid.ofOrInvalid(jid, this instanceof Message); } public Hashtable getAttributes() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java b/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java deleted file mode 100644 index 4e3092821d4c241a004e5ce77f7e8e2940dea0ea..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java +++ /dev/null @@ -1,155 +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.xmpp; - -import androidx.annotation.NonNull; - -import im.conversations.android.xmpp.model.stanza.Stanza; - -public class InvalidJid implements Jid { - - private final String value; - - private InvalidJid(String jid) { - this.value = jid; - } - - public static Jid of(String jid, boolean fallback) { - final int pos = jid.indexOf('/'); - if (fallback && pos >= 0 && jid.length() >= pos + 1) { - if (jid.substring(pos+1).trim().isEmpty()) { - return Jid.ofEscaped(jid.substring(0,pos)); - } - } - return new InvalidJid(jid); - } - - @Override - @NonNull - public String toString() { - return value; - } - - @Override - public boolean isFullJid() { - throw new AssertionError("Not implemented"); - } - - @Override - public boolean isBareJid() { - throw new AssertionError("Not implemented"); - } - - @Override - public boolean isDomainJid() { - throw new AssertionError("Not implemented"); - } - - @Override - public Jid asBareJid() { - throw new AssertionError("Not implemented"); - } - - - @Override - public Jid withResource(CharSequence charSequence) { - throw new AssertionError("Not implemented"); - } - - @Override - public String getLocal() { - throw new AssertionError("Not implemented"); - } - - @Override - public String getEscapedLocal() { - throw new AssertionError("Not implemented"); - } - - @Override - public Jid getDomain() { - throw new AssertionError("Not implemented"); - } - - @Override - public String getResource() { - throw new AssertionError("Not implemented"); - } - - @Override - public String toEscapedString() { - throw new AssertionError("Not implemented"); - } - - @Override - public int length() { - return value.length(); - } - - @Override - public char charAt(int index) { - return value.charAt(index); - } - - @Override - public CharSequence subSequence(int start, int end) { - return value.subSequence(start, end); - } - - @Override - public int compareTo(@NonNull Jid o) { - throw new AssertionError("Not implemented"); - } - - public static Jid getNullForInvalid(Jid jid) { - if (jid instanceof InvalidJid) { - return null; - } else { - return jid; - } - } - - public static boolean isValid(Jid jid) { - return !(jid instanceof InvalidJid); - } - - public static boolean hasValidFrom(Stanza stanza) { - final String from = stanza.getAttribute("from"); - if (from == null) { - return false; - } - try { - Jid.ofEscaped(from); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/Jid.java b/src/main/java/eu/siacs/conversations/xmpp/Jid.java index 299c872b3c665bdcf20aa06fc5be1107a7a46999..34fbb9abb13946e70520382b60c5023379c6caf3 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/Jid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/Jid.java @@ -1,20 +1,19 @@ package eu.siacs.conversations.xmpp; +import androidx.annotation.NonNull; +import com.google.common.base.CharMatcher; +import im.conversations.android.xmpp.model.stanza.Stanza; +import java.io.Serializable; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.parts.Domainpart; import org.jxmpp.jid.parts.Localpart; import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.stringprep.XmppStringprepException; -import java.io.Serializable; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public interface Jid extends Comparable, Serializable, CharSequence { - - Pattern JID = Pattern.compile("^((.*?)@)?([^/@]+)(/(.*))?$"); +public abstract class Jid implements Comparable, Serializable, CharSequence { - static Jid of(CharSequence local, CharSequence domain, CharSequence resource) { + public static Jid of( + final CharSequence local, final CharSequence domain, final CharSequence resource) { if (local == null) { if (resource == null) { return ofDomain(domain); @@ -26,120 +25,312 @@ public interface Jid extends Comparable, Serializable, CharSequence { return ofLocalAndDomain(local, domain); } try { - return new WrappedJid(JidCreate.entityFullFrom( - Localpart.fromUnescaped(local.toString()), - Domainpart.from(domain.toString()), - Resourcepart.from(resource.toString()) - )); - } catch (XmppStringprepException e) { + return new InternalRepresentation( + JidCreate.entityFullFrom( + Localpart.from(local.toString()), + Domainpart.from(domain.toString()), + Resourcepart.from(resource.toString()))); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid ofEscaped(CharSequence local, CharSequence domain, CharSequence resource) { + public static Jid ofDomain(final CharSequence domain) { try { - if (resource == null) { - return new WrappedJid( - JidCreate.bareFrom( - Localpart.from(local.toString()), - Domainpart.from(domain.toString()) - ) - ); - } - return new WrappedJid(JidCreate.entityFullFrom( - Localpart.from(local.toString()), - Domainpart.from(domain.toString()), - Resourcepart.from(resource.toString()) - )); - } catch (XmppStringprepException e) { - throw new IllegalArgumentException(e); - } - } - - - static Jid ofDomain(CharSequence domain) { - try { - return new WrappedJid(JidCreate.domainBareFrom(domain)); - } catch (XmppStringprepException e) { + return new InternalRepresentation(JidCreate.domainBareFrom(domain)); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid ofLocalAndDomain(CharSequence local, CharSequence domain) { + public static Jid ofLocalAndDomain(final CharSequence local, final CharSequence domain) { try { - return new WrappedJid( + return new InternalRepresentation( JidCreate.bareFrom( - Localpart.fromUnescaped(local.toString()), - Domainpart.from(domain.toString()) - ) - ); - } catch (XmppStringprepException e) { + Localpart.from(local.toString()), Domainpart.from(domain.toString()))); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid ofDomainAndResource(CharSequence domain, CharSequence resource) { + public static Jid ofDomainAndResource(CharSequence domain, CharSequence resource) { try { - return new WrappedJid( + return new InternalRepresentation( JidCreate.domainFullFrom( Domainpart.from(domain.toString()), - Resourcepart.from(resource.toString()) - )); - } catch (XmppStringprepException e) { + Resourcepart.from(resource.toString()))); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid ofLocalAndDomainEscaped(CharSequence local, CharSequence domain) { + public static Jid of(final CharSequence input) { + if (input instanceof Jid jid) { + return jid; + } try { - return new WrappedJid( - JidCreate.bareFrom( - Localpart.from(local.toString()), - Domainpart.from(domain.toString()) - ) - ); - } catch (XmppStringprepException e) { + return new InternalRepresentation(JidCreate.from(input)); + } catch (final XmppStringprepException e) { throw new IllegalArgumentException(e); } } - static Jid of(CharSequence jid) { - if (jid instanceof Jid) { - return (Jid) jid; - } - Matcher matcher = JID.matcher(jid); - if (matcher.matches()) { - return of(matcher.group(2), matcher.group(3), matcher.group(5)); - } else { - throw new IllegalArgumentException("Could not parse JID: " + jid); + public static Jid ofUserInput(final CharSequence input) { + final var jid = of(input); + if (CharMatcher.is('@').matchesAnyOf(jid.getDomain())) { + throw new IllegalArgumentException("Domain should not contain @"); } + return jid; + } + + public static Jid ofOrInvalid(final String input) { + return ofOrInvalid(input, false); } - static Jid ofEscaped(CharSequence jid) { + /** + * + * @param jid a string representation of the jid to parse + * @param fallback indicates whether an attempt should be made to parse a bare version of the jid + * @return an instance of Jid; may be Jid.Invalid + */ + public static Jid ofOrInvalid(final String jid, final boolean fallback) { try { - return new WrappedJid(JidCreate.from(jid)); - } catch (final XmppStringprepException e) { - throw new IllegalArgumentException(e); + return Jid.of(jid); + } catch (final IllegalArgumentException e) { + return Jid.invalidOf(jid, fallback); } } - boolean isFullJid(); + private static Jid invalidOf(final String jid, boolean fallback) { + final int pos = jid.indexOf('/'); + if (fallback && pos >= 0 && jid.length() >= pos + 1) { + if (jid.substring(pos + 1).trim().isEmpty()) { + return Jid.of(jid.substring(0, pos)); + } + } + return new Invalid(jid); + } + + public abstract boolean isFullJid(); + + public abstract boolean isBareJid(); + + public abstract boolean isDomainJid(); + + public abstract Jid asBareJid(); - boolean isBareJid(); + public abstract Jid withResource(CharSequence resource); - boolean isDomainJid(); + public abstract String getLocal(); - Jid asBareJid(); + public abstract Jid getDomain(); - Jid withResource(CharSequence resource); + public abstract String getResource(); - String getLocal(); + private static class InternalRepresentation extends Jid { + private final org.jxmpp.jid.Jid inner; - String getEscapedLocal(); + private InternalRepresentation(final org.jxmpp.jid.Jid inner) { + this.inner = inner; + } + + @Override + public boolean isFullJid() { + return inner.isEntityFullJid() || inner.isDomainFullJid(); + } - Jid getDomain(); + @Override + public boolean isBareJid() { + return inner.isDomainBareJid() || inner.isEntityBareJid(); + } - String getResource(); + @Override + public boolean isDomainJid() { + return inner.isDomainBareJid() || inner.isDomainFullJid(); + } + + @Override + public Jid asBareJid() { + return new InternalRepresentation(inner.asBareJid()); + } + + @Override + public Jid withResource(CharSequence resource) { + final Localpart localpart = inner.getLocalpartOrNull(); + try { + final Resourcepart resourcepart = Resourcepart.from(resource.toString()); + if (localpart == null) { + return new InternalRepresentation( + JidCreate.domainFullFrom(inner.getDomain(), resourcepart)); + } else { + return new InternalRepresentation( + JidCreate.fullFrom(localpart, inner.getDomain(), resourcepart)); + } + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public String getLocal() { + final Localpart localpart = inner.getLocalpartOrNull(); + return localpart == null ? null : localpart.toString(); + } + + @Override + public Jid getDomain() { + return new InternalRepresentation(inner.asDomainBareJid()); + } + + @Override + public String getResource() { + final Resourcepart resourcepart = inner.getResourceOrNull(); + return resourcepart == null ? null : resourcepart.toString(); + } + + @NonNull + @Override + public String toString() { + return inner.toString(); + } + + @Override + public int length() { + return inner.length(); + } - String toEscapedString(); + @Override + public char charAt(int i) { + return inner.charAt(i); + } + + @NonNull + @Override + public CharSequence subSequence(int i, int i1) { + return inner.subSequence(i, i1); + } + + @Override + public int compareTo(Jid jid) { + if (jid instanceof InternalRepresentation) { + return inner.compareTo(((InternalRepresentation) jid).inner); + } else { + return 0; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InternalRepresentation that = (InternalRepresentation) o; + return inner.equals(that.inner); + } + + @Override + public int hashCode() { + return inner.hashCode(); + } + } + + public static class Invalid extends Jid { + + private final String value; + + private Invalid(final String jid) { + this.value = jid; + } + + @Override + @NonNull + public String toString() { + return value; + } + + @Override + public boolean isFullJid() { + throw new AssertionError("Not implemented"); + } + + @Override + public boolean isBareJid() { + throw new AssertionError("Not implemented"); + } + + @Override + public boolean isDomainJid() { + throw new AssertionError("Not implemented"); + } + + @Override + public Jid asBareJid() { + throw new AssertionError("Not implemented"); + } + + @Override + public Jid withResource(CharSequence charSequence) { + throw new AssertionError("Not implemented"); + } + + @Override + public String getLocal() { + throw new AssertionError("Not implemented"); + } + + @Override + public Jid getDomain() { + throw new AssertionError("Not implemented"); + } + + @Override + public String getResource() { + throw new AssertionError("Not implemented"); + } + + @Override + public int length() { + return value.length(); + } + + @Override + public char charAt(int index) { + return value.charAt(index); + } + + @NonNull + @Override + public CharSequence subSequence(int start, int end) { + return value.subSequence(start, end); + } + + @Override + public int compareTo(@NonNull Jid o) { + throw new AssertionError("Not implemented"); + } + + public static Jid getNullForInvalid(final Jid jid) { + if (jid instanceof Invalid) { + return null; + } else { + return jid; + } + } + + public static boolean isValid(Jid jid) { + return !(jid instanceof Invalid); + } + + public static boolean hasValidFrom(final Stanza stanza) { + final String from = stanza.getAttribute("from"); + if (from == null) { + return false; + } + try { + Jid.of(from); + return true; + } catch (final IllegalArgumentException e) { + return false; + } + } + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/WrappedJid.java b/src/main/java/eu/siacs/conversations/xmpp/WrappedJid.java deleted file mode 100644 index 08fb6e6dcc832ea105c160bf6132c91abddd095c..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/WrappedJid.java +++ /dev/null @@ -1,130 +0,0 @@ -package eu.siacs.conversations.xmpp; - - -import androidx.annotation.NonNull; - -import org.jxmpp.jid.Jid; -import org.jxmpp.jid.impl.JidCreate; -import org.jxmpp.jid.parts.Localpart; -import org.jxmpp.jid.parts.Resourcepart; -import org.jxmpp.stringprep.XmppStringprepException; - - -public class WrappedJid implements eu.siacs.conversations.xmpp.Jid { - private final Jid inner; - - WrappedJid(Jid inner) { - this.inner = inner; - } - - @Override - public boolean isFullJid() { - return inner.isEntityFullJid() || inner.isDomainFullJid(); - } - - @Override - public boolean isBareJid() { - return inner.isDomainBareJid() || inner.isEntityBareJid(); - } - - @Override - public boolean isDomainJid() { - return inner.isDomainBareJid() || inner.isDomainFullJid(); - } - - @Override - public eu.siacs.conversations.xmpp.Jid asBareJid() { - return new WrappedJid(inner.asBareJid()); - } - - @Override - public eu.siacs.conversations.xmpp.Jid withResource(CharSequence resource) { - final Localpart localpart = inner.getLocalpartOrNull(); - try { - final Resourcepart resourcepart = Resourcepart.from(resource.toString()); - if (localpart == null) { - return new WrappedJid(JidCreate.domainFullFrom(inner.getDomain(),resourcepart)); - } else { - return new WrappedJid( - JidCreate.fullFrom( - localpart, - inner.getDomain(), - resourcepart - )); - } - } catch (XmppStringprepException e) { - throw new IllegalArgumentException(e); - } - } - - @Override - public String getLocal() { - final Localpart localpart = inner.getLocalpartOrNull(); - return localpart == null ? null : localpart.asUnescapedString(); - } - - @Override - public String getEscapedLocal() { - final Localpart localpart = inner.getLocalpartOrNull(); - return localpart == null ? null : localpart.toString(); - } - - @Override - public eu.siacs.conversations.xmpp.Jid getDomain() { - return new WrappedJid(inner.asDomainBareJid()); - } - - @Override - public String getResource() { - final Resourcepart resourcepart = inner.getResourceOrNull(); - return resourcepart == null ? null : resourcepart.toString(); - } - - @Override - public String toEscapedString() { - return inner.toString(); - } - - @NonNull - @Override - public String toString() { - return inner.asUnescapedString(); - } - - @Override - public int length() { - return inner.length(); - } - - @Override - public char charAt(int i) { - return inner.charAt(i); - } - - @Override - public CharSequence subSequence(int i, int i1) { - return inner.subSequence(i,i1); - } - - @Override - public int compareTo(eu.siacs.conversations.xmpp.Jid jid) { - if (jid instanceof WrappedJid) { - return inner.compareTo(((WrappedJid) jid).inner); - } else { - return 0; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - WrappedJid that = (WrappedJid) o; - return inner.equals(that.inner); - } - - @Override - public int hashCode() { - return inner.hashCode(); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 6ce05e3784a07f1ca336b22bb7ff776b59187cae..d02d533cadaf246a1390aa6edb9c42501ca31008 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -2319,7 +2319,7 @@ public class XmppConnection implements Runnable { for (final Element element : elements) { if (element.getName().equals("item")) { final Jid jid = - InvalidJid.getNullForInvalid( + Jid.Invalid.getNullForInvalid( element.getAttributeAsJid("jid")); if (jid != null && !jid.equals(account.getDomain())) { items.add(jid); @@ -2484,7 +2484,7 @@ public class XmppConnection implements Runnable { final Tag stream = Tag.start("stream:stream"); stream.setAttribute("to", account.getServer()); if (from) { - stream.setAttribute("from", account.getJid().asBareJid().toEscapedString()); + stream.setAttribute("from", account.getJid().asBareJid().toString()); } stream.setAttribute("version", "1.0"); stream.setAttribute("xml:lang", LocalizedContent.STREAM_LANGUAGE); @@ -2703,7 +2703,7 @@ public class XmppConnection implements Runnable { public List getMucServersWithholdAccount() { final List servers = getMucServers(); - servers.remove(account.getDomain().toEscapedString()); + servers.remove(account.getDomain().toString()); return servers; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 1f93ed924bd60522bfaf9920e9413f8eab2cd400..d01880ab8c67be468d956f85cae7d948aa11ed8b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -4,9 +4,7 @@ import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.util.Base64; import android.util.Log; - import androidx.annotation.Nullable; - import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; @@ -15,7 +13,6 @@ import com.google.common.cache.CacheBuilder; import com.google.common.collect.Collections2; import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableSet; - import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; @@ -39,10 +36,8 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.jingle.transports.InbandBytestreamsTransport; import eu.siacs.conversations.xmpp.jingle.transports.Transport; - import im.conversations.android.xmpp.model.jingle.Jingle; import im.conversations.android.xmpp.model.stanza.Iq; - import java.lang.ref.WeakReference; import java.security.SecureRandom; import java.util.Arrays; @@ -353,7 +348,8 @@ public class JingleConnectionManager extends AbstractConnectionManager { Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": updated previous busy because call got picked up by another device"); + + ": updated previous busy because call got picked up by" + + " another device"); mXmppConnectionService.getNotificationService().clearMissedCall(previousBusy); return; } @@ -393,15 +389,14 @@ public class JingleConnectionManager extends AbstractConnectionManager { final String theirSessionId = id.sessionId; if (ComparisonChain.start() .compare(ourSessionId, theirSessionId) - .compare( - account.getJid().toEscapedString(), - id.with.toEscapedString()) + .compare(account.getJid().toString(), id.with.toString()) .result() > 0) { Log.d( Config.LOGTAG, account.getJid().asBareJid() - + ": our session lost tie break. automatically accepting their session. winning Session=" + + ": our session lost tie break. automatically accepting" + + " their session. winning Session=" + theirSessionId); // TODO a retract for this reason should probably include some indication of // tie break @@ -417,7 +412,8 @@ public class JingleConnectionManager extends AbstractConnectionManager { Log.d( Config.LOGTAG, account.getJid().asBareJid() - + ": our session won tie break. waiting for other party to accept. winningSession=" + + ": our session won tie break. waiting for other party to" + + " accept. winningSession=" + ourSessionId); // TODO reject their session with ? } @@ -453,7 +449,8 @@ public class JingleConnectionManager extends AbstractConnectionManager { Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": ignoring proposal because busy on this device but there are other devices"); + + ": ignoring proposal because busy on this device but" + + " there are other devices"); } } else { final JingleRtpConnection rtpConnection = @@ -772,12 +769,14 @@ public class JingleConnectionManager extends AbstractConnectionManager { if (hasMatchingRtpSession(account, with, media)) { Log.d( Config.LOGTAG, - "ignoring request to propose jingle session because the other party already created one for us"); + "ignoring request to propose jingle session because the other party" + + " already created one for us"); // TODO return something that we can parse the connection of of return null; } throw new IllegalStateException( - "There is already a running RTP session. This should have been caught by the UI"); + "There is already a running RTP session. This should have been caught by" + + " the UI"); } final CallIntegration callIntegration = new CallIntegration(mXmppConnectionService.getApplicationContext()); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index b4f1882f5bfc906770b33e00d859ef0233cbf85f..10a6d4ba6b6c9ef78667db03de71481a56dead47 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -4,10 +4,8 @@ import android.content.Intent; import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Preconditions; @@ -24,7 +22,6 @@ 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.BuildConfig; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; @@ -47,15 +44,8 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.Proceed; import eu.siacs.conversations.xmpp.jingle.stanzas.Propose; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; - import im.conversations.android.xmpp.model.jingle.Jingle; import im.conversations.android.xmpp.model.stanza.Iq; - -import org.webrtc.EglBase; -import org.webrtc.IceCandidate; -import org.webrtc.PeerConnection; -import org.webrtc.VideoTrack; - import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -67,6 +57,10 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.webrtc.EglBase; +import org.webrtc.IceCandidate; +import org.webrtc.PeerConnection; +import org.webrtc.VideoTrack; public class JingleRtpConnection extends AbstractJingleConnection implements WebRTCWrapper.EventCallback, CallIntegration.Callback, OngoingRtpSession { @@ -278,7 +272,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": PeerConnection was not initialized when processing transport info. this usually indicates a race condition that can be ignored"); + + ": PeerConnection was not initialized when processing transport info." + + " this usually indicates a race condition that can be ignored"); } } @@ -625,7 +620,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.getAccount().getJid().asBareJid() - + ": unable to rollback local description after receiving content-reject", + + ": unable to rollback local description after receiving" + + " content-reject", cause); webRTCWrapper.close(); sendSessionTerminate(Reason.FAILED_APPLICATION, cause.getMessage()); @@ -694,7 +690,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.getAccount().getJid().asBareJid() - + ": unable to rollback local description after trying to retract content-add", + + ": unable to rollback local description after trying to retract" + + " content-add", cause); webRTCWrapper.close(); sendSessionTerminate(Reason.FAILED_APPLICATION, cause.getMessage()); @@ -773,14 +770,16 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": remote has accepted our upgrade to senders=both"); + + ": remote has accepted our upgrade to" + + " senders=both"); acceptContentAdd( ContentAddition.summary(modifiedSenders), modifiedSenders); } else { Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": remote has rejected our upgrade to senders=both"); + + ": remote has rejected our upgrade to" + + " senders=both"); acceptContentAdd(contentAddition, incomingContentAdd); } }); @@ -1072,7 +1071,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": no identification tags found in initial offer. we won't be able to calculate mLineIndices"); + + ": no identification tags found in initial offer. we won't be able to" + + " calculate mLineIndices"); } return identificationTags; } @@ -1169,7 +1169,8 @@ public class JingleRtpConnection extends AbstractJingleConnection sendSessionTerminate( Reason.SECURITY_ERROR, String.format( - "Your session proposal (Jingle Message Initiation) included media %s but your session-initiate was %s", + "Your session proposal (Jingle Message Initiation) included media" + + " %s but your session-initiate was %s", this.proposedMedia, contentMap.getMedia())); return; } @@ -1254,7 +1255,8 @@ public class JingleRtpConnection extends AbstractJingleConnection sendSessionTerminate( Reason.SECURITY_ERROR, String.format( - "Your session-included included media %s but our session-initiate was %s", + "Your session-included included media %s but our session-initiate was" + + " %s", this.proposedMedia, contentMap.getMedia())); return; } @@ -1342,7 +1344,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": ICE servers got discovered when session was already terminated. nothing to do."); + + ": ICE servers got discovered when session was already terminated." + + " nothing to do."); return; } final boolean includeCandidates = remoteHasSdpOfferAnswer(); @@ -1445,7 +1448,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": preparing session accept was too slow. already terminated. nothing to do."); + + ": preparing session accept was too slow. already terminated. nothing" + + " to do."); return; } transitionOrThrow(State.SESSION_ACCEPTED); @@ -1488,10 +1492,10 @@ public class JingleRtpConnection extends AbstractJingleConnection + ": delivered message to JingleRtpConnection " + message); switch (message.getName()) { - case "propose" -> receivePropose( - from, Propose.upgrade(message), serverMessageId, timestamp); - case "proceed" -> receiveProceed( - from, Proceed.upgrade(message), serverMessageId, timestamp); + case "propose" -> + receivePropose(from, Propose.upgrade(message), serverMessageId, timestamp); + case "proceed" -> + receiveProceed(from, Proceed.upgrade(message), serverMessageId, timestamp); case "retract" -> receiveRetract(from, serverMessageId, timestamp); case "reject" -> receiveReject(from, serverMessageId, timestamp); case "accept" -> receiveAccept(from, serverMessageId, timestamp); @@ -1605,7 +1609,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.account.getJid() - + ": received reject while in SESSION_INITIATED_PRE_APPROVED. callee reconsidered before receiving session-init"); + + ": received reject while in SESSION_INITIATED_PRE_APPROVED. callee" + + " reconsidered before receiving session-init"); closeTransitionLogFinish(State.TERMINATED_DECLINED_OR_BUSY); return; } @@ -1727,7 +1732,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": remote party signaled support for OMEMO verification but we have OMEMO disabled"); + + ": remote party signaled support for OMEMO" + + " verification but we have OMEMO disabled"); } this.omemoVerification.setDeviceId(null); } @@ -1822,7 +1828,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": ICE servers got discovered when session was already terminated. nothing to do."); + + ": ICE servers got discovered when session was already terminated." + + " nothing to do."); return; } final boolean includeCandidates = remoteHasSdpOfferAnswer(); @@ -1917,7 +1924,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": preparing session was too slow. already terminated. nothing to do."); + + ": preparing session was too slow. already terminated. nothing to" + + " do."); return; } this.transitionOrThrow(targetState); @@ -1956,7 +1964,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": unable to use OMEMO DTLS verification on outgoing session initiate. falling back", + + ": unable to use OMEMO DTLS verification on outgoing" + + " session initiate. falling back", e); return rtpContentMap; }, @@ -2070,9 +2079,10 @@ public class JingleRtpConnection extends AbstractJingleConnection case CONNECTED -> RtpEndUserState.CONNECTED; case NEW, CONNECTING -> RtpEndUserState.CONNECTING; case CLOSED -> RtpEndUserState.ENDING_CALL; - default -> zeroDuration() - ? RtpEndUserState.CONNECTIVITY_ERROR - : RtpEndUserState.RECONNECTING; + default -> + zeroDuration() + ? RtpEndUserState.CONNECTIVITY_ERROR + : RtpEndUserState.RECONNECTING; }; } @@ -2116,13 +2126,14 @@ public class JingleRtpConnection extends AbstractJingleConnection } case TERMINATED_SUCCESS -> this.callIntegration.success(); case ACCEPTED -> this.callIntegration.accepted(); - case RETRACTED, RETRACTED_RACED, TERMINATED_CANCEL_OR_TIMEOUT -> this.callIntegration - .retracted(); + case RETRACTED, RETRACTED_RACED, TERMINATED_CANCEL_OR_TIMEOUT -> + this.callIntegration.retracted(); case TERMINATED_CONNECTIVITY_ERROR, - TERMINATED_APPLICATION_FAILURE, - TERMINATED_SECURITY_ERROR -> this.callIntegration.error(); - default -> throw new IllegalStateException( - String.format("%s is not handled", this.state)); + TERMINATED_APPLICATION_FAILURE, + TERMINATED_SECURITY_ERROR -> + this.callIntegration.error(); + default -> + throw new IllegalStateException(String.format("%s is not handled", this.state)); } } @@ -2195,14 +2206,18 @@ public class JingleRtpConnection extends AbstractJingleConnection cancelRingingTimeout(); acceptCallFromSessionInitialized(); } - case ACCEPTED -> Log.w( - Config.LOGTAG, - id.account.getJid().asBareJid() - + ": the call has already been accepted with another client. UI was just lagging behind"); - case PROCEED, SESSION_ACCEPTED -> Log.w( - Config.LOGTAG, - id.account.getJid().asBareJid() - + ": the call has already been accepted. user probably double tapped the UI"); + case ACCEPTED -> + Log.w( + Config.LOGTAG, + id.account.getJid().asBareJid() + + ": the call has already been accepted with another client." + + " UI was just lagging behind"); + case PROCEED, SESSION_ACCEPTED -> + Log.w( + Config.LOGTAG, + id.account.getJid().asBareJid() + + ": the call has already been accepted. user probably double" + + " tapped the UI"); default -> throw new IllegalStateException("Can not accept call from " + this.state); } } @@ -2212,7 +2227,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": received rejectCall() when session has already been terminated. nothing to do"); + + ": received rejectCall() when session has already been terminated." + + " nothing to do"); return; } switch (this.state) { @@ -2245,7 +2261,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.w( Config.LOGTAG, id.account.getJid().asBareJid() - + ": received endCall() when session has already been terminated. nothing to do"); + + ": received endCall() when session has already been terminated." + + " nothing to do"); return; } if (isInState(State.PROPOSED) && isResponder()) { @@ -2447,7 +2464,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": not sending session-terminate after connectivity error because session is already in state " + + ": not sending session-terminate after connectivity error" + + " because session is already in state " + this.state); return; } @@ -2649,7 +2667,8 @@ public class JingleRtpConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.account.getJid().asBareJid() - + ": no need to send session-terminate after failed connection. Other party already did"); + + ": no need to send session-terminate after failed connection." + + " Other party already did"); return; } sendSessionTerminate(Reason.CONNECTIVITY_ERROR); @@ -2704,7 +2723,8 @@ public class JingleRtpConnection extends AbstractJingleConnection // callback when the rtp session has already ended. Log.w( Config.LOGTAG, - "CallIntegration requested incoming call UI but session was already terminated"); + "CallIntegration requested incoming call UI but session was already" + + " terminated"); return; } // TODO apparently this can be called too early as well? @@ -2736,8 +2756,8 @@ public class JingleRtpConnection extends AbstractJingleConnection // we need to start the UI to a) show it and b) be able to ask for permissions final Intent intent = new Intent(xmppConnectionService, RtpSessionActivity.class); intent.setAction(RtpSessionActivity.ACTION_ACCEPT_CALL); - intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().toEscapedString()); - intent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString()); + intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().toString()); + intent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toString()); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java index 2925592ea498772ccf62ab48d7b183562f21bf17..8cebf080fd14835dde8d5a36002b929a63514f98 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java @@ -1,10 +1,8 @@ package eu.siacs.conversations.xmpp.jingle.transports; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; import com.google.common.base.Optional; @@ -22,7 +20,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; - import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.SocksSocketFactory; import eu.siacs.conversations.xml.Element; @@ -33,7 +30,6 @@ import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection; import eu.siacs.conversations.xmpp.jingle.DirectConnectionUtils; import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo; import im.conversations.android.xmpp.model.stanza.Iq; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -104,8 +100,8 @@ public class SocksByteStreamsTransport implements Transport { .join( Arrays.asList( streamId, - id.with.toEscapedString(), - id.account.getJid().toEscapedString())), + id.with.toString(), + id.account.getJid().toString())), StandardCharsets.UTF_8) .toString(); final var ourDestination = @@ -115,8 +111,8 @@ public class SocksByteStreamsTransport implements Transport { .join( Arrays.asList( streamId, - id.account.getJid().toEscapedString(), - id.with.toEscapedString())), + id.account.getJid().toString(), + id.with.toString())), StandardCharsets.UTF_8) .toString(); @@ -255,7 +251,7 @@ public class SocksByteStreamsTransport implements Transport { final Element query = proxyActivation.addChild("query", Namespace.BYTE_STREAMS); query.setAttribute("sid", this.streamId); final Element activate = query.addChild("activate"); - activate.setContent(id.with.toEscapedString()); + activate.setContent(id.with.toString()); xmppConnection.sendIqPacket( proxyActivation, (response) -> { @@ -731,10 +727,12 @@ public class SocksByteStreamsTransport implements Transport { && selectedByThemCandidatePriority > candidate.priority) { Log.d( Config.LOGTAG, - "The candidate selected by peer had a higher priority then anything we could try"); + "The candidate selected by peer had a higher priority then anything we" + + " could try"); connectionFuture.setException( new CandidateErrorException( - "The candidate selected by peer had a higher priority then anything we could try")); + "The candidate selected by peer had a higher priority then" + + " anything we could try")); return; } try { @@ -864,7 +862,7 @@ public class SocksByteStreamsTransport implements Transport { return new Candidate( cid, host, - Jid.ofEscaped(jid), + Jid.of(jid), Integer.parseInt(port), Integer.parseInt(priority), CandidateType.valueOf(type.toUpperCase(Locale.ROOT))); diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java b/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java index 27264f7545e079f2c3cb6415f4763ab3c334dc3b..6a7c8605cc1c8262ec356da6295a5a1d97cb8147 100644 --- a/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java +++ b/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java @@ -1,7 +1,6 @@ package im.conversations.android.xmpp.model.bind; import com.google.common.base.Strings; - import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.Extension; @@ -26,7 +25,7 @@ public class Bind extends Extension { return null; } try { - return eu.siacs.conversations.xmpp.Jid.ofEscaped(content); + return eu.siacs.conversations.xmpp.Jid.of(content); } catch (final IllegalArgumentException e) { return null; } diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java index e29ae7dea3ced429d4e25d17f1fe1954017b5d98..d477131b886eb09afdd30a33bcfc46eb095ec04a 100644 --- a/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java @@ -1,7 +1,6 @@ package im.conversations.android.xmpp.model.sasl2; import com.google.common.base.Strings; - import eu.siacs.conversations.xmpp.Jid; import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.Extension; @@ -9,18 +8,17 @@ import im.conversations.android.xmpp.model.Extension; @XmlElement public class AuthorizationIdentifier extends Extension { - public AuthorizationIdentifier() { super(AuthorizationIdentifier.class); } public Jid get() { final var content = getContent(); - if ( Strings.isNullOrEmpty(content)) { + if (Strings.isNullOrEmpty(content)) { return null; } try { - return Jid.ofEscaped(content); + return Jid.of(content); } catch (final IllegalArgumentException e) { return null; } diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java index 82a8ce3dfbfa1f225e6030ace9f746cdff09c9cd..3abf016fffc3cfc8b60766c43f2412dbbed7effa 100644 --- a/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java @@ -1,10 +1,7 @@ package im.conversations.android.xmpp.model.stanza; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; - -import im.conversations.android.xmpp.model.Extension; import im.conversations.android.xmpp.model.StreamElement; import im.conversations.android.xmpp.model.error.Error; @@ -45,7 +42,7 @@ public abstract class Stanza extends StreamElement { public boolean isInvalid() { final var to = getTo(); final var from = getFrom(); - if (to instanceof InvalidJid || from instanceof InvalidJid) { + if (to instanceof Jid.Invalid || from instanceof Jid.Invalid) { return true; } return false; From cee4370dcc590ad6adfbc020aea56e8b6abcf89a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 24 Jan 2025 18:45:23 +0100 Subject: [PATCH 07/39] fixup quicksy and playstore build --- .../java/eu/siacs/conversations/xmpp/Jid.java | 4 +- .../services/PushManagementService.java | 11 +++--- .../siacs/conversations/entities/Entry.java | 35 ++++++++--------- .../conversations/ui/EnterNameActivity.java | 7 +--- .../utils/PhoneNumberUtilWrapper.java | 38 ++++++++++--------- 5 files changed, 47 insertions(+), 48 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/Jid.java b/src/main/java/eu/siacs/conversations/xmpp/Jid.java index 34fbb9abb13946e70520382b60c5023379c6caf3..c6b26903f32bea1deb59182cda9938112bf4bbc2 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/Jid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/Jid.java @@ -88,9 +88,9 @@ public abstract class Jid implements Comparable, Serializable, CharSequence } /** - * * @param jid a string representation of the jid to parse - * @param fallback indicates whether an attempt should be made to parse a bare version of the jid + * @param fallback indicates whether an attempt should be made to parse a bare version of the + * jid * @return an instance of Jid; may be Jid.Invalid */ public static Jid ofOrInvalid(final String jid, final boolean fallback) { diff --git a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java index e5f4b9d7c83fc7463b1283c60326441d929eece2..41e4670541781b8f56d468d6dbc3d7fe25f3aca1 100644 --- a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java +++ b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java @@ -1,11 +1,9 @@ package eu.siacs.conversations.services; import android.util.Log; - import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailabilityLight; import com.google.firebase.messaging.FirebaseMessaging; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -15,7 +13,6 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.forms.Data; - import im.conversations.android.xmpp.model.stanza.Iq; public class PushManagementService { @@ -53,7 +50,7 @@ public class PushManagementService { if (response.getType() == Iq.Type.RESULT && data != null) { final Jid jid; try { - jid = Jid.ofEscaped(data.getValue("jid")); + jid = Jid.of(data.getValue("jid")); } catch (final IllegalArgumentException e) { Log.d( Config.LOGTAG, @@ -70,7 +67,8 @@ public class PushManagementService { Log.d( Config.LOGTAG, account.getJid().asBareJid() - + ": failed to enable push. invalid response from app server " + + ": failed to enable push. invalid response" + + " from app server " + response); } }); @@ -123,7 +121,8 @@ public class PushManagementService { } catch (Exception e) { Log.d( Config.LOGTAG, - "unable to get Firebase instance token due to bug in library ", + "unable to get Firebase instance token due to bug in" + + " library ", e); return; } diff --git a/src/quicksy/java/eu/siacs/conversations/entities/Entry.java b/src/quicksy/java/eu/siacs/conversations/entities/Entry.java index c202be4707a0344143a3e6be3553e062aaa7d33d..7e9fbcf5ebd2868c504c57f50770363559bbe669 100644 --- a/src/quicksy/java/eu/siacs/conversations/entities/Entry.java +++ b/src/quicksy/java/eu/siacs/conversations/entities/Entry.java @@ -1,21 +1,16 @@ package eu.siacs.conversations.entities; import android.util.Base64; - import com.google.common.base.Charsets; import com.google.common.hash.Hashing; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import eu.siacs.conversations.android.PhoneNumberContact; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.Jid; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import eu.siacs.conversations.android.PhoneNumberContact; -import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.Jid; - public class Entry implements Comparable { private final List jids; private final String number; @@ -47,23 +42,25 @@ public class Entry implements Comparable { return entries; } - public static String statusQuo(final Collection phoneNumberContacts, Collection systemContacts) { + public static String statusQuo( + final Collection phoneNumberContacts, + Collection systemContacts) { return statusQuo(ofPhoneNumberContactsAndContacts(phoneNumberContacts, systemContacts)); } private static String statusQuo(final List entries) { Collections.sort(entries); StringBuilder builder = new StringBuilder(); - for(Entry entry : entries) { + for (Entry entry : entries) { if (builder.length() != 0) { builder.append('\u001d'); } builder.append(entry.getNumber()); List jids = entry.getJids(); Collections.sort(jids); - for(Jid jid : jids) { + for (Jid jid : jids) { builder.append('\u001e'); - builder.append(jid.asBareJid().toEscapedString()); + builder.append(jid.asBareJid().toString()); } } @SuppressWarnings("deprecation") @@ -71,12 +68,16 @@ public class Entry implements Comparable { return new String(Base64.encode(sha1, Base64.DEFAULT)).trim(); } - private static List ofPhoneNumberContactsAndContacts(final Collection phoneNumberContacts, Collection systemContacts) { + private static List ofPhoneNumberContactsAndContacts( + final Collection phoneNumberContacts, + Collection systemContacts) { final ArrayList entries = new ArrayList<>(); - for(Contact contact : systemContacts) { - final PhoneNumberContact phoneNumberContact = PhoneNumberContact.findByUri(phoneNumberContacts, contact.getSystemAccount()); + for (Contact contact : systemContacts) { + final PhoneNumberContact phoneNumberContact = + PhoneNumberContact.findByUri(phoneNumberContacts, contact.getSystemAccount()); if (phoneNumberContact != null && phoneNumberContact.getPhoneNumber() != null) { - Entry entry = findOrCreateByPhoneNumber(entries, phoneNumberContact.getPhoneNumber()); + Entry entry = + findOrCreateByPhoneNumber(entries, phoneNumberContact.getPhoneNumber()); entry.jids.add(contact.getJid().asBareJid()); } } @@ -84,7 +85,7 @@ public class Entry implements Comparable { } private static Entry findOrCreateByPhoneNumber(final List entries, String number) { - for(Entry entry : entries) { + for (Entry entry : entries) { if (entry.number.equals(number)) { return entry; } diff --git a/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java b/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java index 22a21d32279eceebf3943263de246821e853d8cb..38d1f0db49d1e4dbf87a573244262d4a5b6ea3da 100644 --- a/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java +++ b/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java @@ -3,16 +3,13 @@ package eu.siacs.conversations.ui; import android.content.Intent; import android.os.Bundle; import android.view.View; - import androidx.databinding.DataBindingUtil; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityEnterNameBinding; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.AbstractQuickConversationsService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.AccountUtils; - import java.util.concurrent.atomic.AtomicBoolean; public class EnterNameActivity extends XmppActivity @@ -46,12 +43,12 @@ public class EnterNameActivity extends XmppActivity if (AbstractQuickConversationsService.isQuicksyPlayStore()) { intent = new Intent(getApplicationContext(), StartConversationActivity.class); intent.putExtra("init", true); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); } else { intent = new Intent(this, PublishProfilePictureActivity.class); intent.putExtra("setup", true); } - intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); startActivity(intent); finish(); } diff --git a/src/quicksy/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java b/src/quicksy/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java index 4bc18b886c9662fe2a7eb035346a69660de6eb4f..f77657ea3bc6a1c83c61d91b84feec72998de292 100644 --- a/src/quicksy/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java +++ b/src/quicksy/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java @@ -1,22 +1,18 @@ package eu.siacs.conversations.utils; import android.content.Context; -import android.telephony.TelephonyManager; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - import eu.siacs.conversations.xmpp.Jid; import io.michaelrocks.libphonenumber.android.NumberParseException; import io.michaelrocks.libphonenumber.android.PhoneNumberUtil; import io.michaelrocks.libphonenumber.android.Phonenumber; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; public class PhoneNumberUtilWrapper { private static volatile PhoneNumberUtil instance; - public static String getCountryForCode(String code) { Locale locale = new Locale("", code); return locale.getDisplayCountry(); @@ -24,20 +20,28 @@ public class PhoneNumberUtilWrapper { public static String toFormattedPhoneNumber(Context context, Jid jid) { try { - return getInstance(context).format(toPhoneNumber(context, jid), PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL).replace(' ','\u202F'); + return getInstance(context) + .format( + toPhoneNumber(context, jid), + PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL) + .replace(' ', '\u202F'); } catch (Exception e) { - return jid.getEscapedLocal(); + return jid.getLocal(); } } - public static Phonenumber.PhoneNumber toPhoneNumber(Context context, Jid jid) throws NumberParseException { - return getInstance(context).parse(jid.getEscapedLocal(), "de"); + public static Phonenumber.PhoneNumber toPhoneNumber(Context context, Jid jid) + throws NumberParseException { + return getInstance(context).parse(jid.getLocal(), "de"); } - public static String normalize(Context context, String input) throws IllegalArgumentException, NumberParseException { - final Phonenumber.PhoneNumber number = getInstance(context).parse(input, LocationProvider.getUserCountry(context)); + public static String normalize(Context context, String input) + throws IllegalArgumentException, NumberParseException { + final Phonenumber.PhoneNumber number = + getInstance(context).parse(input, LocationProvider.getUserCountry(context)); if (!getInstance(context).isValidNumber(number)) { - throw new IllegalArgumentException(String.format("%s is not a valid phone number", input)); + throw new IllegalArgumentException( + String.format("%s is not a valid phone number", input)); } return normalize(context, number); } @@ -54,7 +58,6 @@ public class PhoneNumberUtilWrapper { if (localInstance == null) { instance = localInstance = PhoneNumberUtil.createInstance(context); } - } } return localInstance; @@ -63,10 +66,10 @@ public class PhoneNumberUtilWrapper { public static List getCountries(final Context context) { List countries = new ArrayList<>(); for (String region : getInstance(context).getSupportedRegions()) { - countries.add(new Country(region, getInstance(context).getCountryCodeForRegion(region))); + countries.add( + new Country(region, getInstance(context).getCountryCodeForRegion(region))); } return countries; - } public static class Country implements Comparable { @@ -97,5 +100,4 @@ public class PhoneNumberUtilWrapper { return name.compareTo(o.name); } } - } From 5ed4b147ce49e44cfcf0bda60f469041b35b1d50 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Fri, 24 Jan 2025 12:53:06 +0000 Subject: [PATCH 08/39] Translated using Weblate (Ukrainian) Currently translated at 100.0% (81 of 81 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/uk/ --- fastlane/metadata/android/uk/changelogs/4213104.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/4213104.txt diff --git a/fastlane/metadata/android/uk/changelogs/4213104.txt b/fastlane/metadata/android/uk/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..151ac4a042c33796d760e0bf9ffbda92d50b7046 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Використання SASL SCRAM Downgrade Protection (XEP-0474) +* Надсилання реакцій у приватні повідомлення MUC на правильний JID From 1e9e3bfe03013f9b7cd15f6524262e56221b0c0f Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Fri, 24 Jan 2025 14:40:16 +0000 Subject: [PATCH 09/39] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (81 of 81 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/zh_Hans/ --- fastlane/metadata/android/zh-CN/changelogs/4213104.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/zh-CN/changelogs/4213104.txt diff --git a/fastlane/metadata/android/zh-CN/changelogs/4213104.txt b/fastlane/metadata/android/zh-CN/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..69a28dd68019fff1b5084304a3fbfaa6381d540c --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* 使用 SASL SCRAM 降级保护(XEP-0474) +* 将 MUC 私信中的回应发送至正确的 JID From 949c35a00e464557a49da8cee358983d0471a350 Mon Sep 17 00:00:00 2001 From: ghose Date: Sat, 25 Jan 2025 04:21:03 +0000 Subject: [PATCH 10/39] Translated using Weblate (Galician) Currently translated at 65.4% (53 of 81 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/gl/ --- fastlane/metadata/android/gl-ES/changelogs/4213104.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/gl-ES/changelogs/4213104.txt diff --git a/fastlane/metadata/android/gl-ES/changelogs/4213104.txt b/fastlane/metadata/android/gl-ES/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..3eae5234324d4a1e80ad4db963f7e65e36661129 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Uso de SASL SCRAM Downgrade Protection (XEP-0474) +* Envío de reaccións en MUC PMs ao JID correcto From 6d86614f51edec0f125d6cac098a2fa55b82bcd9 Mon Sep 17 00:00:00 2001 From: flactwin Date: Sat, 25 Jan 2025 09:12:39 +0000 Subject: [PATCH 11/39] Added translation using Weblate (Belarusian) --- src/conversations/res/values-be/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/conversations/res/values-be/strings.xml diff --git a/src/conversations/res/values-be/strings.xml b/src/conversations/res/values-be/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..a6b3daec9354f9ae75cdf8d94a67446c6227dd96 --- /dev/null +++ b/src/conversations/res/values-be/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 06a2e4296db0005f58ee018c3d33b1ad333efc30 Mon Sep 17 00:00:00 2001 From: flactwin Date: Sat, 25 Jan 2025 09:15:19 +0000 Subject: [PATCH 12/39] Added translation using Weblate (Belarusian) --- src/quicksy/res/values-be/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/quicksy/res/values-be/strings.xml diff --git a/src/quicksy/res/values-be/strings.xml b/src/quicksy/res/values-be/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..a6b3daec9354f9ae75cdf8d94a67446c6227dd96 --- /dev/null +++ b/src/quicksy/res/values-be/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 52f13927f70817735f692cbdb0e45df650893acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sat, 25 Jan 2025 09:22:03 +0000 Subject: [PATCH 13/39] Translated using Weblate (Estonian) Currently translated at 49.3% (40 of 81 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/et/ --- fastlane/metadata/android/et/changelogs/42046.txt | 1 + fastlane/metadata/android/et/changelogs/42047.txt | 1 + fastlane/metadata/android/et/changelogs/42050.txt | 1 + fastlane/metadata/android/et/changelogs/42059.txt | 2 ++ fastlane/metadata/android/et/changelogs/42060.txt | 1 + fastlane/metadata/android/et/changelogs/4213104.txt | 2 ++ 6 files changed, 8 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/42046.txt create mode 100644 fastlane/metadata/android/et/changelogs/42047.txt create mode 100644 fastlane/metadata/android/et/changelogs/42050.txt create mode 100644 fastlane/metadata/android/et/changelogs/42059.txt create mode 100644 fastlane/metadata/android/et/changelogs/42060.txt create mode 100644 fastlane/metadata/android/et/changelogs/4213104.txt diff --git a/fastlane/metadata/android/et/changelogs/42046.txt b/fastlane/metadata/android/et/changelogs/42046.txt new file mode 100644 index 0000000000000000000000000000000000000000..a9aa11eadd8566dd1df58a12dee1911d5dacd5b1 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42046.txt @@ -0,0 +1 @@ +* Lõimisime UnifiedPushi tõuketeenuste levitajana teistele UnifiedPushi kasutajatele nagu Tusky ja Fedilab diff --git a/fastlane/metadata/android/et/changelogs/42047.txt b/fastlane/metadata/android/et/changelogs/42047.txt new file mode 100644 index 0000000000000000000000000000000000000000..d65bd1e5f7a847b6e161263aa1414eaaa41c81d3 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42047.txt @@ -0,0 +1 @@ +* Parandasime kokkujooksmise, mis ilmnes UnifiedPushi kasutamisel tõuketeenuste levitajana diff --git a/fastlane/metadata/android/et/changelogs/42050.txt b/fastlane/metadata/android/et/changelogs/42050.txt new file mode 100644 index 0000000000000000000000000000000000000000..cc51ce1f3292e2e2403030f92a2f98d9f9e34a9f --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42050.txt @@ -0,0 +1 @@ +* Suurendasime profiilipiltide nurkade raadiust diff --git a/fastlane/metadata/android/et/changelogs/42059.txt b/fastlane/metadata/android/et/changelogs/42059.txt new file mode 100644 index 0000000000000000000000000000000000000000..1d47a1cf89d9109cb39cf389d7e915ebc9880918 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42059.txt @@ -0,0 +1,2 @@ +* Kompileerime jälle arvestades Androidi SDK versiooni 33 +* Parandasime vead serveritega, mis ilmnesid SASL2 kasutamisel ilma lõimitud meediavoo halduseta (inline Stream Management) diff --git a/fastlane/metadata/android/et/changelogs/42060.txt b/fastlane/metadata/android/et/changelogs/42060.txt new file mode 100644 index 0000000000000000000000000000000000000000..c19cd31ece12079fc84435ca2b500b5e96e42e67 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42060.txt @@ -0,0 +1 @@ +* Parandasime olukorra, kus „q“ tuvastus kirillitsana diff --git a/fastlane/metadata/android/et/changelogs/4213104.txt b/fastlane/metadata/android/et/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..3631b51f5702af963c7b212620ffd6a5d3b45048 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Võtsime kasutusele SASL SCRAM madaldusründe kaitse (SASL SCRAM Downgrade Protection XEP-0474) +* saadame jututoa sõnumite reakstioonid õigetele kasutajatele From 807dce23035b4c389ba88afaede29d953ff2f83d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sat, 25 Jan 2025 18:46:33 +0000 Subject: [PATCH 14/39] Translated using Weblate (Estonian) Currently translated at 51.8% (42 of 81 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/et/ --- fastlane/metadata/android/et/changelogs/42043.txt | 1 + fastlane/metadata/android/et/changelogs/42044.txt | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/42043.txt create mode 100644 fastlane/metadata/android/et/changelogs/42044.txt diff --git a/fastlane/metadata/android/et/changelogs/42043.txt b/fastlane/metadata/android/et/changelogs/42043.txt new file mode 100644 index 0000000000000000000000000000000000000000..5ce423f963fb158a8098804345ffa19489d64038 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42043.txt @@ -0,0 +1 @@ +* Parandasime regressiooni, mis tekkis failide teisaldamisel P2P-ühenduse kaudu diff --git a/fastlane/metadata/android/et/changelogs/42044.txt b/fastlane/metadata/android/et/changelogs/42044.txt new file mode 100644 index 0000000000000000000000000000000000000000..916dd4e19767dddd24d1f161ee8e775467ac50a2 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/42044.txt @@ -0,0 +1,3 @@ +* Parandasime sõnumite kordussaatmise SASL2 kasutamisel +* Parandasime musta videovaate tekkimise mõnede seadmete puhul +* Parandasime rakenduse kokkujooksmise tühja salasõna puhul From 67ba14b5b9514d62f4a61c47fa3c93be9b10e0ee Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 26 Jan 2025 14:01:27 +0100 Subject: [PATCH 15/39] reduce avatar cache invalidation --- .../siacs/conversations/entities/Contact.java | 11 ++-- .../conversations/entities/MucOptions.java | 19 +++---- .../conversations/parser/MessageParser.java | 22 +++++--- .../conversations/parser/PresenceParser.java | 54 ++++++++++--------- .../conversations/services/AvatarService.java | 54 ++++++++++--------- .../services/XmppConnectionService.java | 28 +++++----- .../conversations/ui/EnterNameActivity.java | 2 +- 7 files changed, 106 insertions(+), 84 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index b15381e58c0e63824c999b6a2cb55c29094496c7..6508d00a966b88fccc8c66e61095c9c49be3c30f 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -448,21 +448,22 @@ public class Contact implements ListItem, Blockable { return getJid().getDomain().toString(); } - public void setAvatar(Avatar avatar) { - setAvatar(avatar, false); + public boolean setAvatar(final Avatar avatar) { + return setAvatar(avatar, false); } - public void setAvatar(Avatar avatar, boolean previouslyOmittedPepFetch) { + public boolean setAvatar(final Avatar avatar, final boolean previouslyOmittedPepFetch) { if (this.avatar != null && this.avatar.equals(avatar)) { - return; + return false; } if (!previouslyOmittedPepFetch && this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) { - return; + return false; } this.avatar = avatar; + return true; } public String getAvatarFilename() { diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index a1edc2a036d7970f5d5370b7dd6cefc107fb3e24..1b68acf0fceb5810d9e0df31ea333a80f242a9d0 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -60,7 +60,7 @@ public class MucOptions { return this.conversation.getAccount(); } - public boolean setSelf(User user) { + public boolean setSelf(final User user) { this.self = user; final boolean roleChanged = this.conversation.setAttribute("role", user.role.toString()); final boolean affiliationChanged = @@ -68,8 +68,8 @@ public class MucOptions { return roleChanged || affiliationChanged; } - public void changeAffiliation(Jid jid, Affiliation affiliation) { - User user = findUserByRealJid(jid); + public void changeAffiliation(final Jid jid, final Affiliation affiliation) { + final User user = findUserByRealJid(jid); synchronized (users) { if (user != null && user.getRole() == Role.NONE) { users.remove(user); @@ -440,14 +440,15 @@ public class MucOptions { } } - public List getUsers(int max) { - ArrayList subset = new ArrayList<>(); - HashSet jids = new HashSet<>(); - jids.add(account.getJid().asBareJid()); + public List getUsers(final int max) { + final ArrayList subset = new ArrayList<>(); + final HashSet addresses = new HashSet<>(); + addresses.add(account.getJid().asBareJid()); synchronized (users) { for (User user : users) { if (user.getRealJid() == null - || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) { + || (user.getRealJid().getLocal() != null + && addresses.add(user.getRealJid()))) { subset.add(user); } if (subset.size() >= max) { @@ -877,7 +878,7 @@ public class MucOptions { } } - public boolean setAvatar(Avatar avatar) { + public boolean setAvatar(final Avatar avatar) { if (this.avatar != null && this.avatar.equals(avatar)) { return false; } else { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 9188c1b661ab828341f3fb9ec6363c44f517e581..226c7511474ee03a93bda323d8e9c9f9deed953a 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -266,11 +266,12 @@ public class MessageParser extends AbstractParser mXmppConnectionService.updateAccountUi(); } else { final Contact contact = account.getRoster().getContact(from); - contact.setAvatar(avatar); - mXmppConnectionService.syncRoster(account); - mXmppConnectionService.getAvatarService().clear(contact); - mXmppConnectionService.updateConversationUi(); - mXmppConnectionService.updateRosterUi(); + if (contact.setAvatar(avatar)) { + mXmppConnectionService.syncRoster(account); + mXmppConnectionService.getAvatarService().clear(contact); + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.updateRosterUi(); + } } } else if (mXmppConnectionService.isDataSaverDisabled()) { mXmppConnectionService.fetchAvatar(account, avatar); @@ -1174,7 +1175,7 @@ public class MessageParser extends AbstractParser // ignored } } else if ("item".equals(child.getName())) { - MucOptions.User user = AbstractParser.parseItem(conversation, child); + final var user = AbstractParser.parseItem(conversation, child); Log.d( Config.LOGTAG, account.getJid() @@ -1185,8 +1186,13 @@ public class MessageParser extends AbstractParser + " in " + conversation.getJid().asBareJid()); if (!user.realJidMatchesAccount()) { - boolean isNew = conversation.getMucOptions().updateUser(user); - mXmppConnectionService.getAvatarService().clear(conversation); + final var mucOptions = conversation.getMucOptions(); + final boolean isNew = mucOptions.updateUser(user); + final var avatarService = mXmppConnectionService.getAvatarService(); + if (Strings.isNullOrEmpty(mucOptions.getAvatar())) { + avatarService.clear(mucOptions); + } + avatarService.clear(user); mXmppConnectionService.updateMucRosterUi(); mXmppConnectionService.updateConversationUi(); Contact contact = user.getContact(); diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 088938402ec24c4e6958dbd5660bfcddbf9a8fec..eef615de358c79ba745f5695913b476d8def213a 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -1,6 +1,7 @@ package eu.siacs.conversations.parser; import android.util.Log; +import com.google.common.base.Strings; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.crypto.axolotl.AxolotlService; @@ -37,22 +38,24 @@ public class PresenceParser extends AbstractParser packet.getFrom() == null ? null : mXmppConnectionService.find(account, packet.getFrom().asBareJid()); - if (conversation != null) { - final MucOptions mucOptions = conversation.getMucOptions(); - boolean before = mucOptions.online(); - int count = mucOptions.getUserCount(); - final List tileUserBefore = mucOptions.getUsers(5); - processConferencePresence(packet, conversation); - final List tileUserAfter = mucOptions.getUsers(5); - if (!tileUserAfter.equals(tileUserBefore)) { - mXmppConnectionService.getAvatarService().clear(mucOptions); - } - if (before != mucOptions.online() - || (mucOptions.online() && count != mucOptions.getUserCount())) { - mXmppConnectionService.updateConversationUi(); - } else if (mucOptions.online()) { - mXmppConnectionService.updateMucRosterUi(); - } + if (conversation == null) { + return; + } + final MucOptions mucOptions = conversation.getMucOptions(); + boolean before = mucOptions.online(); + int count = mucOptions.getUserCount(); + final List tileUserBefore = mucOptions.getUsers(5); + processConferencePresence(packet, conversation); + final List tileUserAfter = mucOptions.getUsers(5); + if (Strings.isNullOrEmpty(mucOptions.getAvatar()) + && !tileUserAfter.equals(tileUserBefore)) { + mXmppConnectionService.getAvatarService().clear(mucOptions); + } + if (before != mucOptions.online() + || (mucOptions.online() && count != mucOptions.getUserCount())) { + mXmppConnectionService.updateConversationUi(); + } else if (mucOptions.online()) { + mXmppConnectionService.updateMucRosterUi(); } } @@ -152,9 +155,11 @@ public class PresenceParser extends AbstractParser .getAccount() .getRoster() .getContact(user.getRealJid()); - c.setAvatar(avatar); - mXmppConnectionService.syncRoster(conversation.getAccount()); - mXmppConnectionService.getAvatarService().clear(c); + if (c.setAvatar(avatar)) { + mXmppConnectionService.syncRoster( + conversation.getAccount()); + mXmppConnectionService.getAvatarService().clear(c); + } mXmppConnectionService.updateRosterUi(); } } else if (mXmppConnectionService.isDataSaverDisabled()) { @@ -329,11 +334,12 @@ public class PresenceParser extends AbstractParser mXmppConnectionService.updateConversationUi(); mXmppConnectionService.updateAccountUi(); } else { - contact.setAvatar(avatar); - mXmppConnectionService.syncRoster(account); - mXmppConnectionService.getAvatarService().clear(contact); - mXmppConnectionService.updateConversationUi(); - mXmppConnectionService.updateRosterUi(); + if (contact.setAvatar(avatar)) { + mXmppConnectionService.syncRoster(account); + mXmppConnectionService.getAvatarService().clear(contact); + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.updateRosterUi(); + } } } else if (mXmppConnectionService.isDataSaverDisabled()) { mXmppConnectionService.fetchAvatar(account, avatar); diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index 46021557624fff321a0cda32724cb2f7072ce701..4f2bdc2d69ae128f2db524f28ceb61c60cb65d79 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -15,10 +15,10 @@ import android.net.Uri; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; -import android.util.LruCache; import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import androidx.core.content.res.ResourcesCompat; +import com.google.common.base.Strings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -57,6 +57,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { private static final String CHANNEL_SYMBOL = "#"; private final Set sizes = new HashSet<>(); + // TODO refactor to multimap private final HashMap> conversationDependentKeys = new HashMap<>(); protected XmppConnectionService mXmppConnectionService = null; @@ -261,23 +262,27 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { return avatar; } - public void clear(Contact contact) { + public void clear(final Contact contact) { synchronized (this.sizes) { for (final Integer size : sizes) { this.mXmppConnectionService.getBitmapCache().remove(key(contact, size)); } } - for (Conversation conversation : mXmppConnectionService.findAllConferencesWith(contact)) { - MucOptions.User user = - conversation.getMucOptions().findUserByRealJid(contact.getJid().asBareJid()); + for (final Conversation conversation : + mXmppConnectionService.findAllConferencesWith(contact)) { + final var mucOptions = conversation.getMucOptions(); + final var user = mucOptions.findUserByRealJid(contact.getJid().asBareJid()); if (user != null) { clear(user); } - clear(conversation); + if (Strings.isNullOrEmpty(mucOptions.getAvatar()) + && mucOptions.isPrivateAndNonAnonymous()) { + clear(mucOptions); + } } } - private String key(Contact contact, int size) { + private String key(final Contact contact, final int size) { synchronized (this.sizes) { this.sizes.add(size); } @@ -345,26 +350,15 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { } } - public void clear(Conversation conversation) { + public void clear(final Conversation conversation) { if (conversation.getMode() == Conversation.MODE_SINGLE) { clear(conversation.getContact()); } else { clear(conversation.getMucOptions()); - synchronized (this.conversationDependentKeys) { - Set keys = this.conversationDependentKeys.get(conversation.getUuid()); - if (keys == null) { - return; - } - LruCache cache = this.mXmppConnectionService.getBitmapCache(); - for (String key : keys) { - cache.remove(key); - } - keys.clear(); - } } } - private Bitmap get(MucOptions mucOptions, int size, boolean cachedOnly) { + private Bitmap get(final MucOptions mucOptions, final int size, final boolean cachedOnly) { final String KEY = key(mucOptions, size); Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); if (bitmap != null || cachedOnly) { @@ -377,7 +371,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { Conversation c = mucOptions.getConversation(); if (mucOptions.isPrivateAndNonAnonymous()) { final List users = mucOptions.getUsersRelevantForNameAndAvatar(); - if (users.size() == 0) { + if (users.isEmpty()) { bitmap = getImpl( c.getName().toString(), @@ -456,12 +450,12 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid() + "_" + size; } - private String key(List users, int size) { + private String key(final List users, final int size) { final Conversation conversation = users.get(0).getConversation(); StringBuilder builder = new StringBuilder("TILE_"); builder.append(conversation.getUuid()); - for (MucOptions.User user : users) { + for (final MucOptions.User user : users) { builder.append("\0"); builder.append(emptyOnNull(user.getRealJid())); builder.append("\0"); @@ -551,12 +545,24 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { } } - public void clear(MucOptions.User user) { + public void clear(final MucOptions.User user) { synchronized (this.sizes) { for (Integer size : sizes) { this.mXmppConnectionService.getBitmapCache().remove(key(user, size)); } } + synchronized (this.conversationDependentKeys) { + final Set keys = + this.conversationDependentKeys.get(user.getConversation().getUuid()); + if (keys == null) { + return; + } + final var cache = this.mXmppConnectionService.getBitmapCache(); + for (final String key : keys) { + cache.remove(key); + } + keys.clear(); + } } private String key(Account account, int size) { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 9a5ea426f7bd80f1126ff2f204878eb5ceff15b5..0ff7dbe6fceb11fd24763ee7b8cfb8b3785da4e2 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -3455,15 +3455,15 @@ public class XmppConnectionService extends Service { } public boolean checkListeners() { - return (this.mOnAccountUpdates.size() == 0 - && this.mOnConversationUpdates.size() == 0 - && this.mOnRosterUpdates.size() == 0 - && this.mOnCaptchaRequested.size() == 0 - && this.mOnMucRosterUpdate.size() == 0 - && this.mOnUpdateBlocklist.size() == 0 - && this.mOnShowErrorToasts.size() == 0 - && this.onJingleRtpConnectionUpdate.size() == 0 - && this.mOnKeyStatusUpdated.size() == 0); + return (this.mOnAccountUpdates.isEmpty() + && this.mOnConversationUpdates.isEmpty() + && this.mOnRosterUpdates.isEmpty() + && this.mOnCaptchaRequested.isEmpty() + && this.mOnMucRosterUpdate.isEmpty() + && this.mOnUpdateBlocklist.isEmpty() + && this.mOnShowErrorToasts.isEmpty() + && this.onJingleRtpConnectionUpdate.isEmpty() + && this.mOnKeyStatusUpdated.isEmpty()); } private void switchToForeground() { @@ -3815,7 +3815,8 @@ public class XmppConnectionService extends Service { } ++i; if (i >= affiliations.length) { - List members = conversation.getMucOptions().getMembers(true); + final var mucOptions = conversation.getMucOptions(); + final var members = mucOptions.getMembers(true); if (success) { List cryptoTargets = conversation.getAcceptedCryptoTargets(); boolean changed = false; @@ -3840,7 +3841,7 @@ public class XmppConnectionService extends Service { updateConversation(conversation); } } - getAvatarService().clear(conversation); + getAvatarService().clear(mucOptions); updateMucRosterUi(); updateConversationUi(); } @@ -4487,8 +4488,9 @@ public class XmppConnectionService extends Service { request, (response) -> { if (response.getType() == Iq.Type.RESULT) { - conference.getMucOptions().changeAffiliation(jid, affiliation); - getAvatarService().clear(conference); + final var mucOptions = conference.getMucOptions(); + mucOptions.changeAffiliation(jid, affiliation); + getAvatarService().clear(mucOptions); if (callback != null) { callback.onAffiliationChangedSuccessful(jid); } else { diff --git a/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java b/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java index 38d1f0db49d1e4dbf87a573244262d4a5b6ea3da..42c2d3c6fc453e4b1bab68ec011ab9f432728210 100644 --- a/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java +++ b/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java @@ -54,7 +54,7 @@ public class EnterNameActivity extends XmppActivity } @Override - public void onSaveInstanceState(Bundle savedInstanceState) { + public void onSaveInstanceState(final Bundle savedInstanceState) { savedInstanceState.putBoolean("set_nick", this.setNick.get()); super.onSaveInstanceState(savedInstanceState); } From 5cd410e92e1b7ca08d67508ed3fd3816a2f83608 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 27 Jan 2025 11:00:18 +0100 Subject: [PATCH 16/39] try to recover conversation by shortcut id --- .../persistance/DatabaseBackend.java | 22 ++++++++++++++++ .../conversations/services/AvatarService.java | 10 +------ .../services/ShortcutService.java | 13 +++++++--- .../conversations/ui/ShareWithActivity.java | 26 +++++++++++++++++-- 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 4982a4463406e91fb9901fe39b0ea65be61b82dc..0ff55672cff82c01c0ad574cc62799fc0d91bc5e 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -1656,6 +1656,28 @@ public class DatabaseBackend extends SQLiteOpenHelper { } } + public String findConversationUuid(final Jid account, final Jid jid) { + final SQLiteDatabase db = this.getReadableDatabase(); + final String[] selectionArgs = { + account.getLocal(), + account.getDomain().toString(), + jid.asBareJid().toString() + "/%", + jid.asBareJid().toString() + }; + try (final Cursor cursor = + db.rawQuery( + "SELECT conversations.uuid FROM conversations JOIN accounts ON" + + " conversations.accountUuid=accounts.uuid WHERE accounts.username=?" + + " AND accounts.server=? AND (contactJid=? OR contactJid LIKE ?)", + selectionArgs)) { + if (cursor.getCount() == 0) { + return null; + } + cursor.moveToFirst(); + return cursor.getString(0); + } + } + public void updateConversation(final Conversation conversation) { final SQLiteDatabase db = this.getWritableDatabase(); final String[] args = {conversation.getUuid()}; diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index 4f2bdc2d69ae128f2db524f28ceb61c60cb65d79..941914a92ea7b7a6abde01eda8b7615ba5aae6e6 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -342,7 +342,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { return get(conversation, size, false); } - public Bitmap get(Conversation conversation, int size, boolean cachedOnly) { + public Bitmap get(final Conversation conversation, final int size, final boolean cachedOnly) { if (conversation.getMode() == Conversation.MODE_SINGLE) { return get(conversation.getContact(), size, cachedOnly); } else { @@ -350,14 +350,6 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { } } - public void clear(final Conversation conversation) { - if (conversation.getMode() == Conversation.MODE_SINGLE) { - clear(conversation.getContact()); - } else { - clear(conversation.getMucOptions()); - } - } - private Bitmap get(final MucOptions mucOptions, final int size, final boolean cachedOnly) { final String KEY = key(mucOptions, size); Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); diff --git a/src/main/java/eu/siacs/conversations/services/ShortcutService.java b/src/main/java/eu/siacs/conversations/services/ShortcutService.java index 132f7091200d3df424b50086f1bf8c2f6e84a008..b77020a231a40e39e8c94c3d1889aeaad2999931 100644 --- a/src/main/java/eu/siacs/conversations/services/ShortcutService.java +++ b/src/main/java/eu/siacs/conversations/services/ShortcutService.java @@ -12,6 +12,7 @@ import androidx.annotation.NonNull; import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.content.pm.ShortcutManagerCompat; import androidx.core.graphics.drawable.IconCompat; +import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -29,6 +30,8 @@ import java.util.List; public class ShortcutService { + public static final char ID_SEPARATOR = '#'; + private final XmppConnectionService xmppConnectionService; private final ReplacingSerialSingleThreadExecutor replacingSerialSingleThreadExecutor = new ReplacingSerialSingleThreadExecutor(ShortcutService.class.getSimpleName()); @@ -159,15 +162,17 @@ public class ShortcutService { } private static String getShortcutId(final Contact contact) { - return contact.getAccount().getJid().asBareJid().toString() - + "#" - + contact.getJid().asBareJid().toString(); + return Joiner.on(ID_SEPARATOR) + .join( + contact.getAccount().getJid().asBareJid().toString(), + contact.getJid().asBareJid().toString()); } private static String getShortcutId(final MucOptions mucOptions) { final Account account = mucOptions.getAccount(); final Jid jid = mucOptions.getConversation().getJid(); - return account.getJid().asBareJid().toString() + "#" + jid.asBareJid().toString(); + return Joiner.on(ID_SEPARATOR) + .join(account.getJid().asBareJid().toString(), jid.asBareJid().toString()); } private Intent getShortcutIntent(final MucOptions mucOptions) { diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index a081f3755a7db211a1a9334a58efe7831431afe7..b6937cbebc935c91fad3fa289790b4d5d2919a24 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -12,12 +12,16 @@ import androidx.annotation.NonNull; import androidx.core.content.pm.ShortcutManagerCompat; import androidx.databinding.DataBindingUtil; import androidx.recyclerview.widget.LinearLayoutManager; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; import com.google.common.collect.Iterables; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityShareWithBinding; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.persistance.DatabaseBackend; +import eu.siacs.conversations.services.ShortcutService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.adapter.ConversationAdapter; import eu.siacs.conversations.xmpp.Jid; @@ -132,10 +136,28 @@ public class ShareWithActivity extends XmppActivity if (shortcut.isPresent()) { final var extras = shortcut.get().getExtras(); if (extras == null) { - return null; + return shortcutIdToConversationFallback(shortcutId); } else { - return extras.getString(ConversationsActivity.EXTRA_CONVERSATION); + final var conversation = extras.getString(ConversationsActivity.EXTRA_CONVERSATION); + if (Strings.isNullOrEmpty(conversation)) { + return shortcutIdToConversationFallback(shortcutId); + } else { + return conversation; + } } + } else { + return shortcutIdToConversationFallback(shortcutId); + } + } + + private String shortcutIdToConversationFallback(final String shortcutId) { + final var parts = + Splitter.on(ShortcutService.ID_SEPARATOR).limit(2).splitToList(shortcutId); + if (parts.size() == 2) { + final var account = Jid.of(parts.get(0)); + final var jid = Jid.of(parts.get(1)); + final var database = DatabaseBackend.getInstance(getApplicationContext()); + return database.findConversationUuid(account, jid); } else { return null; } From 9816cda0f0670368db3d776515e38095e2535ac5 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 27 Jan 2025 14:42:03 +0100 Subject: [PATCH 17/39] use outline button for play button in voice message --- .../conversations/ui/service/AudioPlayer.java | 45 ++++++++----------- src/main/res/layout/item_message_content.xml | 12 +++-- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java b/src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java index cb8b591d7ee661d4e781d58f51559caf2e88bdc0..07507c77dfc364ddf5313b189fa815cf3565786f 100644 --- a/src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java +++ b/src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java @@ -14,16 +14,13 @@ import android.os.Handler; import android.os.PowerManager; import android.util.Log; import android.view.View; -import android.widget.ImageButton; import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; - import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; - +import com.google.android.material.button.MaterialButton; import com.google.common.primitives.Ints; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Message; @@ -33,7 +30,6 @@ import eu.siacs.conversations.ui.adapter.MessageAdapter; import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.utils.TimeFrameUtils; import eu.siacs.conversations.utils.WeakReferenceSet; - import java.lang.ref.WeakReference; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -54,7 +50,8 @@ public class AudioPlayer private final WeakReferenceSet audioPlayerLayouts = new WeakReferenceSet<>(); private final SensorManager sensorManager; private final Sensor proximitySensor; - private final PendingItem> pendingOnClickView = new PendingItem<>(); + private final PendingItem> pendingOnClickView = + new PendingItem<>(); private final ExecutorService executor = Executors.newSingleThreadExecutor(); @@ -81,7 +78,7 @@ public class AudioPlayer } private static String formatTime(final int ms) { - return TimeFrameUtils.formatElapsedTime(ms,false); + return TimeFrameUtils.formatElapsedTime(ms, false); } private void initializeProximityWakeLock(Context context) { @@ -124,20 +121,17 @@ public class AudioPlayer final Context context = viewHolder.playPause.getContext(); if (message == currentlyPlayingMessage) { if (AudioPlayer.player != null && AudioPlayer.player.isPlaying()) { - viewHolder.playPause.setImageResource(R.drawable.ic_pause_24dp); - MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor); + viewHolder.playPause.setIconResource(R.drawable.ic_pause_24dp); viewHolder.playPause.setContentDescription(context.getString(R.string.pause_audio)); viewHolder.progress.setEnabled(true); } else { viewHolder.playPause.setContentDescription(context.getString(R.string.play_audio)); - viewHolder.playPause.setImageResource(R.drawable.ic_play_arrow_24dp); - MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor); + viewHolder.playPause.setIconResource(R.drawable.ic_play_arrow_24dp); viewHolder.progress.setEnabled(false); } return true; } else { - viewHolder.playPause.setImageResource(R.drawable.ic_play_arrow_24dp); - MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor); + viewHolder.playPause.setIconResource(R.drawable.ic_play_arrow_24dp); viewHolder.playPause.setContentDescription(context.getString(R.string.play_audio)); viewHolder.runtime.setText(formatTime(message.getFileParams().runtime)); viewHolder.progress.setProgress(0); @@ -150,12 +144,12 @@ public class AudioPlayer public synchronized void onClick(View v) { if (v.getId() == R.id.play_pause) { synchronized (LOCK) { - startStop((ImageButton) v); + startStop((MaterialButton) v); } } } - private void startStop(ImageButton playPause) { + private void startStop(final MaterialButton playPause) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission( messageAdapter.getActivity(), @@ -186,8 +180,7 @@ public class AudioPlayer player.pause(); messageAdapter.flagScreenOff(); releaseProximityWakeLock(); - viewHolder.playPause.setImageResource(R.drawable.ic_play_arrow_24dp); - MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor); + viewHolder.playPause.setIconResource(R.drawable.ic_play_arrow_24dp); viewHolder.playPause.setContentDescription(context.getString(R.string.play_audio)); } else { viewHolder.progress.setEnabled(true); @@ -195,8 +188,7 @@ public class AudioPlayer messageAdapter.flagScreenOn(); acquireProximityWakeLock(); this.stopRefresher(true); - viewHolder.playPause.setImageResource(R.drawable.ic_pause_24dp); - MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor); + viewHolder.playPause.setIconResource(R.drawable.ic_pause_24dp); viewHolder.playPause.setContentDescription(context.getString(R.string.pause_audio)); } return false; @@ -222,8 +214,7 @@ public class AudioPlayer messageAdapter.flagScreenOn(); acquireProximityWakeLock(); viewHolder.progress.setEnabled(true); - viewHolder.playPause.setImageResource(R.drawable.ic_pause_24dp); - MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor); + viewHolder.playPause.setIconResource(R.drawable.ic_pause_24dp); viewHolder.playPause.setContentDescription( viewHolder.playPause.getContext().getString(R.string.pause_audio)); sensorManager.registerListener( @@ -239,9 +230,9 @@ public class AudioPlayer } public void startStopPending() { - WeakReference reference = pendingOnClickView.pop(); + final var reference = pendingOnClickView.pop(); if (reference != null) { - ImageButton imageButton = reference.get(); + var imageButton = reference.get(); if (imageButton != null) { startStop(imageButton); } @@ -283,8 +274,7 @@ public class AudioPlayer final Message message = (Message) audioPlayer.getTag(); viewHolder.playPause.setContentDescription( viewHolder.playPause.getContext().getString(R.string.play_audio)); - viewHolder.playPause.setImageResource(R.drawable.ic_play_arrow_24dp); - MessageAdapter.setImageTint(viewHolder.playPause, viewHolder.bubbleColor); + viewHolder.playPause.setIconResource(R.drawable.ic_play_arrow_24dp); if (message != null) { viewHolder.runtime.setText(formatTime(message.getFileParams().runtime)); } @@ -309,7 +299,8 @@ public class AudioPlayer } @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + public void onProgressChanged( + final SeekBar seekBar, final int progress, final boolean fromUser) { synchronized (AudioPlayer.LOCK) { final RelativeLayout audioPlayer = (RelativeLayout) seekBar.getParent(); final Message message = (Message) audioPlayer.getTag(); @@ -461,7 +452,7 @@ public class AudioPlayer public static class ViewHolder { private TextView runtime; private SeekBar progress; - private ImageButton playPause; + private MaterialButton playPause; private MessageAdapter.BubbleColor bubbleColor = MessageAdapter.BubbleColor.SURFACE; public static ViewHolder get(final RelativeLayout audioPlayer) { diff --git a/src/main/res/layout/item_message_content.xml b/src/main/res/layout/item_message_content.xml index b34343910b8a5c4209d190c29beeb143650bc7ac..06ace848307827b5ed05bf6121a90e925147d313 100644 --- a/src/main/res/layout/item_message_content.xml +++ b/src/main/res/layout/item_message_content.xml @@ -1,6 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> @@ -47,14 +48,17 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="10dp" android:layout_marginVertical="4dp" - android:visibility="gone"> + android:visibility="gone" + tools:visibility="visible"> - + app:iconSize="26dp" + app:icon="@drawable/ic_play_arrow_24dp" /> Date: Mon, 27 Jan 2025 14:56:46 +0100 Subject: [PATCH 18/39] bump XEP-0474 to 0.4.0 --- conversations.doap | 2 +- .../conversations/crypto/sasl/DowngradeProtection.java | 6 +++--- .../siacs/conversations/crypto/sasl/ScramMechanism.java | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/conversations.doap b/conversations.doap index fa2427d14dc28ab819841180ee32dfacfd371a0f..838a248d0634881d7a600a8d182173d78bc2253b 100644 --- a/conversations.doap +++ b/conversations.doap @@ -494,7 +494,7 @@ complete - 0.3.1 + 0.4.0 diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/DowngradeProtection.java b/src/main/java/eu/siacs/conversations/crypto/sasl/DowngradeProtection.java index 6daaa8809398e6f4aa2c7d311ed894fc0ae05299..a1934c9e0d3a791aa73f4afb06bff2f0bd4df657 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/DowngradeProtection.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/DowngradeProtection.java @@ -9,8 +9,8 @@ import java.util.Collection; public class DowngradeProtection { - private static final char SEPARATOR = ','; - private static final char SEPARATOR_MECHANISM_AND_BINDING = '|'; + private static final char SEPARATOR = 0x1E; + private static final char SEPARATOR_MECHANISM_AND_BINDING = 0x1F; public final ImmutableList mechanisms; public final ImmutableList channelBindings; @@ -26,7 +26,7 @@ public class DowngradeProtection { this.channelBindings = null; } - public String asDString() { + public String asHString() { ensureSaslMechanismFormat(this.mechanisms); ensureNoSeparators(this.mechanisms); if (this.channelBindings != null) { diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java index 97ae1600ecfe8a95d4d34b7fc8253174110f3b84..0ee9b879c40fce893ab391d1182e727c8e9a86f7 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java @@ -183,7 +183,7 @@ public abstract class ScramMechanism extends SaslMechanism { final String i = attributes.get("i"); final String s = attributes.get("s"); final String nonce = attributes.get("r"); - final String d = attributes.get("d"); + final String h = attributes.get("h"); if (Strings.isNullOrEmpty(s) || Strings.isNullOrEmpty(nonce) || Strings.isNullOrEmpty(i)) { throw new AuthenticationException("Missing attributes from server first message"); } @@ -205,15 +205,15 @@ public abstract class ScramMechanism extends SaslMechanism { throw new AuthenticationException("Invalid salt in server first message"); } - if (d != null && this.downgradeProtection != null) { + if (h != null && this.downgradeProtection != null) { final String asSeenInFeatures; try { - asSeenInFeatures = downgradeProtection.asDString(); + asSeenInFeatures = downgradeProtection.asHString(); } catch (final SecurityException e) { throw new AuthenticationException(e); } final var hashed = BaseEncoding.base64().encode(digest(asSeenInFeatures.getBytes())); - if (!hashed.equals(d)) { + if (!hashed.equals(h)) { throw new AuthenticationException("Mismatch in SSDP"); } } From 49ad6d642ec79d45c68138ee40beb3920f6ba362 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 27 Jan 2025 16:24:19 +0100 Subject: [PATCH 19/39] include reactions namespace in disco --- .../eu/siacs/conversations/generator/AbstractGenerator.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index 9d94813e72b26c6b97316c5dead77d30d812ccb4..de092f5978126a75e42ae4298b6c95bb712e4f58 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -1,7 +1,6 @@ package eu.siacs.conversations.generator; import android.util.Base64; - import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -10,7 +9,6 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.XmppConnection; - import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; @@ -40,7 +38,8 @@ public abstract class AbstractGenerator { Namespace.NICK + "+notify", "urn:xmpp:ping", "jabber:iq:version", - "http://jabber.org/protocol/chatstates" + "http://jabber.org/protocol/chatstates", + Namespace.REACTIONS }; private final String[] MESSAGE_CONFIRMATION_FEATURES = { "urn:xmpp:chat-markers:0", "urn:xmpp:receipts" From d6996c8d893fda39f1a761b31e2edbea0edf3aae Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 27 Jan 2025 16:25:57 +0100 Subject: [PATCH 20/39] add occupant id to outgoing MUC pm --- .../siacs/conversations/entities/Message.java | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 5b6f0e2aecbbb1b4f0cb061223ff74f5b8c9bf30..d0262b25d50dd6b3d4fc71289c232304fe7774f2 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -950,8 +950,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable return encryption; } - public static boolean configurePrivateMessage(final Message message) { - return configurePrivateMessage(message, false); + public static void configurePrivateMessage(final Message message) { + configurePrivateMessage(message, false); } public static boolean configurePrivateFileMessage(final Message message) { @@ -959,27 +959,19 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } private static boolean configurePrivateMessage(final Message message, final boolean isFile) { - final Conversation conversation; - if (message.conversation instanceof Conversation) { - conversation = (Conversation) message.conversation; - } else { - return false; - } - if (conversation.getMode() == Conversation.MODE_MULTI) { - final Jid nextCounterpart = conversation.getNextCounterpart(); - return configurePrivateMessage(conversation, message, nextCounterpart, isFile); + if (message.conversation instanceof Conversation conversation) { + if (conversation.getMode() == Conversation.MODE_MULTI) { + final Jid nextCounterpart = conversation.getNextCounterpart(); + return configurePrivateMessage(conversation, message, nextCounterpart, isFile); + } } return false; } - public static boolean configurePrivateMessage(final Message message, final Jid counterpart) { - final Conversation conversation; - if (message.conversation instanceof Conversation) { - conversation = (Conversation) message.conversation; - } else { - return false; + public static void configurePrivateMessage(final Message message, final Jid counterpart) { + if (message.conversation instanceof Conversation conversation) { + configurePrivateMessage(conversation, message, counterpart, false); } - return configurePrivateMessage(conversation, message, counterpart, false); } private static boolean configurePrivateMessage( @@ -991,7 +983,16 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable return false; } message.setCounterpart(counterpart); - message.setTrueCounterpart(conversation.getMucOptions().getTrueCounterpart(counterpart)); + final var mucOptions = conversation.getMucOptions(); + if (counterpart.equals(mucOptions.getSelf().getFullJid())) { + message.setTrueCounterpart(conversation.getAccount().getJid().asBareJid()); + } else { + final var user = mucOptions.findUserByFullJid(counterpart); + if (user != null) { + message.setTrueCounterpart(user.getRealJid()); + message.setOccupantId(user.getOccupantId()); + } + } message.setType(isFile ? Message.TYPE_PRIVATE_FILE : Message.TYPE_PRIVATE); return true; } From 34940ad9badd52af154127577146a42ed635d2e4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 27 Jan 2025 22:39:45 +0100 Subject: [PATCH 21/39] add media icon for spreadsheets --- .../conversations/ui/adapter/MediaAdapter.java | 12 ++++++++---- src/main/res/drawable/ic_description_48dp.xml | 15 +++++++++++---- src/main/res/drawable/ic_table_48dp.xml | 10 ++++++++++ 3 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 src/main/res/drawable/ic_table_48dp.xml 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 0a84e53ca863328e2838f405e95bfb6ab8a3ed2e..cba0596ea86951535fdae32a62a4ff48ecaabf3f 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java @@ -10,24 +10,20 @@ import android.os.AsyncTask; import android.view.LayoutInflater; import android.view.ViewGroup; import android.widget.ImageView; - import androidx.annotation.DimenRes; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.core.widget.ImageViewCompat; import androidx.databinding.DataBindingUtil; import androidx.recyclerview.widget.RecyclerView; - import com.google.android.material.color.MaterialColors; import com.google.common.base.Strings; - import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ItemMediaBinding; import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.ui.util.Attachment; import eu.siacs.conversations.ui.util.ViewUtil; import eu.siacs.conversations.worker.ExportBackupWorker; - import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; @@ -44,6 +40,12 @@ public class MediaAdapter extends RecyclerView.Adapter SPREAD_SHEET_MIMES = + Arrays.asList( + "text/comma-separated-values", + "application/vnd.ms-excel", + "application/vnd.oasis.opendocument.spreadsheet", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); private static final List ARCHIVE_MIMES = Arrays.asList( @@ -111,6 +113,8 @@ public class MediaAdapter extends RecyclerView.Adapter - - - + + + + diff --git a/src/main/res/drawable/ic_table_48dp.xml b/src/main/res/drawable/ic_table_48dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..1d356de74488e18918ab7e08526472cd87292e05 --- /dev/null +++ b/src/main/res/drawable/ic_table_48dp.xml @@ -0,0 +1,10 @@ + + + From c4030bc24024c95ee0e5d307c69c25bdebbd0b2e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 Jan 2025 13:12:13 +0100 Subject: [PATCH 22/39] parse reactions from MUC PMS --- .../conversations/parser/MessageParser.java | 71 ++++++++++++++----- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 226c7511474ee03a93bda323d8e9c9f9deed953a..7e22df067a348bcec31b3d2ba84d9f413b382cf6 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -602,6 +602,16 @@ public class MessageParser extends AbstractParser ? mucUserElement : null, mucTrueCounterPartByPresence); + } else if (mucUserElement != null) { + final Conversation conversation = + mXmppConnectionService.find(account, from.asBareJid()); + if (conversation != null) { + final var mucOptions = conversation.getMucOptions(); + occupant = mucOptions.occupantId() ? packet.getExtension(OccupantId.class) : null; + } else { + occupant = null; + } + mucTrueCounterPart = null; } else { mucTrueCounterPart = null; occupant = null; @@ -1567,7 +1577,7 @@ public class MessageParser extends AbstractParser } else { Log.d(Config.LOGTAG, "received reaction in channel w/o occupant ids. ignoring"); } - } else if (conversation.getMode() == Conversational.MODE_SINGLE) { + } else { final Message message; final var inMemoryMessage = conversation.findMessageWithUuidOrRemoteId(reactingTo); if (inMemoryMessage != null) { @@ -1577,28 +1587,51 @@ public class MessageParser extends AbstractParser mXmppConnectionService.databaseBackend.getMessageWithUuidOrRemoteId( conversation, reactingTo); } + if (message == null) { + Log.d(Config.LOGTAG, "message with id " + reactingTo + " not found"); + return; + } final boolean isReceived; final Jid reactionFrom; - if (packet.fromAccount(account)) { - isReceived = false; - reactionFrom = account.getJid().asBareJid(); - } else { - isReceived = true; - reactionFrom = counterpart; - } - packet.fromAccount(account); - if (message != null) { - final var combinedReactions = - Reaction.withFrom( - message.getReactions(), - reactions.getReactions(), - isReceived, - reactionFrom); - message.setReactions(combinedReactions); - mXmppConnectionService.updateMessage(message, false); + if (conversation.getMode() == Conversational.MODE_MULTI) { + Log.d(Config.LOGTAG, "received reaction as MUC PM. triggering validation"); + final var mucOptions = conversation.getMucOptions(); + final var occupantId = occupant == null ? null : occupant.getId(); + if (occupantId == null) { + Log.d( + Config.LOGTAG, + "received reaction via PM channel w/o occupant ids. ignoring"); + return; + } + isReceived = !mucOptions.isSelf(occupantId); + if (isReceived) { + reactionFrom = counterpart; + } else { + if (!occupantId.equals(message.getOccupantId())) { + Log.d( + Config.LOGTAG, + "reaction received via MUC PM did not pass validation"); + return; + } + reactionFrom = account.getJid().asBareJid(); + } } else { - Log.d(Config.LOGTAG, "message with id " + reactingTo + " not found"); + if (packet.fromAccount(account)) { + isReceived = false; + reactionFrom = account.getJid().asBareJid(); + } else { + isReceived = true; + reactionFrom = counterpart; + } } + final var combinedReactions = + Reaction.withFrom( + message.getReactions(), + reactions.getReactions(), + isReceived, + reactionFrom); + message.setReactions(combinedReactions); + mXmppConnectionService.updateMessage(message, false); } } } From 6dde58681cce30eb299e866e8ee29313f015e660 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 Jan 2025 15:53:38 +0100 Subject: [PATCH 23/39] add media preview icon for slide show --- .../ui/adapter/MediaAdapter.java | 11 +++ .../siacs/conversations/utils/MimeUtils.java | 70 ++++++++++--------- src/main/res/drawable/ic_slideshow_48dp.xml | 12 ++++ 3 files changed, 60 insertions(+), 33 deletions(-) create mode 100644 src/main/res/drawable/ic_slideshow_48dp.xml 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 cba0596ea86951535fdae32a62a4ff48ecaabf3f..de4b1f4626723de5c4e281daaeecdc39e6f8814d 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java @@ -44,9 +44,18 @@ public class MediaAdapter extends RecyclerView.Adapter SLIDE_SHOW_MIMES = + Arrays.asList( + "application/vnd.ms-powerpoint", + "application/vnd.stardivision.impress", + "application/vnd.oasis.opendocument.presentation", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "application/vnd.openxmlformats-officedocument.presentationml.slideshow"); + private static final List ARCHIVE_MIMES = Arrays.asList( "application/x-7z-compressed", @@ -115,6 +124,8 @@ public class MediaAdapter extends RecyclerView.Adapter AMBIGUOUS_CONTAINER_FORMATS = ImmutableList.of( - "application/ogg", - "video/3gpp", // .3gp files can contain audio, video or both - "video/3gpp2" - ); + public static final List AMBIGUOUS_CONTAINER_FORMATS = + ImmutableList.of( + "application/ogg", + "video/3gpp", // .3gp files can contain audio, video or both + "video/3gpp2"); private static final Map mimeTypeToExtensionMap = new HashMap<>(); private static final Map extensionToMimeTypeMap = new HashMap<>(); @@ -104,6 +101,7 @@ public final class MimeUtils { add("application/vnd.oasis.opendocument.text-master", "odm"); add("application/vnd.oasis.opendocument.text-template", "ott"); add("application/vnd.oasis.opendocument.text-web", "oth"); + add("application/vnd.oasis.opendocument.presentation", "odp"); add("application/vnd.google-earth.kml+xml", "kml"); add("application/vnd.google-earth.kmz", "kmz"); add("application/msword", "doc"); @@ -141,7 +139,7 @@ public final class MimeUtils { add("application/vnd.sun.xml.writer.global", "sxg"); add("application/vnd.sun.xml.writer.template", "stw"); add("application/vnd.visio", "vsd"); - add("application/x-7z-compressed","7z"); + add("application/x-7z-compressed", "7z"); add("application/x-abiword", "abw"); add("application/x-apple-diskimage", "dmg"); add("application/x-bcpio", "bcpio"); @@ -329,8 +327,8 @@ public final class MimeUtils { add("image/x-xbitmap", "xbm"); add("image/x-xpixmap", "xpm"); add("image/x-xwindowdump", "xwd"); - add("message/rfc822","eml"); - add("message/rfc822","mime"); + add("message/rfc822", "eml"); + add("message/rfc822", "mime"); add("model/iges", "igs"); add("model/iges", "iges"); add("model/mesh", "msh"); @@ -350,7 +348,7 @@ public final class MimeUtils { add("text/plain", "asc"); add("text/plain", "text"); add("text/plain", "diff"); - add("text/plain", "po"); // reserve "pot" for vnd.ms-powerpoint + add("text/plain", "po"); // reserve "pot" for vnd.ms-powerpoint add("text/richtext", "rtx"); add("text/rtf", "rtf"); add("text/text", "phps"); @@ -421,7 +419,8 @@ public final class MimeUtils { } // mime types that are more reliant by path - private static final Collection PATH_PRECEDENCE_MIME_TYPE = Arrays.asList("audio/x-m4b"); + private static final Collection PATH_PRECEDENCE_MIME_TYPE = + Arrays.asList("audio/x-m4b"); private static void add(String mimeType, String extension) { // If we have an existing x -> y mapping, we do not want to @@ -450,7 +449,10 @@ public final class MimeUtils { } } // Standard location? - File f = new File(System.getProperty("java.home"), "lib" + File.separator + "content-types.properties"); + File f = + new File( + System.getProperty("java.home"), + "lib" + File.separator + "content-types.properties"); if (f.exists()) { try { return new FileInputStream(f); @@ -461,9 +463,9 @@ public final class MimeUtils { } /** - * This isn't what the RI does. The RI doesn't have hard-coded defaults, so supplying your - * own "content.types.user.table" means you don't get any of the built-ins, and the built-ins - * come from "$JAVA_HOME/lib/content-types.properties". + * This isn't what the RI does. The RI doesn't have hard-coded defaults, so supplying your own + * "content.types.user.table" means you don't get any of the built-ins, and the built-ins come + * from "$JAVA_HOME/lib/content-types.properties". */ private static void applyOverrides() { // Get the appropriate InputStream to read overrides from, if any. @@ -489,8 +491,7 @@ public final class MimeUtils { } } - private MimeUtils() { - } + private MimeUtils() {} /** * Returns true if the given MIME type has an entry in the map. @@ -532,9 +533,8 @@ public final class MimeUtils { } /** - * Returns the registered extension for the given MIME type. Note that some - * MIME types map to multiple extensions. This call will return the most - * common extension for the given MIME type. + * Returns the registered extension for the given MIME type. Note that some MIME types map to + * multiple extensions. This call will return the most common extension for the given MIME type. * * @param mimeType A MIME type (i.e. text/plain) * @return The extension for the given MIME type or null iff there is none. @@ -546,10 +546,11 @@ public final class MimeUtils { return mimeTypeToExtensionMap.get(mimeType.split(";")[0]); } - public static String guessMimeTypeFromUriAndMime(final Context context, final Uri uri, final String mime) { - Log.d(Config.LOGTAG, "guessMimeTypeFromUriAndMime(" + uri + "," + mime+")"); + public static String guessMimeTypeFromUriAndMime( + final Context context, final Uri uri, final String mime) { + Log.d(Config.LOGTAG, "guessMimeTypeFromUriAndMime(" + uri + "," + mime + ")"); final String mimeFromUri = guessMimeTypeFromUri(context, uri); - Log.d(Config.LOGTAG,"mimeFromUri:"+mimeFromUri); + Log.d(Config.LOGTAG, "mimeFromUri:" + mimeFromUri); if (PATH_PRECEDENCE_MIME_TYPE.contains(mimeFromUri)) { return mimeFromUri; } else if (mime == null || mime.equals("application/octet-stream")) { @@ -572,7 +573,8 @@ public final class MimeUtils { if (PATH_PRECEDENCE_MIME_TYPE.contains(mimeTypeFromPath)) { return mimeTypeFromPath; } - if (mimeTypeContentResolver != null && !"application/octet-stream".equals(mimeTypeContentResolver)) { + if (mimeTypeContentResolver != null + && !"application/octet-stream".equals(mimeTypeContentResolver)) { return mimeTypeContentResolver; } if (mimeTypeFromName != null) { @@ -593,7 +595,8 @@ public final class MimeUtils { } private static String getDisplayName(final Context context, final Uri uri) { - try (final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { + try (final Cursor cursor = + context.getContentResolver().query(uri, null, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { final int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); if (index == -1) { @@ -619,7 +622,8 @@ public final class MimeUtils { return extractRelevantExtension(path, false); } - public static String extractRelevantExtension(final String path, final boolean ignoreCryptoExtension) { + public static String extractRelevantExtension( + final String path, final boolean ignoreCryptoExtension) { if (Strings.isNullOrEmpty(path)) { return null; } diff --git a/src/main/res/drawable/ic_slideshow_48dp.xml b/src/main/res/drawable/ic_slideshow_48dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..5e017d3dcd3da38ca2b63e7f74c92a5e96dbd0c2 --- /dev/null +++ b/src/main/res/drawable/ic_slideshow_48dp.xml @@ -0,0 +1,12 @@ + + + + + From 6787505f5dd11e4c2e674cc3472b04a63f6a43a4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 Jan 2025 17:31:18 +0100 Subject: [PATCH 24/39] allow audio recording to be pause by tapping the timer --- .../conversations/ui/RecordingActivity.java | 122 ++++++++++++------ 1 file changed, 80 insertions(+), 42 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java index 231a8600d239851db4c35d447cb628b929e70d8d..a91c82002bc00bc9aac3c2aad714d5bb5bcc1918 100644 --- a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java @@ -9,39 +9,33 @@ import android.os.Bundle; import android.os.Environment; import android.os.FileObserver; import android.os.Handler; -import android.os.SystemClock; import android.util.Log; import android.view.View; import android.view.WindowManager; import android.widget.Toast; - -import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.DataBindingUtil; - +import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableSet; - +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.databinding.ActivityRecordingBinding; +import eu.siacs.conversations.utils.TimeFrameUtils; import java.io.File; import java.lang.ref.WeakReference; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Objects; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.Set; - -import eu.siacs.conversations.Config; -import eu.siacs.conversations.R; -import eu.siacs.conversations.databinding.ActivityRecordingBinding; -import eu.siacs.conversations.ui.util.SettingsUtils; -import eu.siacs.conversations.utils.TimeFrameUtils; public class RecordingActivity extends BaseActivity implements View.OnClickListener { private ActivityRecordingBinding binding; private MediaRecorder mRecorder; - private long mStartTime = 0; + private Stopwatch stopwatch; private final CountDownLatch outputFileWrittenLatch = new CountDownLatch(1); @@ -63,17 +57,48 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.binding = DataBindingUtil.setContentView(this, R.layout.activity_recording); + this.binding.timer.setOnClickListener( + v -> { + onPauseContinue(); + }); this.binding.cancelButton.setOnClickListener(this); this.binding.shareButton.setOnClickListener(this); this.setFinishOnTouchOutside(false); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } + + private void onPauseContinue() { + final var recorder = this.mRecorder; + final var stopwatch = this.stopwatch; + if (recorder == null + || stopwatch == null + || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return; + } + if (stopwatch.isRunning()) { + try { + recorder.pause(); + stopwatch.stop(); + } catch (final IllegalStateException e) { + Log.d(Config.LOGTAG, "could not pause recording", e); + } + } else { + try { + recorder.resume(); + stopwatch.start(); + } catch (final IllegalStateException e) { + Log.d(Config.LOGTAG, "could not resume recording", e); + } + } + } + @Override public void onStart() { super.onStart(); if (!startRecording()) { this.binding.shareButton.setEnabled(false); - this.binding.timer.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyMedium); + this.binding.timer.setTextAppearance( + com.google.android.material.R.style.TextAppearance_Material3_BodyMedium); // TODO reset font family. make red? this.binding.timer.setText(R.string.unable_to_start_recording); } @@ -93,22 +118,31 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe private static final Set AAC_SENSITIVE_DEVICES = new ImmutableSet.Builder() - .add("FP4") // Fairphone 4 https://codeberg.org/monocles/monocles_chat/issues/133 - .add("ONEPLUS A6000") // OnePlus 6 https://github.com/iNPUTmice/Conversations/issues/4329 - .add("ONEPLUS A6003") // OnePlus 6 https://github.com/iNPUTmice/Conversations/issues/4329 - .add("ONEPLUS A6010") // OnePlus 6T https://codeberg.org/monocles/monocles_chat/issues/133 - .add("ONEPLUS A6013") // OnePlus 6T https://codeberg.org/monocles/monocles_chat/issues/133 - .add("Pixel 4a") // Pixel 4a https://github.com/iNPUTmice/Conversations/issues/4223 - .add("WP12 Pro") // Oukitel WP 12 Pro https://github.com/iNPUTmice/Conversations/issues/4223 - .add("Volla Phone X") // Volla Phone X https://github.com/iNPUTmice/Conversations/issues/4223 + .add("FP4") // Fairphone 4 + // https://codeberg.org/monocles/monocles_chat/issues/133 + .add("ONEPLUS A6000") // OnePlus 6 + // https://github.com/iNPUTmice/Conversations/issues/4329 + .add("ONEPLUS A6003") // OnePlus 6 + // https://github.com/iNPUTmice/Conversations/issues/4329 + .add("ONEPLUS A6010") // OnePlus 6T + // https://codeberg.org/monocles/monocles_chat/issues/133 + .add("ONEPLUS A6013") // OnePlus 6T + // https://codeberg.org/monocles/monocles_chat/issues/133 + .add("Pixel 4a") // Pixel 4a + // https://github.com/iNPUTmice/Conversations/issues/4223 + .add("WP12 Pro") // Oukitel WP 12 Pro + // https://github.com/iNPUTmice/Conversations/issues/4223 + .add("Volla Phone X") // Volla Phone X + // https://github.com/iNPUTmice/Conversations/issues/4223 .build(); private boolean startRecording() { mRecorder = new MediaRecorder(); + stopwatch = Stopwatch.createUnstarted(); try { mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); } catch (final RuntimeException e) { - Log.e(Config.LOGTAG,"could not set audio source", e); + Log.e(Config.LOGTAG, "could not set audio source", e); return false; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { @@ -123,8 +157,10 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe } else { outputFormat = MediaRecorder.OutputFormat.MPEG_4; mRecorder.setOutputFormat(outputFormat); - if (AAC_SENSITIVE_DEVICES.contains(Build.MODEL) && Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) { - // Changing these three settings for AAC sensitive devices for Android<=13 might lead to sporadically truncated (cut-off) voice messages. + if (AAC_SENSITIVE_DEVICES.contains(Build.MODEL) + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) { + // Changing these three settings for AAC sensitive devices for Android<=13 might + // lead to sporadically truncated (cut-off) voice messages. mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC); mRecorder.setAudioSamplingRate(24_000); mRecorder.setAudioEncodingBitRate(28_000); @@ -140,7 +176,7 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe try { mRecorder.prepare(); mRecorder.start(); - mStartTime = SystemClock.elapsedRealtime(); + stopwatch.start(); mHandler.postDelayed(mTickExecutor, 100); Log.d(Config.LOGTAG, "started recording to " + mOutputFile.getAbsolutePath()); return true; @@ -154,14 +190,17 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe try { mRecorder.stop(); mRecorder.release(); - } catch (Exception e) { + if (stopwatch.isRunning()) { + stopwatch.stop(); + } + } catch (final Exception e) { + Log.d(Config.LOGTAG, "could not save recording", e); if (saveFile) { Toast.makeText(this, R.string.unable_to_save_recording, Toast.LENGTH_SHORT).show(); return; } } finally { mRecorder = null; - mStartTime = 0; } if (!saveFile && mOutputFile != null) { if (mOutputFile.delete()) { @@ -256,24 +295,23 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe } private void tick() { - this.binding.timer.setText(TimeFrameUtils.formatTimePassed(mStartTime, true)); + this.binding.timer.setText( + TimeFrameUtils.formatElapsedTime(stopwatch.elapsed(TimeUnit.MILLISECONDS), true)); } @Override public void onClick(final View view) { - switch (view.getId()) { - case R.id.cancel_button: - mHandler.removeCallbacks(mTickExecutor); - stopRecording(false); - setResult(RESULT_CANCELED); - finish(); - break; - case R.id.share_button: - this.binding.shareButton.setEnabled(false); - this.binding.shareButton.setText(R.string.please_wait); - mHandler.removeCallbacks(mTickExecutor); - mHandler.postDelayed(() -> stopRecording(true), 500); - break; + if (view.getId() == R.id.cancel_button) { + mHandler.removeCallbacks(mTickExecutor); + stopRecording(false); + setResult(RESULT_CANCELED); + finish(); + } else if (view.getId() == R.id.share_button) { + this.binding.timer.setOnClickListener(null); + this.binding.shareButton.setEnabled(false); + this.binding.shareButton.setText(R.string.please_wait); + mHandler.removeCallbacks(mTickExecutor); + mHandler.postDelayed(() -> stopRecording(true), 500); } } } From b00a6c2677fcad08e99ac07bc1959cb9f8d50605 Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Sun, 26 Jan 2025 14:42:49 +0000 Subject: [PATCH 25/39] Translated using Weblate (Polish) Currently translated at 49.3% (40 of 81 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/4213104.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/pl-PL/changelogs/4213104.txt diff --git a/fastlane/metadata/android/pl-PL/changelogs/4213104.txt b/fastlane/metadata/android/pl-PL/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..7946673f6abee73e4090c1b760b2fb7151e2ad8d --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Implementacja ochrony przed obniżeniem poziomu bezpieczeństwa SASL SCRAM (XEP-0474) +* Wysyłanie reakcji w prywatnych wiadomościach w rozmowie grupowej do właściwego JID From 38225331070ed0c895347e9fd0396e38751881f8 Mon Sep 17 00:00:00 2001 From: solokot Date: Mon, 27 Jan 2025 05:58:46 +0000 Subject: [PATCH 26/39] Translated using Weblate (Russian) Currently translated at 33.3% (27 of 81 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/ru/ --- fastlane/metadata/android/ru-RU/changelogs/4213104.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/ru-RU/changelogs/4213104.txt diff --git a/fastlane/metadata/android/ru-RU/changelogs/4213104.txt b/fastlane/metadata/android/ru-RU/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..73bc95031fde7804462c2893fc8c9f69a1ea4c5e --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Используется защита от понижения SASL SCRAM (XEP-0474) +* Реакции отправляются в MUC PM для корректировки JID From e3e2dfdf3715fdca0add6c0befd40f899803d89e Mon Sep 17 00:00:00 2001 From: Ricky-Tigg Date: Tue, 28 Jan 2025 08:49:35 +0000 Subject: [PATCH 27/39] Translated using Weblate (Finnish) Currently translated at 80.3% (851 of 1059 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/fi/ --- src/main/res/values-fi/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/res/values-fi/strings.xml b/src/main/res/values-fi/strings.xml index 02ceecb1869627013fac0b525381d627959d776b..eb29b28470c2891528ac3b4df5aec4dc61bee6a6 100644 --- a/src/main/res/values-fi/strings.xml +++ b/src/main/res/values-fi/strings.xml @@ -319,12 +319,12 @@ Varmuuskopiosi on palautettu Älä unohda ottaa tiliä käyttöön. Valitse tiedosto - Vastaanotetaan %1$s (%2$d%% valmis) + Vastaanotetaan %1$s (%2$d% % valmiina) Lataa %s Poista %s tiedosto Avaa %s - Lähetetään (%1$d%% valmis) + lähetetään (%1$d% % valmiina) Valmistellaan tiedoston lähettämistä %s tarjottu ladattavaksi Peru siirto From f196899d82889c65de9a44cd91dd53e8f739ff64 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 Jan 2025 20:32:50 +0100 Subject: [PATCH 28/39] add changelog for 2.17.10 --- CHANGELOG.md | 7 +++++++ fastlane/metadata/android/en-US/changelogs/4213204.txt | 4 ++++ 2 files changed, 11 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/4213204.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index ae720c1515a9a2f88c2da5202f5569876f9a4e7c..5a991842efe4354883a8e783147f3926f687c810 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### Version 2.17.10 + +* Allow audio recording to be pause by tapping the timer +* Fix reactions in MUC PMs +* Stop accepting 'fallback messages' for reactions, receipts and display markers +* Add some more media preview icons + ### Version 2.17.9 * Make use of SASL SCRAM Downgrade Protection (XEP-0474) diff --git a/fastlane/metadata/android/en-US/changelogs/4213204.txt b/fastlane/metadata/android/en-US/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..ae676ad87b4111770e798951049eeadea3864ac4 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4213204.txt @@ -0,0 +1,4 @@ +* Allow audio recording to be pause by tapping the timer +* Fix reactions in MUC PMs +* Stop accepting 'fallback messages' for reactions, receipts and display markers +* Add some more media preview icons From b3870c3b56eb0ad5b50d1150944f57377c92ff2c Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Sat, 1 Feb 2025 14:01:32 +0000 Subject: [PATCH 29/39] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (82 of 82 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/zh_Hans/ --- fastlane/metadata/android/zh-CN/changelogs/4213204.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/zh-CN/changelogs/4213204.txt diff --git a/fastlane/metadata/android/zh-CN/changelogs/4213204.txt b/fastlane/metadata/android/zh-CN/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..d81e465d52d704a11390e9a38e3ba61846d89df9 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4213204.txt @@ -0,0 +1,4 @@ +* 允许通过点按计时器暂停录制音频 +* 修复 MUC 私信中的回应 +* 停止接收回应,回执和显示标记的“回退消息” +* 添加更多媒体预览图标 From 37252803eb283e5bf762ebe1cd42997d95223bff Mon Sep 17 00:00:00 2001 From: nautilusx Date: Sun, 2 Feb 2025 09:14:52 +0000 Subject: [PATCH 30/39] Translated using Weblate (German) Currently translated at 100.0% (82 of 82 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/de/ --- fastlane/metadata/android/de-DE/changelogs/4213104.txt | 2 ++ fastlane/metadata/android/de-DE/changelogs/4213204.txt | 4 ++++ 2 files changed, 6 insertions(+) create mode 100644 fastlane/metadata/android/de-DE/changelogs/4213104.txt create mode 100644 fastlane/metadata/android/de-DE/changelogs/4213204.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/4213104.txt b/fastlane/metadata/android/de-DE/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..fabfac695af6a4173e7af34c49715beac984534b --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Verwendung von SASL SCRAM Downgrade Protection (XEP-0474) +* Reaktionen an MUC PMs senden, damit die XMPP-Adresse korrekt ist diff --git a/fastlane/metadata/android/de-DE/changelogs/4213204.txt b/fastlane/metadata/android/de-DE/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..c5ea7dbb555bf730494cb25a88b369f3493b43e5 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4213204.txt @@ -0,0 +1,4 @@ +* Audioaufnahme kann durch Antippen des Timers angehalten werden +* Reaktionen in MUC PMs korrigiert +* Annahme von 'Fallback-Nachrichten' für Reaktionen, Bestätigungen und Anzeigemarkierungen beendet +* Weitere Medienvorschau-Symbole hinzugefügt From 69754889c6f1905e3bfc477959c2517393d0bd71 Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Sun, 2 Feb 2025 09:42:34 +0000 Subject: [PATCH 31/39] Translated using Weblate (Polish) Currently translated at 50.0% (41 of 82 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/4213204.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/pl-PL/changelogs/4213204.txt diff --git a/fastlane/metadata/android/pl-PL/changelogs/4213204.txt b/fastlane/metadata/android/pl-PL/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..ff73979e3e854126aca483077d95048e99e32196 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4213204.txt @@ -0,0 +1,4 @@ +* Umożliwienie wstrzymania nagrywania dźwięku poprzez kliknięcie czasomierza +* Naprawienie reakcji w prywatnych wiadomościach w rozmowie grupowej +* Koniec akceptowania „zastępczych wiadomości”, dla reakcji, potwierdzeń i wskaźników przeczytania +* Dodanie paru więcej ikonek załączników From c19ffee054fd7abb84cc90f6e6cd5cf4bb8c6247 Mon Sep 17 00:00:00 2001 From: Besnik_b Date: Sun, 2 Feb 2025 09:57:16 +0000 Subject: [PATCH 32/39] Translated using Weblate (Albanian) Currently translated at 98.7% (81 of 82 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/sq/ --- fastlane/metadata/android/sq/changelogs/4213204.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/sq/changelogs/4213204.txt diff --git a/fastlane/metadata/android/sq/changelogs/4213204.txt b/fastlane/metadata/android/sq/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..c44d9a8f9422a0e2d566590929af0cc65a8e2c2e --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/4213204.txt @@ -0,0 +1,4 @@ +* Lejim ndaljeje regjistrimi audio duke shtypur matësin e kohës +* Ndreqje reagimesh në MP-ra MUC +* Reshtje pranimi 'fallback messages' për reagime, dëftesa dhe shenja në ekrani +* Shtim i disa ikonash paraparjeje media më tepër From 3b4e8d96fb99f7229353cc42350556342a9617a6 Mon Sep 17 00:00:00 2001 From: "Miguel P.L" Date: Sun, 2 Feb 2025 01:02:32 +0000 Subject: [PATCH 33/39] Translated using Weblate (Spanish) Currently translated at 98.7% (81 of 82 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/es/ --- fastlane/metadata/android/es-ES/changelogs/4213104.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/es-ES/changelogs/4213104.txt diff --git a/fastlane/metadata/android/es-ES/changelogs/4213104.txt b/fastlane/metadata/android/es-ES/changelogs/4213104.txt new file mode 100644 index 0000000000000000000000000000000000000000..d029b2c27c85805e9a3f3e021a3e4de06aaad0a7 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/4213104.txt @@ -0,0 +1,2 @@ +* Hicimos uso de la protección contra degradación de SCRAM SASL (XEP-0474) +* Envíe reacciones a los mensajes privados de MUC para corregir el JID From 331090aa31e09e92f3e190b7347e38bacc81aaa5 Mon Sep 17 00:00:00 2001 From: "lucasmz.dev" Date: Sun, 2 Feb 2025 17:49:29 +0000 Subject: [PATCH 34/39] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (1059 of 1059 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 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index d0582ddd8625507099a43d44c98e631b9c5a0e6a..34fea932a9a00149ab86e416480fb4c20129af65 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -27,7 +27,7 @@ %d minutos atrás %d conversa não lida - %d conversas não lidas + %d de conversas não lidas %d conversas não lidas enviando… @@ -444,7 +444,7 @@ Excluir a seleção %d certificado cancelado - %d certificados cancelados + %d de certificados cancelados %d certificados cancelados Troca o botão \"Enviar\" pelo de ação rápida @@ -502,7 +502,7 @@ %1$d de %2$d contas conectadas %d mensagem - %d mensagens + %d de mensagens %d mensagens Carregar mais mensagens From e6314b7b0af0af6fe5e7d84f7a74637440dda8fa Mon Sep 17 00:00:00 2001 From: "lucasmz.dev" Date: Sun, 2 Feb 2025 17:49:34 +0000 Subject: [PATCH 35/39] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (1059 of 1059 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 | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index 34fea932a9a00149ab86e416480fb4c20129af65..e299526a916bac6ef2e71bf1da83dc3ffe57df43 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -628,32 +628,32 @@ Tem certeza que deseja remover a verificação para este dispositivo?\nEste dispositivo e as mensagens oriundas dele serão marcadas como \"não confiáveis\". %d segundo - %d segundos + %d de segundos %d segundos %d minuto - %d minutos + %d de minutos %d minutos %d hora - %d horas + %d de horas %d horas %d dia - %d dias + %d de dias %d dias %d semana - %d semanas + %d de semanas %d semanas %d mês - %d meses + %d de meses %d meses Exclusão automática de mensagens @@ -928,18 +928,18 @@ Chamada realizada Chamada perdida - %1$d chamada perdida para %2$s - %1$d chamadas perdidas para %2$s - %1$d chamadas perdidas para %2$s + %1$d chamada perdida de %2$s + %1$d de chamadas perdidas de %2$s + %1$d chamadas perdidas de %2$s %d chamada perdida - %d chamadas perdidas + %d de chamadas perdidas %d chamadas perdidas %1$d chamadas perdidas de %2$d contato - %1$d chamadas perdidas de %2$d contatos + %1$d de chamadas perdidas de %2$d contatos %1$d chamadas perdidas de %2$d contatos Chamada de áudio @@ -967,7 +967,7 @@ Adicionar contato, criar ou associar-se a uma conversa em grupo ou descobrir canais Ver %1$d participante - Ver %1$d participantes + Ver %1$d de participantes Ver %1$d participantes From 25e88894b95d13ee6e166ad429f3977e82c5a68d Mon Sep 17 00:00:00 2001 From: solokot Date: Mon, 3 Feb 2025 06:29:59 +0000 Subject: [PATCH 36/39] Translated using Weblate (Russian) Currently translated at 34.1% (28 of 82 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/ru/ --- fastlane/metadata/android/ru-RU/changelogs/4213204.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/ru-RU/changelogs/4213204.txt diff --git a/fastlane/metadata/android/ru-RU/changelogs/4213204.txt b/fastlane/metadata/android/ru-RU/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..14f4c0400134be6289f4623cf0b17a9dbe8513f2 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/4213204.txt @@ -0,0 +1,4 @@ +* Добавлена возможность приостановить запись звука нажатием на таймер +* Исправлены реакции в MUC PM +* Прекращён приём "резервных сообщений" для реакций, уведомлений о доставке и маркеров отображения +* Добавлено ещё несколько значков для просмотра медиа From b9c8513a95506d0bc7d7da4533594172e4e5664b Mon Sep 17 00:00:00 2001 From: dqjxelg2srtivm Date: Tue, 4 Feb 2025 00:52:34 +0000 Subject: [PATCH 37/39] Translated using Weblate (Japanese) Currently translated at 99.8% (1057 of 1059 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/ja/ --- src/main/res/values-ja/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 6f5a17e8bb6f721c7d304c30674755f4bf581c4f..78a55790441d2fd259068034ba1bd1eb0ea37e51 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -454,7 +454,7 @@ ダウンロード失敗: 無効なファイル Tor ネットワークが利用できません バインド失敗 - そのサーバーはこのドメインに責任を持ちません + このドメインに責任を持ちません 壊れています 在席状況 デバイスがロックされているときは離席 @@ -1091,4 +1091,5 @@ ふきだし 背景色、文字サイズ、プロフィール画像など ふきだし + 接続タイムアウト \ No newline at end of file From cd224d5b87ba4f3896fbe87d5884692f1ee086a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 3 Feb 2025 17:13:08 +0000 Subject: [PATCH 38/39] Translated using Weblate (Estonian) Currently translated at 52.4% (43 of 82 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/et/ --- fastlane/metadata/android/et/changelogs/4213204.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/4213204.txt diff --git a/fastlane/metadata/android/et/changelogs/4213204.txt b/fastlane/metadata/android/et/changelogs/4213204.txt new file mode 100644 index 0000000000000000000000000000000000000000..a879dc812c17864b030587e1dada751d95f2d351 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/4213204.txt @@ -0,0 +1,4 @@ +* Nüüd saab heliklipi salvestust peatada taimeril klõpsides +* Parandasime reageerimise jututubade otsesõnumitele +* Enam me ei võta vastu reageerimiste, lugemisteatiste ja lugemismarkerite asendussõnumeid +* Lisasime veel mõned meedia eelvaate ikoonid From dab3b6052571714a44c292e02e7847305a8c6bc0 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 4 Feb 2025 14:18:52 +0100 Subject: [PATCH 39/39] version bump to 2.17.10 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 8895988ea9d4a6abeff2191dbab8ffae40da80a7..73e3aa833a1810eda12827a485d3d7ee126fcab9 100644 --- a/build.gradle +++ b/build.gradle @@ -113,8 +113,8 @@ android { defaultConfig { minSdkVersion 23 targetSdkVersion 34 - versionCode 42131 - versionName "2.17.9" + versionCode 42132 + versionName "2.17.10" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId