From e66902dad579bf696ea81ed46eecb8347c0fbf62 Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Fri, 10 May 2024 16:52:02 +0000 Subject: [PATCH 001/192] Translated using Weblate (Polish) Currently translated at 100.0% (1021 of 1021 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/pl/ --- src/main/res/values-pl/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 8fa55e8996b7fcc6fd5536e20b108a9412201f01..442f24367ee8fa67b3ba4590d63218ad465cfe43 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -1046,7 +1046,7 @@ Czy chcesz usunąć zakładkę dla %s i zarchiwizować rozmowę? Archiwizuj rozmowę Nowa rozmowa - Archiwizuj tę rozmowę + Usuń rozmowę później Kolorowe dymki rozmowy Kod kreskowy nie zawiera odcisków palca dla tej rozmowy. Powiązane rozmowy zarchiwizowane. From 7552b26cfc6262fa1f4c646619f7d7b33ebbea6c Mon Sep 17 00:00:00 2001 From: dqjxelg2srtivm Date: Sat, 11 May 2024 23:48:47 +0000 Subject: [PATCH 002/192] Translated using Weblate (Japanese) Currently translated at 99.0% (1011 of 1021 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 | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 80adacec3efae3fe228f116718f432027d55eed9..c815cba899e7af544852da90309c77361a1abf28 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -1046,4 +1046,5 @@ 全画面通知 端末がロックされているとき、このアプリが全画面を占める着信通知を表示することを許可する。 UnifiedPush互換サードパーティアプリの通知中継 + 対応されていない動作 \ No newline at end of file From c9cb4c47af83cf25753d322e719e84bb821adc32 Mon Sep 17 00:00:00 2001 From: projjalm Date: Sat, 11 May 2024 18:32:16 +0000 Subject: [PATCH 003/192] Translated using Weblate (Bengali (India)) Currently translated at 10.4% (107 of 1021 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/bn_IN/ --- src/main/res/values-bn-rIN/strings.xml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/res/values-bn-rIN/strings.xml b/src/main/res/values-bn-rIN/strings.xml index f87d6df06f2df2e3c5a63ada249abe466e490db4..19fc85dd23718bf2664eb533db0692391db76c58 100644 --- a/src/main/res/values-bn-rIN/strings.xml +++ b/src/main/res/values-bn-rIN/strings.xml @@ -26,8 +26,8 @@ এক মিনিট আগে %d মিনিট আগে - %dটাই কথোপকথন পড়া বাকি - %dকথোেকথন পড়া হয়নি + %dটি চ্যাট পড়া হয়নি + %dটি চ্যাট পড়া হয়নি পাঠানো হচ্ছে... অপেক্ষা করুন, সাঙ্কেতিক সন্দেশ পঠিত হচ্ছে... @@ -39,7 +39,7 @@ নির্ধারক অংশগ্রহণকারী অতিথি - আপনি কি আপনার পরিচিতি তালিকা থেকে %s-কে অপসারণ করতে চান? এই যোগাযোগের সাথে কথোপকথনগুলি সরানো হবে না। + আপনি কি আপনার পরিচিতিদের তালিকা থেকে %s কে মুছে ফেলতে চান? এই পরিচিতির চ্যাট মোছা হবে না। %s-কে বার্তা পাঠানো থেকে ব্লক করতে চান? আপনি কি %s-কে আনব্লক করতে চান এবং তাদের আপনাকে বার্তা পাঠানোর অনুমতি দিতে চান? ব্যক্তিটিকে ব্লক্ করা হয়েছে @@ -74,7 +74,7 @@ ছবিগুলি পাঠানোর জন্য তৈরী করা হচ্ছে ফাইলগুলো শেয়ার করা হচ্ছে, অপেক্ষা করুন প্রতিলিপি মুছে ফেলা যাক - Conversation-এর সব প্রতিলিপি মুছে ফেলা যাক + চ্যাটের ইতিহাস মুছুন এই কথোপকথনের সবকটি বার্তাই কি মুছে ফেলতে চান? \n‌ \nসতর্ক থাকবেন: সার্ভার বা অন্য যন্ত্রে থাকা বার্তা কিন্তু অপরিআর্তিতই থাকবে। @@ -104,4 +104,12 @@ ব্যক্তিগত গ্রুপ চ্যাট তৈরি করুন পাবলিক চ্যানেল তৈরি করা যাক বর্তমান চ্যানেলগুলির মধ্যে থেকে খোঁজা যাক + আপনি কি %s-এর জন্য বুকমার্কটি মুছে দিতে চান? + %s থেকে সবাইকে ব্লক করতে চান? + আপনার XMPP একাউন্ট থেকে স্ট্যাক ট্রেস গুলি পাঠালে %1$s-এর উন্নয়নে সাহায্য হয়। + আপনি কি %s-এর জন্য বুকমার্কটি মুছে দিতে এবং ওটার চ্যাট আর্কাইভ করে দিতে চান? + শেয়ার করুন এদের সঙ্গে… + চ্যাট আর্কাইভ করুন + নতুন চ্যাট + %s থেকে সবাইকে আনব্লক করতে চান? \ No newline at end of file From 054a5bc2e341bea2c3bb7b991339ec12771eb12b Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 12 May 2024 14:59:51 +0200 Subject: [PATCH 004/192] get rid of ResolverApi and use DNS client directly --- build.gradle | 3 +- .../de/gultsch/minidns/AndroidDNSClient.java | 64 +- .../java/de/gultsch/minidns/DNSSocket.java | 36 +- .../de/gultsch/minidns/NetworkDataSource.java | 66 +- .../ResolutionUnsuccessfulException.java | 35 ++ .../de/gultsch/minidns/ResolverResult.java | 178 ++++++ .../eu/siacs/conversations/Conversations.java | 9 + .../services/XmppConnectionService.java | 1 - .../utils/AndroidUsingExecLowPriority.java | 92 --- .../utils/AndroidUsingLinkProperties.java | 90 --- .../siacs/conversations/utils/Resolver.java | 565 +++++++++--------- .../conversations/xmpp/XmppConnection.java | 2 +- 12 files changed, 590 insertions(+), 551 deletions(-) create mode 100644 src/main/java/de/gultsch/minidns/ResolutionUnsuccessfulException.java create mode 100644 src/main/java/de/gultsch/minidns/ResolverResult.java delete mode 100644 src/main/java/eu/siacs/conversations/utils/AndroidUsingExecLowPriority.java delete mode 100644 src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java diff --git a/build.gradle b/build.gradle index 700fd54e3ea60e97420a728fc9834f5d927469fe..9f1ae2334e49c2991576558a8e860f2fd4efd1fc 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,8 @@ dependencies { //zxing stopped supporting Java 7 so we have to stick with 3.3.3 //https://github.com/zxing/zxing/issues/1170 implementation 'com.google.zxing:core:3.3.3' - implementation 'de.measite.minidns:minidns-hla:0.2.4' + implementation 'org.minidns:minidns-client:1.0.4' + implementation 'org.minidns:minidns-dnssec:1.0.4' implementation 'me.leolin:ShortcutBadger:1.1.22@aar' implementation 'org.whispersystems:signal-protocol-java:2.6.2' implementation 'com.makeramen:roundedimageview:2.3.0' diff --git a/src/main/java/de/gultsch/minidns/AndroidDNSClient.java b/src/main/java/de/gultsch/minidns/AndroidDNSClient.java index 2c3731871c1f7ab7cf2fb5eaa869867dc31fd93c..f1789f63a320eb8300903a82258f47ab1c26793d 100644 --- a/src/main/java/de/gultsch/minidns/AndroidDNSClient.java +++ b/src/main/java/de/gultsch/minidns/AndroidDNSClient.java @@ -14,10 +14,15 @@ import com.google.common.base.Strings; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; -import de.measite.minidns.AbstractDNSClient; -import de.measite.minidns.DNSMessage; -import de.measite.minidns.Record; -import de.measite.minidns.record.Data; +import org.minidns.AbstractDnsClient; +import org.minidns.dnsmessage.DnsMessage; +import org.minidns.dnsqueryresult.DirectCachedDnsQueryResult; +import org.minidns.dnsqueryresult.DnsQueryResult; +import org.minidns.dnsqueryresult.StandardDnsQueryResult; +import org.minidns.dnsqueryresult.SynthesizedCachedDnsQueryResult; +import org.minidns.record.Data; + +import org.minidns.record.Record; import eu.siacs.conversations.Config; @@ -27,11 +32,11 @@ import java.time.Duration; import java.util.Collections; import java.util.List; -public class AndroidDNSClient extends AbstractDNSClient { +public class AndroidDNSClient extends AbstractDnsClient { private static final long DNS_MAX_TTL = 86_400L; - private static final LruCache QUERY_CACHE = + private static final LruCache QUERY_CACHE = new LruCache<>(1024); private final Context context; private final NetworkDataSource networkDataSource = new NetworkDataSource(); @@ -60,7 +65,7 @@ public class AndroidDNSClient extends AbstractDNSClient { } @Override - protected DNSMessage.Builder newQuestion(final DNSMessage.Builder message) { + protected DnsMessage.Builder newQuestion(final DnsMessage.Builder message) { message.setRecursionDesired(true); message.getEdnsBuilder() .setUdpPayloadSize(networkDataSource.getUdpPayloadSize()) @@ -69,15 +74,16 @@ public class AndroidDNSClient extends AbstractDNSClient { } @Override - protected DNSMessage query(final DNSMessage.Builder queryBuilder) throws IOException { - final DNSMessage question = newQuestion(queryBuilder).build(); + protected DnsQueryResult query(final DnsMessage.Builder queryBuilder) throws IOException { + final DnsMessage question = newQuestion(queryBuilder).build(); for (final DNSServer dnsServer : getDNSServers()) { final QuestionServerTuple cacheKey = new QuestionServerTuple(dnsServer, question); - final DNSMessage cachedResponse = queryCache(cacheKey); + final DnsMessage cachedResponse = queryCache(cacheKey); if (cachedResponse != null) { - return cachedResponse; + return new CachedDnsQueryResult(question, cachedResponse); } - final DNSMessage response = this.networkDataSource.query(question, dnsServer); + final DnsQueryResult result = this.networkDataSource.query(question, dnsServer); + final var response = result.response; if (response == null) { continue; } @@ -89,7 +95,7 @@ public class AndroidDNSClient extends AbstractDNSClient { continue; } cacheQuery(cacheKey, response); - return response; + return new StandardDnsQueryResult(dnsServer.inetAddress, dnsServer.port,result.queryMethod,question,response); } return null; } @@ -104,8 +110,7 @@ public class AndroidDNSClient extends AbstractDNSClient { private List getDNSServers() { final ImmutableList.Builder dnsServerBuilder = new ImmutableList.Builder<>(); - final ConnectivityManager connectivityManager = - (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + final ConnectivityManager connectivityManager = context.getSystemService(ConnectivityManager.class); final Network[] networks = getActiveNetworks(connectivityManager); for (final Network network : networks) { final LinkProperties linkProperties = connectivityManager.getLinkProperties(network); @@ -133,17 +138,15 @@ public class AndroidDNSClient extends AbstractDNSClient { if (connectivityManager == null) { return new Network[0]; } - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { - final Network activeNetwork = connectivityManager.getActiveNetwork(); - if (activeNetwork != null) { + final Network activeNetwork = connectivityManager.getActiveNetwork(); + if (activeNetwork != null) { return new Network[] {activeNetwork}; - } } return connectivityManager.getAllNetworks(); } - private DNSMessage queryCache(final QuestionServerTuple key) { - final DNSMessage cachedResponse; + private DnsMessage queryCache(final QuestionServerTuple key) { + final DnsMessage cachedResponse; synchronized (QUERY_CACHE) { cachedResponse = QUERY_CACHE.get(key); if (cachedResponse == null) { @@ -163,7 +166,7 @@ public class AndroidDNSClient extends AbstractDNSClient { return cachedResponse; } - private void cacheQuery(final QuestionServerTuple key, final DNSMessage response) { + private void cacheQuery(final QuestionServerTuple key, final DnsMessage response) { if (response.receiveTimestamp <= 0) { return; } @@ -172,7 +175,7 @@ public class AndroidDNSClient extends AbstractDNSClient { } } - private static long ttl(final DNSMessage dnsMessage) { + private static long ttl(final DnsMessage dnsMessage) { final List> answerSection = dnsMessage.answerSection; if (answerSection == null || answerSection.isEmpty()) { final List> authoritySection = dnsMessage.authoritySection; @@ -187,19 +190,19 @@ public class AndroidDNSClient extends AbstractDNSClient { } } - private static long expiresAt(final DNSMessage dnsMessage) { + private static long expiresAt(final DnsMessage dnsMessage) { return dnsMessage.receiveTimestamp + (Math.min(DNS_MAX_TTL, ttl(dnsMessage)) * 1000L); } - private static long expiresIn(final DNSMessage dnsMessage) { + private static long expiresIn(final DnsMessage dnsMessage) { return expiresAt(dnsMessage) - System.currentTimeMillis(); } private static class QuestionServerTuple { private final DNSServer dnsServer; - private final DNSMessage question; + private final DnsMessage question; - private QuestionServerTuple(final DNSServer dnsServer, final DNSMessage question) { + private QuestionServerTuple(final DNSServer dnsServer, final DnsMessage question) { this.dnsServer = dnsServer; this.question = question.asNormalizedVersion(); } @@ -218,4 +221,11 @@ public class AndroidDNSClient extends AbstractDNSClient { return Objects.hashCode(dnsServer, question); } } + + public static class CachedDnsQueryResult extends DnsQueryResult { + + private CachedDnsQueryResult(final DnsMessage query, final DnsMessage response) { + super(QueryMethod.cachedDirect, query, response); + } + } } diff --git a/src/main/java/de/gultsch/minidns/DNSSocket.java b/src/main/java/de/gultsch/minidns/DNSSocket.java index e3d86b80c33b24a12bca7dce025c46c144bd1432..4b7ad2bbd813cdeebcdbe908384c0e27b3d187d8 100644 --- a/src/main/java/de/gultsch/minidns/DNSSocket.java +++ b/src/main/java/de/gultsch/minidns/DNSSocket.java @@ -7,11 +7,11 @@ import com.google.common.base.Strings; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; -import de.measite.minidns.DNSMessage; import eu.siacs.conversations.Config; import org.conscrypt.OkHostnameVerifier; +import org.minidns.dnsmessage.DnsMessage; import java.io.Closeable; import java.io.DataInputStream; @@ -41,7 +41,7 @@ final class DNSSocket implements Closeable { public static final int QUERY_TIMEOUT = 5_000; private final Semaphore semaphore = new Semaphore(1); - private final Map> inFlightQueries = new HashMap<>(); + private final Map> inFlightQueries = new HashMap<>(); private final Socket socket; private final DataInputStream dataInputStream; private final DataOutputStream dataOutputStream; @@ -59,8 +59,8 @@ final class DNSSocket implements Closeable { private void readDNSMessages() { try { while (socket.isConnected()) { - final DNSMessage response = readDNSMessage(); - final SettableFuture future; + final DnsMessage response = readDNSMessage(); + final SettableFuture future; synchronized (inFlightQueries) { future = inFlightQueries.remove(response.id); } @@ -78,10 +78,10 @@ final class DNSSocket implements Closeable { private void evictInFlightQueries(final Exception e) { synchronized (inFlightQueries) { - final Iterator>> iterator = + final Iterator>> iterator = inFlightQueries.entrySet().iterator(); while (iterator.hasNext()) { - final Map.Entry> entry = iterator.next(); + final Map.Entry> entry = iterator.next(); entry.getValue().setException(e); iterator.remove(); } @@ -95,14 +95,11 @@ final class DNSSocket implements Closeable { } public static DNSSocket connect(final DNSServer dnsServer) throws IOException { - switch (dnsServer.uniqueTransport()) { - case TCP: - return connectTcpSocket(dnsServer); - case TLS: - return connectTlsSocket(dnsServer); - default: - throw new IllegalStateException("This is not a socket based transport"); - } + return switch (dnsServer.uniqueTransport()) { + case TCP -> connectTcpSocket(dnsServer); + case TLS -> connectTlsSocket(dnsServer); + default -> throw new IllegalStateException("This is not a socket based transport"); + }; } private static DNSSocket connectTcpSocket(final DNSServer dnsServer) throws IOException { @@ -133,10 +130,9 @@ final class DNSSocket implements Closeable { sslSocket.startHandshake(); final SSLSession session = sslSocket.getSession(); final Certificate[] peerCertificates = session.getPeerCertificates(); - if (peerCertificates.length == 0 || !(peerCertificates[0] instanceof X509Certificate)) { + if (peerCertificates.length == 0 || !(peerCertificates[0] instanceof X509Certificate certificate)) { throw new IOException("Peer did not provide X509 certificates"); } - final X509Certificate certificate = (X509Certificate) peerCertificates[0]; if (!OkHostnameVerifier.strictInstance().verify(dnsServer.hostname, certificate)) { throw new SSLPeerUnverifiedException("Peer did not provide valid certificates"); } @@ -144,7 +140,7 @@ final class DNSSocket implements Closeable { return DNSSocket.of(sslSocket); } - public DNSMessage query(final DNSMessage query) throws IOException, InterruptedException { + public DnsMessage query(final DnsMessage query) throws IOException, InterruptedException { try { return queryAsync(query).get(QUERY_TIMEOUT, TimeUnit.MILLISECONDS); } catch (final ExecutionException e) { @@ -159,9 +155,9 @@ final class DNSSocket implements Closeable { } } - public ListenableFuture queryAsync(final DNSMessage query) + public ListenableFuture queryAsync(final DnsMessage query) throws InterruptedException, IOException { - final SettableFuture responseFuture = SettableFuture.create(); + final SettableFuture responseFuture = SettableFuture.create(); synchronized (this.inFlightQueries) { this.inFlightQueries.put(query.id, responseFuture); } @@ -175,7 +171,7 @@ final class DNSSocket implements Closeable { return responseFuture; } - private DNSMessage readDNSMessage() throws IOException { + private DnsMessage readDNSMessage() throws IOException { final int length = this.dataInputStream.readUnsignedShort(); byte[] data = new byte[length]; int read = 0; diff --git a/src/main/java/de/gultsch/minidns/NetworkDataSource.java b/src/main/java/de/gultsch/minidns/NetworkDataSource.java index 67a8f8c334feb10a27749108b70ced563066bff8..0714594feaaa2c1afa4f8d757be8863c30a3d39e 100644 --- a/src/main/java/de/gultsch/minidns/NetworkDataSource.java +++ b/src/main/java/de/gultsch/minidns/NetworkDataSource.java @@ -11,13 +11,14 @@ import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalListener; import com.google.common.collect.ImmutableList; -import de.measite.minidns.DNSMessage; -import de.measite.minidns.MiniDNSException; -import de.measite.minidns.source.DNSDataSource; -import de.measite.minidns.util.MultipleIoException; - import eu.siacs.conversations.Config; +import org.minidns.MiniDnsException; +import org.minidns.dnsmessage.DnsMessage; +import org.minidns.dnsqueryresult.DnsQueryResult; +import org.minidns.dnsqueryresult.StandardDnsQueryResult; +import org.minidns.util.MultipleIoException; + import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; @@ -28,7 +29,7 @@ import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -public class NetworkDataSource extends DNSDataSource { +public class NetworkDataSource extends org.minidns.source.NetworkDataSource { private static final LoadingCache socketCache = CacheBuilder.newBuilder() @@ -66,7 +67,8 @@ public class NetworkDataSource extends DNSDataSource { } @Override - public DNSMessage query(final DNSMessage message, final InetAddress address, final int port) + public StandardDnsQueryResult query( + final DnsMessage message, final InetAddress address, final int port) throws IOException { final List transports = transportsForPort(port); Log.w( @@ -80,16 +82,21 @@ public class NetworkDataSource extends DNSDataSource { return query(message, new DNSServer(address, port, transports)); } - public DNSMessage query(final DNSMessage message, final DNSServer dnsServer) + public StandardDnsQueryResult query(final DnsMessage message, final DNSServer dnsServer) throws IOException { Log.d(Config.LOGTAG, "using " + dnsServer); final List ioExceptions = new ArrayList<>(); for (final Transport transport : dnsServer.transports) { try { - final DNSMessage response = + final DnsMessage response = queryWithUniqueTransport(message, dnsServer.asUniqueTransport(transport)); if (response != null && !response.truncated) { - return response; + return new StandardDnsQueryResult( + dnsServer.inetAddress, + dnsServer.port, + transportToMethod(transport), + message, + response); } } catch (final IOException e) { ioExceptions.add(e); @@ -101,23 +108,26 @@ public class NetworkDataSource extends DNSDataSource { return null; } - private DNSMessage queryWithUniqueTransport(final DNSMessage message, final DNSServer dnsServer) + private static DnsQueryResult.QueryMethod transportToMethod(final Transport transport) { + return switch (transport) { + case UDP -> DnsQueryResult.QueryMethod.udp; + default -> DnsQueryResult.QueryMethod.tcp; + }; + } + + private DnsMessage queryWithUniqueTransport(final DnsMessage message, final DNSServer dnsServer) throws IOException, InterruptedException { final Transport transport = dnsServer.uniqueTransport(); - switch (transport) { - case UDP: - return queryUdp(message, dnsServer.inetAddress, dnsServer.port); - case TCP: - case TLS: - return queryDnsSocket(message, dnsServer); - default: - throw new IOException( - String.format("Transport %s has not been implemented", transport)); - } + return switch (transport) { + case UDP -> queryUdp(message, dnsServer.inetAddress, dnsServer.port); + case TCP, TLS -> queryDnsSocket(message, dnsServer); + default -> throw new IOException( + String.format("Transport %s has not been implemented", transport)); + }; } - protected DNSMessage queryUdp( - final DNSMessage message, final InetAddress address, final int port) + protected DnsMessage queryUdp( + final DnsMessage message, final InetAddress address, final int port) throws IOException { final DatagramPacket request = message.asDatagram(address, port); final byte[] buffer = new byte[udpPayloadSize]; @@ -126,15 +136,15 @@ public class NetworkDataSource extends DNSDataSource { socket.send(request); final DatagramPacket response = new DatagramPacket(buffer, buffer.length); socket.receive(response); - final DNSMessage dnsMessage = readDNSMessage(response.getData()); + final DnsMessage dnsMessage = readDNSMessage(response.getData()); if (dnsMessage.id != message.id) { - throw new MiniDNSException.IdMismatch(message, dnsMessage); + throw new MiniDnsException.IdMismatch(message, dnsMessage); } return dnsMessage; } } - protected DNSMessage queryDnsSocket(final DNSMessage message, final DNSServer dnsServer) + protected DnsMessage queryDnsSocket(final DnsMessage message, final DNSServer dnsServer) throws IOException, InterruptedException { final DNSSocket cachedDnsSocket = socketCache.getIfPresent(dnsServer); if (cachedDnsSocket != null) { @@ -159,9 +169,9 @@ public class NetworkDataSource extends DNSDataSource { } } - public static DNSMessage readDNSMessage(final byte[] bytes) throws IOException { + public static DnsMessage readDNSMessage(final byte[] bytes) throws IOException { try { - return new DNSMessage(bytes); + return new DnsMessage(bytes); } catch (final IllegalArgumentException e) { throw new IOException(Throwables.getRootCause(e)); } diff --git a/src/main/java/de/gultsch/minidns/ResolutionUnsuccessfulException.java b/src/main/java/de/gultsch/minidns/ResolutionUnsuccessfulException.java new file mode 100644 index 0000000000000000000000000000000000000000..e5e406ca7a6ff6a947f99b0a7a99101c900c6b71 --- /dev/null +++ b/src/main/java/de/gultsch/minidns/ResolutionUnsuccessfulException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2022 the original author or authors + * + * This software is licensed under the Apache License, Version 2.0, + * the GNU Lesser General Public License version 2 or later ("LGPL") + * and the WTFPL. + * You may choose either license to govern your use of this software only + * upon the condition that you accept all of the terms of either + * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. + */ +package de.gultsch.minidns; + +import org.minidns.MiniDnsException; +import org.minidns.dnsmessage.Question; +import org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE; + +import java.io.Serial; + +public class ResolutionUnsuccessfulException extends MiniDnsException { + + /** + * + */ + @Serial + private static final long serialVersionUID = 1L; + + public final Question question; + public final RESPONSE_CODE responseCode; + + public ResolutionUnsuccessfulException(Question question, RESPONSE_CODE responseCode) { + super("Asking for " + question + " yielded an error response " + responseCode); + this.question = question; + this.responseCode = responseCode; + } +} diff --git a/src/main/java/de/gultsch/minidns/ResolverResult.java b/src/main/java/de/gultsch/minidns/ResolverResult.java new file mode 100644 index 0000000000000000000000000000000000000000..3d80fb8df4d3dbffa43fa2924c2991497e2a8483 --- /dev/null +++ b/src/main/java/de/gultsch/minidns/ResolverResult.java @@ -0,0 +1,178 @@ +/* + * Copyright 2015-2022 the original author or authors + * + * This software is licensed under the Apache License, Version 2.0, + * the GNU Lesser General Public License version 2 or later ("LGPL") + * and the WTFPL. + * You may choose either license to govern your use of this software only + * upon the condition that you accept all of the terms of either + * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. + */ +package de.gultsch.minidns; + +import java.util.Collections; +import java.util.Set; + +import org.minidns.MiniDnsException; +import org.minidns.MiniDnsException.NullResultException; +import org.minidns.dnsmessage.DnsMessage; +import org.minidns.dnsmessage.Question; +import org.minidns.dnsqueryresult.DnsQueryResult; +import org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE; +import org.minidns.dnssec.DnssecResultNotAuthenticException; +import org.minidns.dnssec.DnssecUnverifiedReason; +import org.minidns.record.Data; + +public class ResolverResult { + + protected final Question question; + private final RESPONSE_CODE responseCode; + private final Set data; + private final boolean isAuthenticData; + protected final Set unverifiedReasons; + protected final DnsMessage answer; + protected final DnsQueryResult result; + + public ResolverResult(Question question, DnsQueryResult result, Set unverifiedReasons) throws NullResultException { + // TODO: Is this null check still needed? + if (result == null) { + throw new MiniDnsException.NullResultException(question.asMessageBuilder().build()); + } + + this.result = result; + + DnsMessage answer = result.response; + this.question = question; + this.responseCode = answer.responseCode; + this.answer = answer; + + Set r = answer.getAnswersFor(question); + if (r == null) { + this.data = Collections.emptySet(); + } else { + this.data = Collections.unmodifiableSet(r); + } + + if (unverifiedReasons == null) { + this.unverifiedReasons = null; + isAuthenticData = false; + } else { + this.unverifiedReasons = Collections.unmodifiableSet(unverifiedReasons); + isAuthenticData = this.unverifiedReasons.isEmpty(); + } + } + + public boolean wasSuccessful() { + return responseCode == RESPONSE_CODE.NO_ERROR; + } + + public Set getAnswers() { + throwIseIfErrorResponse(); + return data; + } + + public Set getAnswersOrEmptySet() { + return data; + } + + public RESPONSE_CODE getResponseCode() { + return responseCode; + } + + public boolean isAuthenticData() { + throwIseIfErrorResponse(); + return isAuthenticData; + } + + /** + * Get the reasons the result could not be verified if any exists. + * + * @return The reasons the result could not be verified or null. + */ + public Set getUnverifiedReasons() { + throwIseIfErrorResponse(); + return unverifiedReasons; + } + + public Question getQuestion() { + return question; + } + + public void throwIfErrorResponse() throws ResolutionUnsuccessfulException { + ResolutionUnsuccessfulException resolutionUnsuccessfulException = getResolutionUnsuccessfulException(); + if (resolutionUnsuccessfulException != null) throw resolutionUnsuccessfulException; + } + + private ResolutionUnsuccessfulException resolutionUnsuccessfulException; + + public ResolutionUnsuccessfulException getResolutionUnsuccessfulException() { + if (wasSuccessful()) return null; + + if (resolutionUnsuccessfulException == null) { + resolutionUnsuccessfulException = new ResolutionUnsuccessfulException(question, responseCode); + } + + return resolutionUnsuccessfulException; + } + + private DnssecResultNotAuthenticException dnssecResultNotAuthenticException; + + public DnssecResultNotAuthenticException getDnssecResultNotAuthenticException() { + if (!wasSuccessful()) + return null; + if (isAuthenticData) + return null; + + if (dnssecResultNotAuthenticException == null) { + dnssecResultNotAuthenticException = DnssecResultNotAuthenticException.from(getUnverifiedReasons()); + } + + return dnssecResultNotAuthenticException; + } + + /** + * Get the raw answer DNS message we received. This is likely not what you want, try {@link #getAnswers()} instead. + * + * @return the raw answer DNS Message. + * @see #getAnswers() + */ + public DnsMessage getRawAnswer() { + return answer; + } + + public DnsQueryResult getDnsQueryResult() { + return result; + } + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append(getClass().getName()).append('\n') + .append("Question: ").append(question).append('\n') + .append("Response Code: ").append(responseCode).append('\n'); + + if (responseCode == RESPONSE_CODE.NO_ERROR) { + if (isAuthenticData) { + sb.append("Results verified via DNSSEC\n"); + } + if (hasUnverifiedReasons()) { + sb.append(unverifiedReasons).append('\n'); + } + sb.append(answer.answerSection); + } + + return sb.toString(); + } + + boolean hasUnverifiedReasons() { + return unverifiedReasons != null && !unverifiedReasons.isEmpty(); + } + + protected void throwIseIfErrorResponse() { + ResolutionUnsuccessfulException resolutionUnsuccessfulException = getResolutionUnsuccessfulException(); + if (resolutionUnsuccessfulException != null) + throw new IllegalStateException("Can not perform operation because the DNS resolution was unsuccessful", + resolutionUnsuccessfulException); + } +} diff --git a/src/main/java/eu/siacs/conversations/Conversations.java b/src/main/java/eu/siacs/conversations/Conversations.java index 8b564c0cfa2d04e5ee0a300ae44a55b0478efc24..72ed0a72b9ec618d8c779ec9485dcdc2567e0843 100644 --- a/src/main/java/eu/siacs/conversations/Conversations.java +++ b/src/main/java/eu/siacs/conversations/Conversations.java @@ -1,5 +1,6 @@ package eu.siacs.conversations; +import android.annotation.SuppressLint; import android.app.Application; import android.content.Context; import android.content.SharedPreferences; @@ -14,9 +15,17 @@ import eu.siacs.conversations.utils.ExceptionHelper; public class Conversations extends Application { + @SuppressLint("StaticFieldLeak") + private static Context CONTEXT; + + public static Context getContext() { + return Conversations.CONTEXT; + } + @Override public void onCreate() { super.onCreate(); + CONTEXT = this.getApplicationContext(); ExceptionHelper.init(getApplicationContext()); applyThemeSettings(); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 89efa949a3185ec722b4def9abccfe7a97b2e5e6..484eaea0daafb15102b2c5503b7a666c5b47268b 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -1267,7 +1267,6 @@ public class XmppConnectionService extends Service { } catch (Throwable throwable) { Log.e(Config.LOGTAG, "unable to initialize security provider", throwable); } - Resolver.init(this); updateMemorizingTrustManager(); final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); final int cacheSize = maxMemory / 8; diff --git a/src/main/java/eu/siacs/conversations/utils/AndroidUsingExecLowPriority.java b/src/main/java/eu/siacs/conversations/utils/AndroidUsingExecLowPriority.java deleted file mode 100644 index d8c46b08f7ae2b1b3b71bd2abd0a58752fb58719..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/utils/AndroidUsingExecLowPriority.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2015-2016 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package eu.siacs.conversations.utils; - - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.LineNumberReader; -import java.net.InetAddress; -import java.util.HashSet; -import java.util.logging.Level; - -import de.measite.minidns.dnsserverlookup.AbstractDNSServerLookupMechanism; -import de.measite.minidns.dnsserverlookup.AndroidUsingReflection; -import de.measite.minidns.dnsserverlookup.DNSServerLookupMechanism; -import de.measite.minidns.util.PlatformDetection; - -/** - * Try to retrieve the list of DNS server by executing getprop. - */ -public class AndroidUsingExecLowPriority extends AbstractDNSServerLookupMechanism { - - public static final DNSServerLookupMechanism INSTANCE = new AndroidUsingExecLowPriority(); - public static final int PRIORITY = AndroidUsingReflection.PRIORITY + 1; - - private AndroidUsingExecLowPriority() { - super(AndroidUsingExecLowPriority.class.getSimpleName(), PRIORITY); - } - - @Override - public String[] getDnsServerAddresses() { - try { - Process process = Runtime.getRuntime().exec("getprop"); - InputStream inputStream = process.getInputStream(); - LineNumberReader lnr = new LineNumberReader( - new InputStreamReader(inputStream)); - String line; - HashSet server = new HashSet<>(6); - while ((line = lnr.readLine()) != null) { - int split = line.indexOf("]: ["); - if (split == -1) { - continue; - } - String property = line.substring(1, split); - String value = line.substring(split + 4, line.length() - 1); - - if (value.isEmpty()) { - continue; - } - - if (property.endsWith(".dns") || property.endsWith(".dns1") || - property.endsWith(".dns2") || property.endsWith(".dns3") || - property.endsWith(".dns4")) { - - // normalize the address - - InetAddress ip = InetAddress.getByName(value); - - if (ip == null) continue; - - value = ip.getHostAddress(); - - if (value == null) continue; - if (value.length() == 0) continue; - - server.add(value); - } - } - if (server.size() > 0) { - return server.toArray(new String[server.size()]); - } - } catch (IOException e) { - LOGGER.log(Level.WARNING, "Exception in findDNSByExec", e); - } - return null; - } - - @Override - public boolean isAvailable() { - return PlatformDetection.isAndroid(); - } - -} diff --git a/src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java b/src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java deleted file mode 100644 index 658e7abcd7e8d1046fc51b02a3944e48aacf265f..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java +++ /dev/null @@ -1,90 +0,0 @@ -package eu.siacs.conversations.utils; - -import android.annotation.TargetApi; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.LinkProperties; -import android.net.Network; -import android.net.NetworkInfo; -import android.net.RouteInfo; -import android.os.Build; - -import java.net.Inet4Address; -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.List; - -import de.measite.minidns.dnsserverlookup.AbstractDNSServerLookupMechanism; -import de.measite.minidns.dnsserverlookup.AndroidUsingExec; - -public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism { - - private final Context context; - - AndroidUsingLinkProperties(Context context) { - super(AndroidUsingLinkProperties.class.getSimpleName(), AndroidUsingExec.PRIORITY - 1); - this.context = context; - } - - @Override - public boolean isAvailable() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; - } - - @Override - @TargetApi(21) - public String[] getDnsServerAddresses() { - final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - final Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks(); - if (networks == null) { - return new String[0]; - } - final Network activeNetwork = getActiveNetwork(connectivityManager); - final List servers = new ArrayList<>(); - int vpnOffset = 0; - for(Network network : networks) { - LinkProperties linkProperties = connectivityManager.getLinkProperties(network); - if (linkProperties == null) { - continue; - } - final NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network); - final boolean isActiveNetwork = network.equals(activeNetwork); - final boolean isVpn = networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_VPN; - if (isActiveNetwork && isVpn) { - final List tmp = getIPv4First(linkProperties.getDnsServers()); - servers.addAll(0, tmp); - vpnOffset += tmp.size(); - } else if (hasDefaultRoute(linkProperties) || isActiveNetwork || activeNetwork == null || isVpn) { - servers.addAll(vpnOffset, getIPv4First(linkProperties.getDnsServers())); - } - } - return servers.toArray(new String[0]); - } - - @TargetApi(23) - private static Network getActiveNetwork(ConnectivityManager cm) { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? cm.getActiveNetwork() : null; - } - - private static List getIPv4First(List in) { - List out = new ArrayList<>(); - for(InetAddress address : in) { - if (address instanceof Inet4Address) { - out.add(0, address.getHostAddress()); - } else { - out.add(address.getHostAddress()); - } - } - return out; - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static boolean hasDefaultRoute(LinkProperties linkProperties) { - for(RouteInfo route: linkProperties.getRoutes()) { - if (route.isDefaultRoute()) { - return true; - } - } - return false; - } -} diff --git a/src/main/java/eu/siacs/conversations/utils/Resolver.java b/src/main/java/eu/siacs/conversations/utils/Resolver.java index 6746f3ea9a5d8216f4c966faab907ca3acbd43fe..88f2c601e47ad4812e65afd80146102554d8ce57 100644 --- a/src/main/java/eu/siacs/conversations/utils/Resolver.java +++ b/src/main/java/eu/siacs/conversations/utils/Resolver.java @@ -6,85 +6,83 @@ import android.util.Log; import androidx.annotation.NonNull; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; import com.google.common.base.Strings; -import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Ordering; import com.google.common.net.InetAddresses; import com.google.common.primitives.Ints; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; + +import de.gultsch.minidns.AndroidDNSClient; +import de.gultsch.minidns.ResolverResult; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.Conversations; +import eu.siacs.conversations.xmpp.Jid; + +import org.minidns.dnsmessage.Question; +import org.minidns.dnsname.DnsName; +import org.minidns.dnsqueryresult.DnsQueryResult; +import org.minidns.record.A; +import org.minidns.record.AAAA; +import org.minidns.record.CNAME; +import org.minidns.record.Data; +import org.minidns.record.InternetAddressRR; +import org.minidns.record.Record; +import org.minidns.record.SRV; -import java.io.IOException; -import java.lang.reflect.Field; import java.net.Inet4Address; import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.List; - -import de.gultsch.minidns.AndroidDNSClient; -import de.measite.minidns.AbstractDNSClient; -import de.measite.minidns.DNSCache; -import de.measite.minidns.DNSClient; -import de.measite.minidns.DNSName; -import de.measite.minidns.Question; -import de.measite.minidns.Record; -import de.measite.minidns.cache.LRUCache; -import de.measite.minidns.dnssec.DNSSECResultNotAuthenticException; -import de.measite.minidns.dnsserverlookup.AndroidUsingExec; -import de.measite.minidns.hla.DnssecResolverApi; -import de.measite.minidns.hla.ResolverApi; -import de.measite.minidns.hla.ResolverResult; -import de.measite.minidns.iterative.ReliableDNSClient; -import de.measite.minidns.record.A; -import de.measite.minidns.record.AAAA; -import de.measite.minidns.record.CNAME; -import de.measite.minidns.record.Data; -import de.measite.minidns.record.InternetAddressRR; -import de.measite.minidns.record.SRV; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.R; -import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.xmpp.Jid; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class Resolver { + private static final Comparator RESULT_COMPARATOR = + (left, right) -> { + if (left.priority == right.priority) { + if (left.directTls == right.directTls) { + if (left.ip == null && right.ip == null) { + return 0; + } else if (left.ip != null && right.ip != null) { + if (left.ip instanceof Inet4Address + && right.ip instanceof Inet4Address) { + return 0; + } else { + return left.ip instanceof Inet4Address ? -1 : 1; + } + } else { + return left.ip != null ? -1 : 1; + } + } else { + return left.directTls ? -1 : 1; + } + } else { + return left.priority - right.priority; + } + }; + + private static final ExecutorService DNS_QUERY_EXECUTOR = Executors.newFixedThreadPool(12); + public static final int DEFAULT_PORT_XMPP = 5222; private static final String DIRECT_TLS_SERVICE = "_xmpps-client"; private static final String STARTTLS_SERVICE = "_xmpp-client"; - private static XmppConnectionService SERVICE = null; - - - public static void init(XmppConnectionService service) { - Resolver.SERVICE = service; - DNSClient.removeDNSServerLookupMechanism(AndroidUsingExec.INSTANCE); - DNSClient.addDnsServerLookupMechanism(AndroidUsingExecLowPriority.INSTANCE); - DNSClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(service)); - final AbstractDNSClient client = ResolverApi.INSTANCE.getClient(); - if (client instanceof ReliableDNSClient) { - disableHardcodedDnsServers((ReliableDNSClient) client); - } - } - - private static void disableHardcodedDnsServers(ReliableDNSClient reliableDNSClient) { - try { - final Field dnsClientField = ReliableDNSClient.class.getDeclaredField("dnsClient"); - dnsClientField.setAccessible(true); - final DNSClient dnsClient = (DNSClient) dnsClientField.get(reliableDNSClient); - if (dnsClient != null) { - dnsClient.getDataSource().setTimeout(3000); - } - final Field useHardcodedDnsServers = DNSClient.class.getDeclaredField("useHardcodedDnsServers"); - useHardcodedDnsServers.setAccessible(true); - useHardcodedDnsServers.setBoolean(dnsClient, false); - } catch (NoSuchFieldException | IllegalAccessException e) { - Log.e(Config.LOGTAG, "Unable to disable hardcoded DNS servers", e); - } - } - public static List fromHardCoded(final String hostname, final int port) { final Result result = new Result(); - result.hostname = DNSName.from(hostname); + result.hostname = DnsName.from(hostname); result.port = port; result.directTls = useDirectTls(port); result.authenticated = true; @@ -92,221 +90,236 @@ public class Resolver { } public static void checkDomain(final Jid jid) { - DNSName.from(jid.getDomain()); + DnsName.from(jid.getDomain()); } public static boolean invalidHostname(final String hostname) { try { - DNSName.from(hostname); + DnsName.from(hostname); return false; } catch (IllegalArgumentException e) { return true; } } - public static void clearCache() { - final AbstractDNSClient client = ResolverApi.INSTANCE.getClient(); - final DNSCache dnsCache = client.getCache(); - if (dnsCache instanceof LRUCache) { - Log.d(Config.LOGTAG,"clearing DNS cache"); - ((LRUCache) dnsCache).clear(); - } - } - + public static void clearCache() {} public static boolean useDirectTls(final int port) { return port == 443 || port == 5223; } public static List resolve(final String domain) { - final List ipResults = fromIpAddress(domain); - if (ipResults.size() > 0) { + final List ipResults = fromIpAddress(domain); + if (!ipResults.isEmpty()) { return ipResults; } - final List results = new ArrayList<>(); - final List fallbackResults = new ArrayList<>(); - final Thread[] threads = new Thread[3]; - threads[0] = new Thread(() -> { - try { - final List list = resolveSrv(domain, true); - synchronized (results) { - results.addAll(list); - } - } catch (final Throwable throwable) { - if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) { - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (direct TLS)", throwable); - } - } - }); - threads[1] = new Thread(() -> { - try { - final List list = resolveSrv(domain, false); - synchronized (results) { - results.addAll(list); - } - } catch (final Throwable throwable) { - if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) { - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (STARTTLS)", throwable); - } - } - }); - threads[2] = new Thread(() -> { - List list = resolveNoSrvRecords(DNSName.from(domain), true); - synchronized (fallbackResults) { - fallbackResults.addAll(list); - } - }); - for (final Thread thread : threads) { - thread.start(); - } + + final var startTls = resolveSrvAsFuture(domain, false); + final var directTls = resolveSrvAsFuture(domain, true); + + final var combined = merge(ImmutableList.of(startTls, directTls)); + + final var combinedWithFallback = + Futures.transformAsync( + combined, + results -> { + if (results.isEmpty()) { + return resolveNoSrvAsFuture(DnsName.from(domain), true); + } else { + return Futures.immediateFuture(results); + } + }, + MoreExecutors.directExecutor()); + final var orderedFuture = + Futures.transform( + combinedWithFallback, + all -> Ordering.from(RESULT_COMPARATOR).immutableSortedCopy(all), + MoreExecutors.directExecutor()); try { - threads[0].join(); - threads[1].join(); - if (results.size() > 0) { - threads[2].interrupt(); - synchronized (results) { - Collections.sort(results); - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + results); - return results; - } - } else { - threads[2].join(); - synchronized (fallbackResults) { - Collections.sort(fallbackResults); - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + fallbackResults); - return fallbackResults; - } - } - } catch (InterruptedException e) { - for (Thread thread : threads) { - thread.interrupt(); - } + final var ordered = orderedFuture.get(); + Log.d(Config.LOGTAG, "Resolver (" + ordered.size() + "): " + ordered); + return ordered; + } catch (final ExecutionException e) { + Log.d(Config.LOGTAG, "error resolving DNS", e); + return Collections.emptyList(); + } catch (final InterruptedException e) { + Log.d(Config.LOGTAG, "DNS resolution interrupted"); return Collections.emptyList(); } } - private static List fromIpAddress(String domain) { - if (!IP.matches(domain)) { - return Collections.emptyList(); - } - try { - Result result = new Result(); - result.ip = InetAddress.getByName(domain); + private static List fromIpAddress(final String domain) { + if (IP.matches(domain)) { + final InetAddress inetAddress; + try { + inetAddress = InetAddress.getByName(domain); + } catch (final UnknownHostException e) { + return Collections.emptyList(); + } + final Result result = new Result(); + result.ip = inetAddress; result.port = DEFAULT_PORT_XMPP; return Collections.singletonList(result); - } catch (UnknownHostException e) { + } else { return Collections.emptyList(); } } - private static List resolveSrv(String domain, final boolean directTls) throws IOException { - DNSName dnsName = DNSName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain); - ResolverResult result = resolveWithFallback(dnsName, SRV.class); - final List results = new ArrayList<>(); - final List threads = new ArrayList<>(); - for (SRV record : result.getAnswersOrEmptySet()) { - if (record.name.length() == 0 && record.priority == 0) { - continue; - } - threads.add(new Thread(() -> { - final List ipv4s = resolveIp(record, A.class, result.isAuthenticData(), directTls); - if (ipv4s.size() == 0) { - Result resolverResult = Result.fromRecord(record, directTls); - resolverResult.authenticated = result.isAuthenticData(); - ipv4s.add(resolverResult); - } - synchronized (results) { - results.addAll(ipv4s); - } - - })); - threads.add(new Thread(() -> { - final List ipv6s = resolveIp(record, AAAA.class, result.isAuthenticData(), directTls); - synchronized (results) { - results.addAll(ipv6s); - } - })); - } - for (Thread thread : threads) { - thread.start(); - } - for (Thread thread : threads) { - try { - thread.join(); - } catch (InterruptedException e) { - return Collections.emptyList(); - } - } - return results; + private static ListenableFuture> resolveSrvAsFuture( + final String domain, final boolean directTls) { + final DnsName dnsName = + DnsName.from( + (directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain); + final var resultFuture = resolveAsFuture(dnsName, SRV.class); + return Futures.transformAsync( + resultFuture, + result -> resolveIpsAsFuture(result, directTls), + MoreExecutors.directExecutor()); } - private static List resolveIp(SRV srv, Class type, boolean authenticated, boolean directTls) { - List list = new ArrayList<>(); - try { - ResolverResult results = resolveWithFallback(srv.name, type, authenticated); - for (D record : results.getAnswersOrEmptySet()) { - Result resolverResult = Result.fromRecord(srv, directTls); - resolverResult.authenticated = results.isAuthenticData() && authenticated; //TODO technically it doesn’t matter if the IP was authenticated - resolverResult.ip = record.getInetAddress(); - list.add(resolverResult); + @NonNull + private static ListenableFuture> resolveIpsAsFuture( + final ResolverResult srvResolverResult, final boolean directTls) { + final ImmutableList.Builder>> futuresBuilder = + new ImmutableList.Builder<>(); + for (final SRV record : srvResolverResult.getAnswersOrEmptySet()) { + if (record.target.length() == 0 && record.priority == 0) { + continue; } - } catch (Throwable t) { - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " " + t.getMessage()); + final var ipv4sRaw = + resolveIpsAsFuture( + record, A.class, srvResolverResult.isAuthenticData(), directTls); + final var ipv4s = + Futures.transform( + ipv4sRaw, + results -> { + if (results.isEmpty()) { + final Result resolverResult = + Result.fromRecord(record, directTls); + resolverResult.authenticated = + srvResolverResult.isAuthenticData(); + return Collections.singletonList(resolverResult); + } else { + return results; + } + }, + MoreExecutors.directExecutor()); + final var ipv6s = + resolveIpsAsFuture( + record, AAAA.class, srvResolverResult.isAuthenticData(), directTls); + futuresBuilder.add(ipv4s); + futuresBuilder.add(ipv6s); } - return list; + final ImmutableList>> futures = futuresBuilder.build(); + return merge(futures); } - private static List resolveNoSrvRecords(DNSName dnsName, boolean withCnames) { - final List results = new ArrayList<>(); - try { - for (A a : resolveWithFallback(dnsName, A.class, false).getAnswersOrEmptySet()) { - results.add(Result.createDefault(dnsName, a.getInetAddress())); - } - for (AAAA aaaa : resolveWithFallback(dnsName, AAAA.class, false).getAnswersOrEmptySet()) { - results.add(Result.createDefault(dnsName, aaaa.getInetAddress())); - } - if (results.size() == 0 && withCnames) { - for (CNAME cname : resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) { - results.addAll(resolveNoSrvRecords(cname.name, false)); - } - } - } catch (final Throwable throwable) { - if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) { - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records", throwable); - } - } - results.add(Result.createDefault(dnsName)); - return results; + private static ListenableFuture> merge( + final Collection>> futures) { + return Futures.transform( + Futures.successfulAsList(futures), + lists -> { + final var builder = new ImmutableList.Builder(); + for (final Collection list : lists) { + if (list == null) { + continue; + } + builder.addAll(list); + } + return builder.build(); + }, + MoreExecutors.directExecutor()); } - private static ResolverResult resolveWithFallback(DNSName dnsName, Class type) throws IOException { - return resolveWithFallback(dnsName, type, validateHostname()); + private static > + ListenableFuture> resolveIpsAsFuture( + final SRV srv, Class type, boolean authenticated, boolean directTls) { + final var resultFuture = resolveAsFuture(srv.target, type); + return Futures.transform( + resultFuture, + result -> { + final var builder = new ImmutableList.Builder(); + for (D record : result.getAnswersOrEmptySet()) { + Result resolverResult = Result.fromRecord(srv, directTls); + resolverResult.authenticated = + result.isAuthenticData() + && authenticated; // TODO technically it does not matter if + // the IP + // was authenticated + resolverResult.ip = record.getInetAddress(); + builder.add(resolverResult); + } + return builder.build(); + }, + MoreExecutors.directExecutor()); } - private static ResolverResult resolveWithFallback(DNSName dnsName, Class type, boolean validateHostname) throws IOException { - final Question question = new Question(dnsName, Record.TYPE.getType(type)); - if (!validateHostname) { - final AndroidDNSClient androidDNSClient = new AndroidDNSClient(SERVICE); - final ResolverApi resolverApi = new ResolverApi(androidDNSClient); - return resolverApi.resolve(question); - } - try { - return DnssecResolverApi.INSTANCE.resolveDnssecReliable(question); - } catch (DNSSECResultNotAuthenticException e) { - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", e); - } catch (IOException e) { - throw e; - } catch (Throwable throwable) { - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", throwable); + private static ListenableFuture> resolveNoSrvAsFuture( + final DnsName dnsName, boolean cName) { + final ImmutableList.Builder>> futuresBuilder = + new ImmutableList.Builder<>(); + ListenableFuture> aRecordResults = + Futures.transform( + resolveAsFuture(dnsName, A.class), + result -> + Lists.transform( + ImmutableList.copyOf(result.getAnswersOrEmptySet()), + a -> Result.createDefault(dnsName, a.getInetAddress())), + MoreExecutors.directExecutor()); + futuresBuilder.add(aRecordResults); + ListenableFuture> aaaaRecordResults = + Futures.transform( + resolveAsFuture(dnsName, AAAA.class), + result -> + Lists.transform( + ImmutableList.copyOf(result.getAnswersOrEmptySet()), + aaaa -> + Result.createDefault( + dnsName, aaaa.getInetAddress())), + MoreExecutors.directExecutor()); + futuresBuilder.add(aaaaRecordResults); + if (cName) { + ListenableFuture> cNameRecordResults = + Futures.transformAsync( + resolveAsFuture(dnsName, CNAME.class), + result -> { + Collection>> test = + Lists.transform( + ImmutableList.copyOf(result.getAnswersOrEmptySet()), + cname -> resolveNoSrvAsFuture(cname.target, false)); + return merge(test); + }, + MoreExecutors.directExecutor()); + futuresBuilder.add(cNameRecordResults); } - return ResolverApi.INSTANCE.resolve(question); + final ImmutableList>> futures = futuresBuilder.build(); + final var noSrvFallbacks = merge(futures); + return Futures.transform( + noSrvFallbacks, + results -> { + if (results.isEmpty()) { + return Collections.singletonList(Result.createDefault(dnsName)); + } else { + return results; + } + }, + MoreExecutors.directExecutor()); } - private static boolean validateHostname() { - return SERVICE != null && SERVICE.getBooleanPreference("validate_hostname", R.bool.validate_hostname); + private static ListenableFuture> resolveAsFuture( + final DnsName dnsName, final Class type) { + return Futures.submit( + () -> { + final Question question = new Question(dnsName, Record.TYPE.getType(type)); + final AndroidDNSClient androidDNSClient = + new AndroidDNSClient(Conversations.getContext()); + final DnsQueryResult dnsQueryResult = androidDNSClient.query(question); + return new ResolverResult(question, dnsQueryResult, null); + }, + DNS_QUERY_EXECUTOR); } - public static class Result implements Comparable { + public static class Result { public static final String DOMAIN = "domain"; public static final String IP = "ip"; public static final String HOSTNAME = "hostname"; @@ -315,7 +328,7 @@ public class Resolver { public static final String DIRECT_TLS = "directTls"; public static final String AUTHENTICATED = "authenticated"; private InetAddress ip; - private DNSName hostname; + private DnsName hostname; private int port = DEFAULT_PORT_XMPP; private boolean directTls = false; private boolean authenticated = false; @@ -330,7 +343,7 @@ public class Resolver { return result; } - static Result createDefault(DNSName hostname, InetAddress ip) { + static Result createDefault(DnsName hostname, InetAddress ip) { Result result = new Result(); result.port = DEFAULT_PORT_XMPP; result.hostname = hostname; @@ -338,23 +351,24 @@ public class Resolver { return result; } - static Result createDefault(DNSName hostname) { + static Result createDefault(DnsName hostname) { return createDefault(hostname, null); } public static Result fromCursor(Cursor cursor) { final Result result = new Result(); try { - result.ip = InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndex(IP))); + result.ip = + InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndexOrThrow(IP))); } catch (UnknownHostException e) { result.ip = null; } - final String hostname = cursor.getString(cursor.getColumnIndex(HOSTNAME)); - result.hostname = hostname == null ? null : DNSName.from(hostname); - result.port = cursor.getInt(cursor.getColumnIndex(PORT)); - result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY)); - result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0; - result.directTls = cursor.getInt(cursor.getColumnIndex(DIRECT_TLS)) > 0; + final String hostname = cursor.getString(cursor.getColumnIndexOrThrow(HOSTNAME)); + result.hostname = hostname == null ? null : DnsName.from(hostname); + result.port = cursor.getInt(cursor.getColumnIndexOrThrow(PORT)); + result.priority = cursor.getInt(cursor.getColumnIndexOrThrow(PRIORITY)); + result.authenticated = cursor.getInt(cursor.getColumnIndexOrThrow(AUTHENTICATED)) > 0; + result.directTls = cursor.getInt(cursor.getColumnIndexOrThrow(DIRECT_TLS)) > 0; return result; } @@ -362,26 +376,18 @@ public class Resolver { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - Result result = (Result) o; - - if (port != result.port) return false; - if (directTls != result.directTls) return false; - if (authenticated != result.authenticated) return false; - if (priority != result.priority) return false; - if (ip != null ? !ip.equals(result.ip) : result.ip != null) return false; - return hostname != null ? hostname.equals(result.hostname) : result.hostname == null; + return port == result.port + && directTls == result.directTls + && authenticated == result.authenticated + && priority == result.priority + && Objects.equal(ip, result.ip) + && Objects.equal(hostname, result.hostname); } @Override public int hashCode() { - int result = ip != null ? ip.hashCode() : 0; - result = 31 * result + (hostname != null ? hostname.hashCode() : 0); - result = 31 * result + port; - result = 31 * result + (directTls ? 1 : 0); - result = 31 * result + (authenticated ? 1 : 0); - result = 31 * result + priority; - return result; + return Objects.hashCode(ip, hostname, port, directTls, authenticated, priority); } public InetAddress getIp() { @@ -392,7 +398,7 @@ public class Resolver { return port; } - public DNSName getHostname() { + public DnsName getHostname() { return hostname; } @@ -405,38 +411,16 @@ public class Resolver { } @Override + @NonNull public String toString() { - return "Result{" + - "ip='" + (ip == null ? null : ip.getHostAddress()) + '\'' + - ", hostame='" + (hostname == null ? null : hostname.toString()) + '\'' + - ", port=" + port + - ", directTls=" + directTls + - ", authenticated=" + authenticated + - ", priority=" + priority + - '}'; - } - - @Override - public int compareTo(@NonNull Result result) { - if (result.priority == priority) { - if (directTls == result.directTls) { - if (ip == null && result.ip == null) { - return 0; - } else if (ip != null && result.ip != null) { - if (ip instanceof Inet4Address && result.ip instanceof Inet4Address) { - return 0; - } else { - return ip instanceof Inet4Address ? -1 : 1; - } - } else { - return ip != null ? -1 : 1; - } - } else { - return directTls ? -1 : 1; - } - } else { - return priority - result.priority; - } + return MoreObjects.toStringHelper(this) + .add("ip", ip) + .add("hostname", hostname) + .add("port", port) + .add("directTls", directTls) + .add("authenticated", authenticated) + .add("priority", priority) + .toString(); } public ContentValues toContentValues() { @@ -482,7 +466,7 @@ public class Resolver { return null; } try { - result.hostname = DNSName.from(hostPart.trim()); + result.hostname = DnsName.from(hostPart.trim()); } catch (final Exception e) { return null; } @@ -499,7 +483,7 @@ public class Resolver { result.ip = inetAddress; } else { try { - result.hostname = DNSName.from(hostname); + result.hostname = DnsName.from(hostname); } catch (final Exception e) { return null; } @@ -509,5 +493,4 @@ public class Resolver { return result; } } - } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 6390e891cb79f09f2325ea9e0c1ba5b87e67a3ae..31e150ac7447734b48112377aad6bf50aa927a09 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -354,7 +354,7 @@ public class XmppConnection implements Runnable { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": Thread was interrupted"); return; } - if (results.size() == 0) { + if (results.isEmpty()) { Log.e( Config.LOGTAG, account.getJid().asBareJid() + ": Resolver results were empty"); From 2140c2789aea90b36e497871f6b52c0db75a221b Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 12 May 2024 21:27:26 +0200 Subject: [PATCH 005/192] minor code clean up --- .../eu/siacs/conversations/utils/Resolver.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/Resolver.java b/src/main/java/eu/siacs/conversations/utils/Resolver.java index 88f2c601e47ad4812e65afd80146102554d8ce57..99be0592448eee99845bc05ffb58ca066e4bb997 100644 --- a/src/main/java/eu/siacs/conversations/utils/Resolver.java +++ b/src/main/java/eu/siacs/conversations/utils/Resolver.java @@ -314,7 +314,7 @@ public class Resolver { final AndroidDNSClient androidDNSClient = new AndroidDNSClient(Conversations.getContext()); final DnsQueryResult dnsQueryResult = androidDNSClient.query(question); - return new ResolverResult(question, dnsQueryResult, null); + return new ResolverResult<>(question, dnsQueryResult, null); }, DNS_QUERY_EXECUTOR); } @@ -334,16 +334,16 @@ public class Resolver { private boolean authenticated = false; private int priority; - static Result fromRecord(SRV srv, boolean directTls) { - Result result = new Result(); + static Result fromRecord(final SRV srv, final boolean directTls) { + final Result result = new Result(); result.port = srv.port; - result.hostname = srv.name; + result.hostname = srv.target; result.directTls = directTls; result.priority = srv.priority; return result; } - static Result createDefault(DnsName hostname, InetAddress ip) { + static Result createDefault(final DnsName hostname, final InetAddress ip) { Result result = new Result(); result.port = DEFAULT_PORT_XMPP; result.hostname = hostname; @@ -351,16 +351,16 @@ public class Resolver { return result; } - static Result createDefault(DnsName hostname) { + static Result createDefault(final DnsName hostname) { return createDefault(hostname, null); } - public static Result fromCursor(Cursor cursor) { + public static Result fromCursor(final Cursor cursor) { final Result result = new Result(); try { result.ip = InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndexOrThrow(IP))); - } catch (UnknownHostException e) { + } catch (final UnknownHostException e) { result.ip = null; } final String hostname = cursor.getString(cursor.getColumnIndexOrThrow(HOSTNAME)); From fb8c79405ec51b5511b1740751ef44c1f1c59222 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 12 May 2024 21:27:48 +0200 Subject: [PATCH 006/192] simplify swipe code --- .../ui/ConversationsOverviewFragment.java | 32 ++++++++----------- .../ui/adapter/ConversationAdapter.java | 4 +-- src/main/res/layout/item_conversation.xml | 2 ++ src/main/res/values/dimens.xml | 2 -- 4 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java index 19121f2252f54b31cd40160aed2c5e87483eac17..59776a0abeaedab0cb98e30e988b74cfbd8f54db 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java @@ -45,6 +45,7 @@ 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; @@ -90,42 +91,36 @@ public class ConversationsOverviewFragment extends XmppFragment { private FragmentConversationsOverviewBinding binding; private ConversationAdapter conversationsAdapter; private XmppActivity activity; - private float mSwipeEscapeVelocity = 0f; private final PendingActionHelper pendingActionHelper = new PendingActionHelper(); private final ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0,LEFT|RIGHT) { @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { - //todo maybe we can manually changing the position of the conversation + public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { return false; } @Override - public float getSwipeEscapeVelocity (float defaultValue) { - return mSwipeEscapeVelocity; + 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 onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, - float dX, float dY, int actionState, boolean isCurrentlyActive) { - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); - if(actionState != ItemTouchHelper.ACTION_STATE_IDLE){ - Paint paint = new Paint(); - paint.setColor(MaterialColors.getColor(viewHolder.itemView, com.google.android.material.R.attr.colorSecondaryFixedDim)); - paint.setStyle(Paint.Style.FILL); - c.drawRect(viewHolder.itemView.getLeft(),viewHolder.itemView.getTop() - ,viewHolder.itemView.getRight(),viewHolder.itemView.getBottom(), paint); + public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { + if (viewHolder instanceof ConversationAdapter.ConversationViewHolder conversationViewHolder) { + getDefaultUIUtil().clearView(conversationViewHolder.binding.frame); } } @Override - public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { - super.clearView(recyclerView, viewHolder); - viewHolder.itemView.setAlpha(1f); + public float getSwipeEscapeVelocity(final float defaultEscapeVelocity) { + return 32 * defaultEscapeVelocity; } @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int direction) { pendingActionHelper.execute(); int position = viewHolder.getLayoutPosition(); try { @@ -285,7 +280,6 @@ public class ConversationsOverviewFragment extends XmppFragment { @Override public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - this.mSwipeEscapeVelocity = getResources().getDimension(R.dimen.swipe_escape_velocity); this.binding = DataBindingUtil.inflate(inflater, R.layout.fragment_conversations_overview, container, false); this.binding.fab.setOnClickListener((view) -> StartConversationActivity.launch(getActivity())); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index 395781b0ec05025788b042e563579634f7cef858..a64ffd5f249329a0943f4316c38fa890e0a4f8ab 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -272,8 +272,8 @@ public class ConversationAdapter void onConversationClick(View view, Conversation conversation); } - static class ConversationViewHolder extends RecyclerView.ViewHolder { - private final ItemConversationBinding binding; + public static class ConversationViewHolder extends RecyclerView.ViewHolder { + public final ItemConversationBinding binding; private ConversationViewHolder(final ItemConversationBinding binding) { super(binding.getRoot()); diff --git a/src/main/res/layout/item_conversation.xml b/src/main/res/layout/item_conversation.xml index e0ab2fecdf352ec14dc548e33c407f84a85df5ba..1df9cb8d37d848910ae1ad4d531c02865355f7e3 100644 --- a/src/main/res/layout/item_conversation.xml +++ b/src/main/res/layout/item_conversation.xml @@ -3,11 +3,13 @@ xmlns:tools="http://schemas.android.com/tools"> diff --git a/src/main/res/values/dimens.xml b/src/main/res/values/dimens.xml index baa9d4ea902bb97ceb5e50d31bd16b7ec07186f0..1c812d9d1d8b5d6b004ee665ffdfa665747c7600 100644 --- a/src/main/res/values/dimens.xml +++ b/src/main/res/values/dimens.xml @@ -35,8 +35,6 @@ 4dp 8dp - 1200dp - 0.12 256dp From be265b2e86edbfafc1d312568cd104cf2d87a42f Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 13 May 2024 09:34:20 +0200 Subject: [PATCH 007/192] make allow_pm configurable --- .../conversations/entities/MucOptions.java | 14 +-- .../services/XmppConnectionService.java | 9 +- .../ui/ConferenceDetailsActivity.java | 55 ++++++---- .../ui/util/MucConfiguration.java | 103 ++++++++++-------- .../siacs/conversations/xmpp/forms/Data.java | 4 +- src/main/res/values/strings.xml | 1 + 6 files changed, 107 insertions(+), 79 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index c408d147ff0db514af49f28809650f2b100fb85f..38e97957a29d706b5817bfaa3312c6f090697f49 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -5,13 +5,6 @@ import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collections; -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.services.AvatarService; @@ -24,6 +17,13 @@ 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.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + public class MucOptions { public static final String STATUS_CODE_SELF_PRESENCE = "110"; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 484eaea0daafb15102b2c5503b7a666c5b47268b..cff35a97dd852140353af52fa362af6c0b3a3af1 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -3663,7 +3663,7 @@ public class XmppConnectionService extends Service { Element configuration = pubsub == null ? null : pubsub.findChild("configure"); Element x = configuration == null ? null : configuration.findChild("x", Namespace.DATA); if (x != null) { - Data data = Data.parse(x); + final Data data = Data.parse(x); data.submit(options); sendIqPacket(account, mIqGenerator.publishPubsubConfiguration(jid, node, data), new OnIqPacketReceived() { @Override @@ -3695,6 +3695,12 @@ public class XmppConnectionService extends Service { final boolean moderated = "1".equals(options.getString("muc#roomconfig_moderatedroom")); options.putString("members_by_default", moderated ? "0" : "1"); } + if (options.containsKey("muc#roomconfig_allowpm")) { + // ejabberd :-/ + final boolean allow = "anyone".equals(options.getString("muc#roomconfig_allowpm")); + options.putString("allow_private_messages", allow ? "1" : "0"); + options.putString("allow_private_messages_from_visitors", allow ? "anyone" : "nobody"); + } final IqPacket request = new IqPacket(IqPacket.TYPE.GET); request.setTo(conversation.getJid().asBareJid()); request.query("http://jabber.org/protocol/muc#owner"); @@ -3714,6 +3720,7 @@ public class XmppConnectionService extends Service { if (packet.getType() == IqPacket.TYPE.RESULT) { callback.onPushSucceeded(); } else { + Log.d(Config.LOGTAG,"failed: "+packet.toString()); callback.onPushFailed(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 5ed83219d19bb5bd41e6e1157cc281431c5e14cb..928a625d84e241b2a8756b44c3cc8517c628f1bf 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -128,28 +128,39 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } }; - private final OnClickListener mChangeConferenceSettings = new OnClickListener() { - @Override - public void onClick(View v) { - final MucOptions mucOptions = mConversation.getMucOptions(); - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ConferenceDetailsActivity.this); - MucConfiguration configuration = MucConfiguration.get(ConferenceDetailsActivity.this, mAdvancedMode, mucOptions); - builder.setTitle(configuration.title); - final boolean[] values = configuration.values; - builder.setMultiChoiceItems(configuration.names, values, (dialog, which, isChecked) -> values[which] = isChecked); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.confirm, (dialog, which) -> { - final Bundle options = configuration.toBundle(values); - options.putString("muc#roomconfig_persistentroom", "1"); - options.putString("{http://prosody.im/protocol/muc}roomconfig_allowmemberinvites", options.getString("muc#roomconfig_allowinvites")); - xmppConnectionService.pushConferenceConfiguration(mConversation, - options, - ConferenceDetailsActivity.this); - }); - builder.create().show(); - } - }; - + private final OnClickListener mChangeConferenceSettings = + new OnClickListener() { + @Override + public void onClick(View v) { + final MucOptions mucOptions = mConversation.getMucOptions(); + final MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(ConferenceDetailsActivity.this); + MucConfiguration configuration = + MucConfiguration.get( + ConferenceDetailsActivity.this, mAdvancedMode, mucOptions); + builder.setTitle(configuration.title); + final boolean[] values = configuration.values; + builder.setMultiChoiceItems( + configuration.names, + values, + (dialog, which, isChecked) -> values[which] = isChecked); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton( + R.string.confirm, + (dialog, which) -> { + final Bundle options = configuration.toBundle(values); + options.putString("muc#roomconfig_persistentroom", "1"); + if (options.containsKey("muc#roomconfig_allowinvites")) { + options.putString( + "{http://prosody.im/protocol/muc}roomconfig_allowmemberinvites", + options.getString("muc#roomconfig_allowinvites")); + } + xmppConnectionService.pushConferenceConfiguration( + mConversation, options, ConferenceDetailsActivity.this); + }); + builder.create().show(); + } + }; @Override public void onConversationUpdate() { diff --git a/src/main/java/eu/siacs/conversations/ui/util/MucConfiguration.java b/src/main/java/eu/siacs/conversations/ui/util/MucConfiguration.java index 3772a851e5574e2b9e9db1ddc09ffc63ccfa0916..1b6c4e6d6ee391e91147c013c421b65fda20e5fd 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/MucConfiguration.java +++ b/src/main/java/eu/siacs/conversations/ui/util/MucConfiguration.java @@ -10,13 +10,13 @@ import eu.siacs.conversations.entities.MucOptions; public class MucConfiguration { - public final @StringRes - int title; + public final @StringRes int title; public final String[] names; public final boolean[] values; public final Option[] options; - private MucConfiguration(@StringRes int title, String[] names, boolean[] values, Option[] options) { + private MucConfiguration( + @StringRes int title, String[] names, boolean[] values, Option[] options) { this.title = title; this.names = names; this.values = values; @@ -25,52 +25,62 @@ public class MucConfiguration { public static MucConfiguration get(Context context, boolean advanced, MucOptions mucOptions) { if (mucOptions.isPrivateAndNonAnonymous()) { - String[] names = new String[]{ - context.getString(R.string.allow_participants_to_edit_subject), - context.getString(R.string.allow_participants_to_invite_others) - }; - boolean[] values = new boolean[]{ - mucOptions.participantsCanChangeSubject(), - mucOptions.allowInvites() - }; - final Option[] options = new Option[]{ - new Option("muc#roomconfig_changesubject"), - new Option("muc#roomconfig_allowinvites") - }; + String[] names = + new String[] { + context.getString(R.string.allow_participants_to_edit_subject), + context.getString(R.string.allow_participants_to_invite_others) + }; + boolean[] values = + new boolean[] { + mucOptions.participantsCanChangeSubject(), mucOptions.allowInvites() + }; + final Option[] options = + new Option[] { + new Option("muc#roomconfig_changesubject"), + new Option("muc#roomconfig_allowinvites") + }; return new MucConfiguration(R.string.conference_options, names, values, options); } else { final String[] names; final boolean[] values; final Option[] options; if (advanced) { - names = new String[]{ - context.getString(R.string.non_anonymous), - context.getString(R.string.allow_participants_to_edit_subject), - context.getString(R.string.moderated) - }; - values = new boolean[]{ - mucOptions.nonanonymous(), - mucOptions.participantsCanChangeSubject(), - mucOptions.moderated() - }; - options = new Option[]{ - new Option("muc#roomconfig_whois", "anyone", "moderators"), - new Option("muc#roomconfig_changesubject"), - new Option("muc#roomconfig_moderatedroom") - }; + names = + new String[] { + context.getString(R.string.non_anonymous), + context.getString(R.string.allow_participants_to_edit_subject), + context.getString(R.string.moderated), + context.getString(R.string.allow_private_messages) + }; + values = + new boolean[] { + mucOptions.nonanonymous(), + mucOptions.participantsCanChangeSubject(), + mucOptions.moderated(), + mucOptions.allowPm() + }; + options = + new Option[] { + new Option("muc#roomconfig_whois", "anyone", "moderators"), + new Option("muc#roomconfig_changesubject"), + new Option("muc#roomconfig_moderatedroom"), + new Option("muc#roomconfig_allowpm", "anyone", "moderators"), + }; } else { - names = new String[]{ - context.getString(R.string.non_anonymous), - context.getString(R.string.allow_participants_to_edit_subject), - }; - values = new boolean[]{ - mucOptions.nonanonymous(), - mucOptions.participantsCanChangeSubject() - }; - options = new Option[]{ - new Option("muc#roomconfig_whois", "anyone", "moderators"), - new Option("muc#roomconfig_changesubject") - }; + names = + new String[] { + context.getString(R.string.non_anonymous), + context.getString(R.string.allow_participants_to_edit_subject), + }; + values = + new boolean[] { + mucOptions.nonanonymous(), mucOptions.participantsCanChangeSubject() + }; + options = + new Option[] { + new Option("muc#roomconfig_whois", "anyone", "moderators"), + new Option("muc#roomconfig_changesubject") + }; } return new MucConfiguration(R.string.channel_options, names, values, options); } @@ -108,9 +118,9 @@ public class MucConfiguration { public Bundle toBundle(boolean[] values) { Bundle bundle = new Bundle(); - for(int i = 0; i < values.length; ++i) { + for (int i = 0; i < values.length; ++i) { final Option option = options[i]; - bundle.putString(option.name,option.values[values[i] ? 0 : 1]); + bundle.putString(option.name, option.values[values[i] ? 0 : 1]); } return bundle; } @@ -121,13 +131,12 @@ public class MucConfiguration { private Option(String name) { this.name = name; - this.values = new String[]{"1","0"}; + this.values = new String[] {"1", "0"}; } private Option(String name, String on, String off) { this.name = name; - this.values = new String[]{on,off}; + this.values = new String[] {on, off}; } } - } diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java index e3bd9eb7481229914bd0fdc144373036420b7a53..58fa06cc51223fad1c87f0c41c5532bea0f41c69 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java +++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java @@ -59,8 +59,8 @@ public class Data extends Element { field.setValues(values); } - public void submit(Bundle options) { - for (Field field : getFields()) { + public void submit(final Bundle options) { + for (final Field field : getFields()) { if (options.containsKey(field.getFieldName())) { field.setValue(options.getString(field.getFieldName())); } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index a7acb7ecacb51174c2ddda2e838366a558c65e47..ecd0e1ee6c900abda76bc09494540a89892cb3c9 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -1064,4 +1064,5 @@ Full screen notifications Allow this app to show incoming call notifications that take up the full screen when the device is locked. Unsupported operation + Allow private messages From 49d6acee550963befb3c3daf2fe3926c9c291d33 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 13 May 2024 14:04:18 +0200 Subject: [PATCH 008/192] refactor crash reporter. collect stats on crash; not send --- .../services/XmppConnectionService.java | 1 - .../settings/MainSettingsFragment.java | 2 +- .../conversations/utils/ExceptionHandler.java | 69 ++++++---- .../conversations/utils/ExceptionHelper.java | 125 ++++++++---------- 4 files changed, 104 insertions(+), 93 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index cff35a97dd852140353af52fa362af6c0b3a3af1..80b4d6ddde600f4e4bc3b2a757921a77567ca0c5 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -1261,7 +1261,6 @@ public class XmppConnectionService extends Service { toggleForegroundService(); this.destroyed = false; OmemoSetting.load(this); - ExceptionHelper.init(getApplicationContext()); try { Security.insertProviderAt(Conscrypt.newProvider(), 1); } catch (Throwable throwable) { diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java index 4ab8ade3ceb472b2762017306aacda450ab2e82c..0373d715fdf9aa2aa690406b8be038abe86913a8 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java @@ -16,7 +16,7 @@ public class MainSettingsFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { setPreferencesFromResource(R.xml.preferences_main, rootKey); - final var about = findPreference("about"); + final var about = findPreference("about2"); final var connection = findPreference("connection"); if (about == null || connection == null) { throw new IllegalStateException( diff --git a/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java b/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java index 8a77e04bb884b4441e5b9d40a9e819a0873bee9c..c585ad38fd8293cbc24742d9b583791e3b8095f2 100644 --- a/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java +++ b/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java @@ -1,36 +1,59 @@ package eu.siacs.conversations.utils; import android.content.Context; +import android.os.Build; import androidx.annotation.NonNull; +import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; + +import eu.siacs.conversations.BuildConfig; +import eu.siacs.conversations.services.NotificationService; + +import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; -import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; - -import eu.siacs.conversations.services.NotificationService; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Locale; public class ExceptionHandler implements UncaughtExceptionHandler { - private final UncaughtExceptionHandler defaultHandler; - private final Context context; - - ExceptionHandler(final Context context) { - this.context = context; - this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); - } - - @Override - public void uncaughtException(@NonNull Thread thread, final Throwable throwable) { - NotificationService.cancelIncomingCallNotification(context); - final Writer stringWriter = new StringWriter(); - final PrintWriter printWriter = new PrintWriter(stringWriter); - throwable.printStackTrace(printWriter); - final String stacktrace = stringWriter.toString(); - printWriter.close(); - ExceptionHelper.writeToStacktraceFile(context, stacktrace); - this.defaultHandler.uncaughtException(thread, throwable); - } - + private static final SimpleDateFormat DATE_FORMAT = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ENGLISH); + + private final UncaughtExceptionHandler defaultHandler; + private final Context context; + + ExceptionHandler(final Context context) { + this.context = context; + this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + } + + @Override + public void uncaughtException(@NonNull Thread thread, final Throwable throwable) { + NotificationService.cancelIncomingCallNotification(context); + final String stacktrace; + try (final StringWriter stringWriter = new StringWriter(); + final PrintWriter printWriter = new PrintWriter(stringWriter)) { + throwable.printStackTrace(printWriter); + stacktrace = stringWriter.toString(); + } catch (final IOException e) { + return; + } + final List report = + ImmutableList.of( + String.format( + "Version: %s %s", BuildConfig.APP_NAME, BuildConfig.VERSION_NAME), + String.format("Manufacturer: %s", Strings.nullToEmpty(Build.MANUFACTURER)), + String.format("Device: %s", Strings.nullToEmpty(Build.DEVICE)), + String.format("Timestamp: %s", DATE_FORMAT.format(new Date())), + stacktrace); + ExceptionHelper.writeToStacktraceFile(context, Joiner.on("\n").join(report)); + this.defaultHandler.uncaughtException(thread, throwable); + } } diff --git a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java index a804f013b99d8323bc10b862c999cbc64e98efeb..f2c23aa8d8a2f51ea1fc436c034435002ae2c6a5 100644 --- a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java @@ -1,12 +1,12 @@ package eu.siacs.conversations.utils; import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.Signature; import android.util.Log; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.common.base.Charsets; +import com.google.common.io.CharSink; +import com.google.common.io.Files; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; @@ -17,19 +17,15 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.XmppActivity; -import java.io.BufferedReader; -import java.io.FileInputStream; +import java.io.File; import java.io.IOException; -import java.io.InputStreamReader; import java.io.OutputStream; import java.text.SimpleDateFormat; -import java.util.Date; import java.util.Locale; public class ExceptionHelper { private static final String FILENAME = "stacktrace.txt"; - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH); public static void init(final Context context) { if (Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler) { @@ -39,72 +35,65 @@ public class ExceptionHelper { } public static boolean checkForCrash(final XmppActivity activity) { + final XmppConnectionService service = + activity == null ? null : activity.xmppConnectionService; + if (service == null) { + return false; + } + final AppSettings appSettings = new AppSettings(activity); + if (!appSettings.isSendCrashReports() || Config.BUG_REPORTS == null) { + return false; + } + final Account account = AccountUtils.getFirstEnabled(service); + if (account == null) { + return false; + } + final var file = new File(activity.getCacheDir(), FILENAME); + if (!file.exists()) { + return false; + } + final String report; try { - final XmppConnectionService service = activity == null ? null : activity.xmppConnectionService; - if (service == null) { - return false; - } - final AppSettings appSettings = new AppSettings(activity); - if (!appSettings.isSendCrashReports() || Config.BUG_REPORTS == null) { - return false; - } - final Account account = AccountUtils.getFirstEnabled(service); - if (account == null) { - return false; - } - final FileInputStream file = activity.openFileInput(FILENAME); - final InputStreamReader inputStreamReader = new InputStreamReader(file); - final BufferedReader stacktrace = new BufferedReader(inputStreamReader); - final StringBuilder report = new StringBuilder(); - final PackageManager pm = activity.getPackageManager(); - final PackageInfo packageInfo; - try { - packageInfo = pm.getPackageInfo(activity.getPackageName(), PackageManager.GET_SIGNATURES); - final String versionName = packageInfo.versionName; - final int versionCode = packageInfo.versionCode; - final int version = versionCode > 10000 ? (versionCode / 100) : versionCode; - report.append(String.format(Locale.ROOT, "Version: %s(%d)", versionName, version)).append('\n'); - report.append("Last Update: ").append(DATE_FORMAT.format(new Date(packageInfo.lastUpdateTime))).append('\n'); - Signature[] signatures = packageInfo.signatures; - if (signatures != null && signatures.length >= 1) { - report.append("SHA-1: ").append(CryptoHelper.getFingerprintCert(packageInfo.signatures[0].toByteArray())).append('\n'); - } - report.append('\n'); - } catch (final Exception e) { - return false; - } - String line; - while ((line = stacktrace.readLine()) != null) { - report.append(line); - report.append('\n'); - } - file.close(); - activity.deleteFile(FILENAME); - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); - builder.setTitle(activity.getString(R.string.crash_report_title, activity.getString(R.string.app_name))); - builder.setMessage(activity.getString(R.string.crash_report_message, activity.getString(R.string.app_name))); - builder.setPositiveButton(activity.getText(R.string.send_now), (dialog, which) -> { - - Log.d(Config.LOGTAG, "using account=" + account.getJid().asBareJid() + " to send in stack trace"); - Conversation conversation = service.findOrCreateConversation(account, Config.BUG_REPORTS, false, true); - Message message = new Message(conversation, report.toString(), Message.ENCRYPTION_NONE); - service.sendMessage(message); - }); - builder.setNegativeButton(activity.getText(R.string.send_never), (dialog, which) -> appSettings.setSendCrashReports(false)); - builder.create().show(); - return true; - } catch (final IOException ignored) { + report = Files.asCharSource(file, Charsets.UTF_8).read(); + } catch (final IOException e) { return false; } + if (file.delete()) { + Log.d(Config.LOGTAG, "deleted crash report file"); + } + final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); + builder.setTitle( + activity.getString( + R.string.crash_report_title, activity.getString(R.string.app_name))); + builder.setMessage( + activity.getString( + R.string.crash_report_message, activity.getString(R.string.app_name))); + builder.setPositiveButton( + activity.getText(R.string.send_now), + (dialog, which) -> { + Log.d( + Config.LOGTAG, + "using account=" + + account.getJid().asBareJid() + + " to send in stack trace"); + Conversation conversation = + service.findOrCreateConversation( + account, Config.BUG_REPORTS, false, true); + Message message = new Message(conversation, report, Message.ENCRYPTION_NONE); + service.sendMessage(message); + }); + builder.setNegativeButton( + activity.getText(R.string.send_never), + (dialog, which) -> appSettings.setSendCrashReports(false)); + builder.create().show(); + return true; } - static void writeToStacktraceFile(Context context, String msg) { + static void writeToStacktraceFile(final Context context, final String msg) { try { - OutputStream os = context.openFileOutput(FILENAME, Context.MODE_PRIVATE); - os.write(msg.getBytes()); - os.flush(); - os.close(); - } catch (IOException ignored) { + Files.asCharSink(new File(context.getCacheDir(), FILENAME), Charsets.UTF_8).write(msg); + } catch (IOException e) { + Log.w(Config.LOGTAG, "could not write stack trace to file", e); } } } From c2b3e5e2ca6f2911278ed9a586330349d3f25e39 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 13 May 2024 16:13:55 +0200 Subject: [PATCH 009/192] ask again for notification permission if dialog was dismissed earlier --- .../java/eu/siacs/conversations/ui/ConversationsActivity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index 7d3846ebeae6ff2d378221b8b97a5fe034e0e807..fc03846af9eaf9a5b2936d128190384805b77d2b 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -214,6 +214,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (openBatteryOptimizationDialogIfNeeded()) { return; } + requestNotificationPermissionIfNeeded(); } } @@ -237,7 +238,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio intent.setData(uri); try { startActivityForResult(intent, REQUEST_BATTERY_OP); - } catch (ActivityNotFoundException e) { + } catch (final ActivityNotFoundException e) { Toast.makeText(this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); } }); From 8bc28eb9eaae263925470cf8b684dd852b0e5a90 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 13 May 2024 16:14:52 +0200 Subject: [PATCH 010/192] fix regression in avatar/bubble distance --- src/main/res/layout/item_message_sent.xml | 24 ++++++++--------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/main/res/layout/item_message_sent.xml b/src/main/res/layout/item_message_sent.xml index f29a5d96c0b41a52654737c387c2c027ea8e08ca..be511878521c585ab81e8f54d461c7554fa9e2e7 100644 --- a/src/main/res/layout/item_message_sent.xml +++ b/src/main/res/layout/item_message_sent.xml @@ -9,23 +9,15 @@ android:paddingHorizontal="8dp" android:paddingVertical="4dp"> - - - - - + android:layout_marginStart="6dp" + android:scaleType="fitXY" + app:riv_corner_radius="8dp" /> Date: Mon, 13 May 2024 18:30:25 +0200 Subject: [PATCH 011/192] ask for notification permission in start chat screen --- .../ui/StartConversationActivity.java | 889 ++++++++++-------- 1 file changed, 517 insertions(+), 372 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index acd0320ed52ac011f4e0f0e4e14990d079ec8383..8710a746facff4aaa94341cdfc843cb50bde61d6 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -34,7 +34,6 @@ import android.widget.AutoCompleteTextView; import android.widget.CheckBox; import android.widget.EditText; import android.widget.ListView; -import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; @@ -45,7 +44,7 @@ import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.PopupMenu; -import androidx.core.content.ContextCompat; +import androidx.core.app.ActivityCompat; import androidx.databinding.DataBindingUtil; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; @@ -57,15 +56,11 @@ 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; +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 java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -93,9 +88,22 @@ import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.XmppConnection; -public class StartConversationActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, CreatePrivateGroupChatDialog.CreateConferenceDialogListener, JoinConferenceDialog.JoinConferenceDialogListener, SwipeRefreshLayout.OnRefreshListener, CreatePublicChannelDialog.CreatePublicChannelDialogListener { +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class StartConversationActivity extends XmppActivity + implements XmppConnectionService.OnConversationUpdate, + OnRosterUpdate, + OnUpdateBlocklist, + CreatePrivateGroupChatDialog.CreateConferenceDialogListener, + JoinConferenceDialog.JoinConferenceDialogListener, + SwipeRefreshLayout.OnRefreshListener, + CreatePublicChannelDialog.CreatePublicChannelDialogListener { - private static final String PREF_KEY_CONTACT_INTEGRATION_CONSENT = "contact_list_integration_consent"; + private static final String PREF_KEY_CONTACT_INTEGRATION_CONSENT = + "contact_list_integration_consent"; public static final String EXTRA_INVITE_URI = "eu.siacs.conversations.invite_uri"; @@ -117,125 +125,137 @@ public class StartConversationActivity extends XmppActivity implements XmppConne private final AtomicBoolean mOpenedFab = new AtomicBoolean(false); private boolean mHideOfflineContacts = false; private boolean createdByViewIntent = false; - private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() { - - @Override - public boolean onMenuItemActionExpand(MenuItem item) { - mSearchEditText.post(() -> { - updateSearchViewHint(); - mSearchEditText.requestFocus(); - if (oneShotKeyboardSuppress.compareAndSet(true, false)) { - return; - } - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm != null) { - imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT); + private final MenuItem.OnActionExpandListener mOnActionExpandListener = + new MenuItem.OnActionExpandListener() { + + @Override + public boolean onMenuItemActionExpand(@NonNull final MenuItem item) { + mSearchEditText.post( + () -> { + updateSearchViewHint(); + mSearchEditText.requestFocus(); + if (oneShotKeyboardSuppress.compareAndSet(true, false)) { + return; + } + InputMethodManager imm = + (InputMethodManager) + getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput( + mSearchEditText, InputMethodManager.SHOW_IMPLICIT); + } + }); + if (binding.speedDial.isOpen()) { + binding.speedDial.close(); + } + return true; } - }); - if (binding.speedDial.isOpen()) { - binding.speedDial.close(); - } - return true; - } - @Override - public boolean onMenuItemActionCollapse(MenuItem item) { - SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this); - mSearchEditText.setText(""); - filter(null); - return true; - } - }; - private final TextWatcher mSearchTextWatcher = new TextWatcher() { + @Override + public boolean onMenuItemActionCollapse(@NonNull final MenuItem item) { + SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this); + mSearchEditText.setText(""); + filter(null); + return true; + } + }; + private final TextWatcher mSearchTextWatcher = + new TextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - filter(editable.toString()); - } + @Override + public void afterTextChanged(Editable editable) { + filter(editable.toString()); + } - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } + @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 onTextChanged(CharSequence s, int start, int before, int count) {} + }; private MenuItem mMenuSearchView; - private final ListItemAdapter.OnTagClickedListener mOnTagClickedListener = new ListItemAdapter.OnTagClickedListener() { - @Override - public void onTagClicked(String tag) { - if (mMenuSearchView != null) { - mMenuSearchView.expandActionView(); - mSearchEditText.setText(""); - mSearchEditText.append(tag); - filter(tag); - } - } - }; + private final ListItemAdapter.OnTagClickedListener mOnTagClickedListener = + new ListItemAdapter.OnTagClickedListener() { + @Override + public void onTagClicked(String tag) { + if (mMenuSearchView != null) { + mMenuSearchView.expandActionView(); + mSearchEditText.setText(""); + mSearchEditText.append(tag); + filter(tag); + } + } + }; private Pair mPostponedActivityResult; private Toast mToast; - private final UiCallback mAdhocConferenceCallback = new UiCallback() { - @Override - public void success(final Conversation conversation) { - runOnUiThread(() -> { - hideToast(); - switchToConversation(conversation); - }); - } - - @Override - public void error(final int errorCode, Conversation object) { - runOnUiThread(() -> replaceToast(getString(errorCode))); - } + private final UiCallback mAdhocConferenceCallback = + new UiCallback<>() { + @Override + public void success(final Conversation conversation) { + runOnUiThread( + () -> { + hideToast(); + switchToConversation(conversation); + }); + } - @Override - public void userInputRequired(PendingIntent pi, Conversation object) { + @Override + public void error(final int errorCode, Conversation object) { + runOnUiThread(() -> replaceToast(getString(errorCode))); + } - } - }; + @Override + public void userInputRequired(PendingIntent pi, Conversation object) {} + }; private ActivityStartConversationBinding binding; - private final TextView.OnEditorActionListener mSearchDone = new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - int pos = binding.startConversationViewPager.getCurrentItem(); - if (pos == 0) { - if (contacts.size() == 1) { - openConversationForContact((Contact) contacts.get(0)); - return true; - } else if (contacts.size() == 0 && conferences.size() == 1) { - openConversationsForBookmark((Bookmark) conferences.get(0)); - return true; - } - } else { - if (conferences.size() == 1) { - openConversationsForBookmark((Bookmark) conferences.get(0)); - return true; - } else if (conferences.size() == 0 && contacts.size() == 1) { - openConversationForContact((Contact) contacts.get(0)); + private final TextView.OnEditorActionListener mSearchDone = + new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + int pos = binding.startConversationViewPager.getCurrentItem(); + if (pos == 0) { + if (contacts.size() == 1) { + openConversationForContact((Contact) contacts.get(0)); + return true; + } else if (contacts.isEmpty() && conferences.size() == 1) { + openConversationsForBookmark((Bookmark) conferences.get(0)); + return true; + } + } else { + if (conferences.size() == 1) { + openConversationsForBookmark((Bookmark) conferences.get(0)); + return true; + } else if (conferences.isEmpty() && contacts.size() == 1) { + openConversationForContact((Contact) contacts.get(0)); + return true; + } + } + SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this); + mListPagerAdapter.requestFocus(pos); return true; } - } - SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this); - mListPagerAdapter.requestFocus(pos); - return true; - } - }; + }; - public static void populateAccountSpinner(final Context context, final List accounts, final AutoCompleteTextView spinner) { + public static void populateAccountSpinner( + final Context context, + final List accounts, + final AutoCompleteTextView spinner) { if (accounts.isEmpty()) { - ArrayAdapter adapter = new ArrayAdapter<>(context, - R.layout.item_autocomplete, - Collections.singletonList(context.getString(R.string.no_accounts))); + ArrayAdapter adapter = + new ArrayAdapter<>( + context, + R.layout.item_autocomplete, + Collections.singletonList(context.getString(R.string.no_accounts))); adapter.setDropDownViewResource(R.layout.item_autocomplete); spinner.setAdapter(adapter); spinner.setEnabled(false); } else { - final ArrayAdapter adapter = new ArrayAdapter<>(context, R.layout.item_autocomplete, accounts); + final ArrayAdapter adapter = + new ArrayAdapter<>(context, R.layout.item_autocomplete, accounts); adapter.setDropDownViewResource(R.layout.item_autocomplete); spinner.setAdapter(adapter); spinner.setEnabled(true); - spinner.setText(Iterables.getFirst(accounts,null),false); + spinner.setText(Iterables.getFirst(accounts, null), false); } } @@ -252,7 +272,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } private static boolean isViewIntent(final Intent i) { - return i != null && (Intent.ACTION_VIEW.equals(i.getAction()) || Intent.ACTION_SENDTO.equals(i.getAction()) || i.hasExtra(EXTRA_INVITE_URI)); + return i != null + && (Intent.ACTION_VIEW.equals(i.getAction()) + || Intent.ACTION_SENDTO.equals(i.getAction()) + || i.hasExtra(EXTRA_INVITE_URI)); } protected void hideToast() { @@ -282,12 +305,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne inflateFab(binding.speedDial, R.menu.start_conversation_fab_submenu); binding.tabLayout.setupWithViewPager(binding.startConversationViewPager); - binding.startConversationViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { - @Override - public void onPageSelected(int position) { - updateSearchViewHint(); - } - }); + binding.startConversationViewPager.addOnPageChangeListener( + new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(int position) { + updateSearchViewHint(); + } + }); mListPagerAdapter = new ListPagerAdapter(getSupportFragmentManager()); binding.startConversationViewPager.setAdapter(mListPagerAdapter); @@ -297,9 +321,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne final SharedPreferences preferences = getPreferences(); - this.mHideOfflineContacts = QuickConversationsService.isConversations() && preferences.getBoolean("hide_offline", false); + this.mHideOfflineContacts = + QuickConversationsService.isConversations() + && preferences.getBoolean("hide_offline", false); - final boolean startSearching = preferences.getBoolean("start_searching", getResources().getBoolean(R.bool.start_searching)); + final boolean startSearching = + preferences.getBoolean( + "start_searching", getResources().getBoolean(R.bool.start_searching)); final Intent intent; if (savedInstanceState == null) { @@ -320,39 +348,45 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } else if (startSearching && mInitialSearchValue.peek() == null) { mInitialSearchValue.push(""); } - mRequestedContactsPermission.set(savedInstanceState != null && savedInstanceState.getBoolean("requested_contacts_permission", false)); - mOpenedFab.set(savedInstanceState != null && savedInstanceState.getBoolean("opened_fab", false)); - binding.speedDial.setOnActionSelectedListener(actionItem -> { - final String searchString = mSearchEditText != null ? mSearchEditText.getText().toString() : null; - final String prefilled; - if (isValidJid(searchString)) { - prefilled = Jid.ofEscaped(searchString).toEscapedString(); - } else { - prefilled = null; - } - switch (actionItem.getId()) { - case R.id.discover_public_channels: - if (QuickConversationsService.isPlayStoreFlavor()) { - throw new IllegalStateException("Channel discovery is not available on Google Play flavor"); + mRequestedContactsPermission.set( + savedInstanceState != null + && savedInstanceState.getBoolean("requested_contacts_permission", false)); + mOpenedFab.set( + savedInstanceState != null && savedInstanceState.getBoolean("opened_fab", false)); + binding.speedDial.setOnActionSelectedListener( + actionItem -> { + final String searchString = + mSearchEditText != null ? mSearchEditText.getText().toString() : null; + final String prefilled; + if (isValidJid(searchString)) { + prefilled = Jid.ofEscaped(searchString).toEscapedString(); } else { - startActivity(new Intent(this, ChannelDiscoveryActivity.class)); + prefilled = null; } - break; - case R.id.join_public_channel: - showJoinConferenceDialog(prefilled); - break; - case R.id.create_private_group_chat: - showCreatePrivateGroupChatDialog(); - break; - case R.id.create_public_channel: - showPublicChannelDialog(); - break; - case R.id.create_contact: - showCreateContactDialog(prefilled, null); - break; - } - return false; - }); + switch (actionItem.getId()) { + case R.id.discover_public_channels: + if (QuickConversationsService.isPlayStoreFlavor()) { + throw new IllegalStateException( + "Channel discovery is not available on Google Play flavor"); + } else { + startActivity(new Intent(this, ChannelDiscoveryActivity.class)); + } + break; + case R.id.join_public_channel: + showJoinConferenceDialog(prefilled); + break; + case R.id.create_private_group_chat: + showCreatePrivateGroupChatDialog(); + break; + case R.id.create_public_channel: + showPublicChannelDialog(); + break; + case R.id.create_contact: + showCreateContactDialog(prefilled, null); + break; + } + return false; + }); } private void inflateFab(final SpeedDialView speedDialView, final @MenuRes int menuRes) { @@ -362,14 +396,26 @@ public class StartConversationActivity extends XmppActivity implements XmppConne final Menu menu = popupMenu.getMenu(); for (int i = 0; i < menu.size(); i++) { final MenuItem menuItem = menu.getItem(i); - if (QuickConversationsService.isPlayStoreFlavor() && menuItem.getItemId() == R.id.discover_public_channels) { + if (QuickConversationsService.isPlayStoreFlavor() + && menuItem.getItemId() == R.id.discover_public_channels) { continue; } - final SpeedDialActionItem actionItem = new SpeedDialActionItem.Builder(menuItem.getItemId(), menuItem.getIcon()) - .setLabel(menuItem.getTitle() != null ? menuItem.getTitle().toString() : null) - .setFabImageTintColor(MaterialColors.getColor(speedDialView, com.google.android.material.R.attr.colorOnSurface)) - .setFabBackgroundColor(MaterialColors.getColor(speedDialView, com.google.android.material.R.attr.colorSurfaceContainerHighest)) - .create(); + final SpeedDialActionItem actionItem = + new SpeedDialActionItem.Builder(menuItem.getItemId(), menuItem.getIcon()) + .setLabel( + menuItem.getTitle() != null + ? menuItem.getTitle().toString() + : null) + .setFabImageTintColor( + MaterialColors.getColor( + speedDialView, + com.google.android.material.R.attr.colorOnSurface)) + .setFabBackgroundColor( + MaterialColors.getColor( + speedDialView, + com.google.android.material.R.attr + .colorSurfaceContainerHighest)) + .create(); speedDialView.addActionItem(actionItem); } } @@ -386,12 +432,16 @@ public class StartConversationActivity extends XmppActivity implements XmppConne @Override public void onSaveInstanceState(Bundle savedInstanceState) { Intent pendingIntent = pendingViewIntent.peek(); - savedInstanceState.putParcelable("intent", pendingIntent != null ? pendingIntent : getIntent()); - savedInstanceState.putBoolean("requested_contacts_permission", mRequestedContactsPermission.get()); + savedInstanceState.putParcelable( + "intent", pendingIntent != null ? pendingIntent : getIntent()); + savedInstanceState.putBoolean( + "requested_contacts_permission", mRequestedContactsPermission.get()); savedInstanceState.putBoolean("opened_fab", mOpenedFab.get()); savedInstanceState.putBoolean("created_by_view_intent", createdByViewIntent); if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) { - savedInstanceState.putString("search", mSearchEditText != null ? mSearchEditText.getText().toString() : null); + savedInstanceState.putString( + "search", + mSearchEditText != null ? mSearchEditText.getText().toString() : null); } super.onSaveInstanceState(savedInstanceState); } @@ -399,11 +449,24 @@ public class StartConversationActivity extends XmppActivity implements XmppConne @Override public void onStart() { super.onStart(); - if (pendingViewIntent.peek() == null) { - askForContactsPermissions(); - } mConferenceAdapter.refreshSettings(); mContactsAdapter.refreshSettings(); + if (pendingViewIntent.peek() == null) { + if (askForContactsPermissions()) { + return; + } + requestNotificationPermissionIfNeeded(); + } + } + + private void requestNotificationPermissionIfNeeded() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED) { + requestPermissions( + new String[] {Manifest.permission.POST_NOTIFICATIONS}, + REQUEST_POST_NOTIFICATION); + } } @Override @@ -423,7 +486,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } protected void openConversationForContact(Contact contact) { - Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true); + Conversation conversation = + xmppConnectionService.findOrCreateConversation( + contact.getAccount(), contact.getJid(), false, true); SoftKeyboardUtils.hideSoftKeyboard(this); switchToConversation(conversation); } @@ -448,9 +513,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne shareIntent.putExtra(Intent.EXTRA_TEXT, "xmpp:" + address + "?join"); shareIntent.setType("text/plain"); try { - context.startActivity(Intent.createChooser(shareIntent, context.getText(R.string.share_uri_with))); + context.startActivity( + Intent.createChooser(shareIntent, context.getText(R.string.share_uri_with))); } catch (ActivityNotFoundException e) { - Toast.makeText(context, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show(); + Toast.makeText(context, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT) + .show(); } } @@ -460,7 +527,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show(); return; } - final Conversation conversation = xmppConnectionService.findOrCreateConversation(bookmark.getAccount(), jid, true, true, true); + final Conversation conversation = + xmppConnectionService.findOrCreateConversation( + bookmark.getAccount(), jid, true, true, true); bookmark.setConversation(conversation); if (!bookmark.autojoin()) { bookmark.setAutojoin(true); @@ -493,11 +562,15 @@ public class StartConversationActivity extends XmppActivity implements XmppConne final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); 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())); - builder.setPositiveButton(R.string.delete, (dialog, which) -> { - xmppConnectionService.deleteContactOnServer(contact); - filter(mSearchEditText.getText().toString()); - }); + builder.setMessage( + JidDialog.style( + this, R.string.remove_contact_text, contact.getJid().toEscapedString())); + builder.setPositiveButton( + R.string.delete, + (dialog, which) -> { + xmppConnectionService.deleteContactOnServer(contact); + filter(mSearchEditText.getText().toString()); + }); builder.create().show(); } @@ -510,21 +583,28 @@ public class StartConversationActivity extends XmppActivity implements XmppConne builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.delete_bookmark); if (hasConversation) { - builder.setMessage(JidDialog.style(this, R.string.remove_bookmark_and_close, bookmark.getJid().toEscapedString())); + builder.setMessage( + JidDialog.style( + this, + R.string.remove_bookmark_and_close, + bookmark.getJid().toEscapedString())); } else { - builder.setMessage(JidDialog.style(this, R.string.remove_bookmark, bookmark.getJid().toEscapedString())); - } - builder.setPositiveButton(hasConversation ? R.string.delete_and_close : R.string.delete, (dialog, which) -> { - bookmark.setConversation(null); - final Account account = bookmark.getAccount(); - xmppConnectionService.deleteBookmark(account, bookmark); - if (conversation != null) { - xmppConnectionService.archiveConversation(conversation); - } - filter(mSearchEditText.getText().toString()); - }); + builder.setMessage( + JidDialog.style( + this, R.string.remove_bookmark, bookmark.getJid().toEscapedString())); + } + builder.setPositiveButton( + hasConversation ? R.string.delete_and_close : R.string.delete, + (dialog, which) -> { + bookmark.setConversation(null); + final Account account = bookmark.getAccount(); + xmppConnectionService.deleteBookmark(account, bookmark); + if (conversation != null) { + xmppConnectionService.archiveConversation(conversation); + } + filter(mSearchEditText.getText().toString()); + }); builder.create().show(); - } @SuppressLint("InflateParams") @@ -535,45 +615,52 @@ public class StartConversationActivity extends XmppActivity implements XmppConne ft.remove(prev); } ft.addToBackStack(null); - EnterJidDialog dialog = EnterJidDialog.newInstance( - mActivatedAccounts, - getString(R.string.add_contact), - getString(R.string.add), - prefilledJid, - invite == null ? null : invite.account, - invite == null || !invite.hasFingerprints(), - true - ); - - dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> { - if (!xmppConnectionServiceBound) { - return false; - } + EnterJidDialog dialog = + EnterJidDialog.newInstance( + mActivatedAccounts, + getString(R.string.add_contact), + getString(R.string.add), + prefilledJid, + invite == null ? null : invite.account, + invite == null || !invite.hasFingerprints(), + true); + + dialog.setOnEnterJidDialogPositiveListener( + (accountJid, contactJid) -> { + if (!xmppConnectionServiceBound) { + return false; + } - final Account account = xmppConnectionService.findAccountByJid(accountJid); - if (account == null) { - return true; - } + final Account account = xmppConnectionService.findAccountByJid(accountJid); + if (account == null) { + return true; + } - final Contact contact = account.getRoster().getContact(contactJid); - if (invite != null && invite.getName() != null) { - contact.setServerName(invite.getName()); - } - if (contact.isSelf()) { - switchToConversation(contact); - return true; - } else if (contact.showInRoster()) { - throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists)); - } else { - final String preAuth = invite == null ? null : invite.getParameter(XmppUri.PARAMETER_PRE_AUTH); - xmppConnectionService.createContact(contact, true, preAuth); - if (invite != null && invite.hasFingerprints()) { - xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints()); - } - switchToConversationDoNotAppend(contact, invite == null ? null : invite.getBody()); - return true; - } - }); + final Contact contact = account.getRoster().getContact(contactJid); + if (invite != null && invite.getName() != null) { + contact.setServerName(invite.getName()); + } + if (contact.isSelf()) { + switchToConversation(contact); + return true; + } else if (contact.showInRoster()) { + throw new EnterJidDialog.JidError( + getString(R.string.contact_already_exists)); + } else { + final String preAuth = + invite == null + ? null + : invite.getParameter(XmppUri.PARAMETER_PRE_AUTH); + xmppConnectionService.createContact(contact, true, preAuth); + if (invite != null && invite.hasFingerprints()) { + xmppConnectionService.verifyFingerprints( + contact, invite.getFingerprints()); + } + switchToConversationDoNotAppend( + contact, invite == null ? null : invite.getBody()); + return true; + } + }); dialog.show(ft, FRAGMENT_TAG_DIALOG); } @@ -585,7 +672,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne ft.remove(prev); } ft.addToBackStack(null); - JoinConferenceDialog joinConferenceFragment = JoinConferenceDialog.newInstance(prefilledJid, mActivatedAccounts); + JoinConferenceDialog joinConferenceFragment = + JoinConferenceDialog.newInstance(prefilledJid, mActivatedAccounts); joinConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG); } @@ -596,7 +684,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne ft.remove(prev); } ft.addToBackStack(null); - CreatePrivateGroupChatDialog createConferenceFragment = CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts); + CreatePrivateGroupChatDialog createConferenceFragment = + CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts); createConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG); } @@ -607,11 +696,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne ft.remove(prev); } ft.addToBackStack(null); - CreatePublicChannelDialog dialog = CreatePublicChannelDialog.newInstance(mActivatedAccounts); + CreatePublicChannelDialog dialog = + CreatePublicChannelDialog.newInstance(mActivatedAccounts); dialog.show(ft, FRAGMENT_TAG_DIALOG); } - public static Account getSelectedAccount(final Context context, final AutoCompleteTextView spinner) { + public static Account getSelectedAccount( + final Context context, final AutoCompleteTextView spinner) { if (spinner == null || !spinner.isEnabled()) { return null; } @@ -633,12 +724,16 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } protected void switchToConversation(Contact contact) { - Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true); + Conversation conversation = + xmppConnectionService.findOrCreateConversation( + contact.getAccount(), contact.getJid(), false, true); switchToConversation(conversation); } protected void switchToConversationDoNotAppend(Contact contact, String body) { - Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true); + Conversation conversation = + xmppConnectionService.findOrCreateConversation( + contact.getAccount(), contact.getJid(), false, true); switchToConversationDoNotAppend(conversation, body); } @@ -752,11 +847,15 @@ public class StartConversationActivity extends XmppActivity implements XmppConne this.mPostponedActivityResult = null; if (requestCode == REQUEST_CREATE_CONFERENCE) { Account account = extractAccount(intent); - final String name = intent.getStringExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME); + final String name = + intent.getStringExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME); final List jids = ChooseContactActivity.extractJabberIds(intent); if (account != null && jids.size() > 0) { - if (xmppConnectionService.createAdhocConference(account, name, jids, mAdhocConferenceCallback)) { - mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG); + if (xmppConnectionService.createAdhocConference( + account, name, jids, mAdhocConferenceCallback)) { + mToast = + Toast.makeText( + this, R.string.creating_conference, Toast.LENGTH_LONG); mToast.show(); } } @@ -768,104 +867,109 @@ public class StartConversationActivity extends XmppActivity implements XmppConne super.onActivityResult(requestCode, requestCode, intent); } - private void askForContactsPermissions() { - if (QuickConversationsService.isContactListIntegration(this)) { - if (checkSelfPermission(Manifest.permission.READ_CONTACTS) - != PackageManager.PERMISSION_GRANTED) { - if (mRequestedContactsPermission.compareAndSet(false, true)) { - final String consent = - PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) - .getString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, null); - final boolean requiresConsent = - (QuickConversationsService.isQuicksy() - || QuickConversationsService.isPlayStoreFlavor()) - && !"agreed".equals(consent); - if (requiresConsent && "declined".equals(consent)) { - Log.d(Config.LOGTAG,"not asking for contacts permission because consent has been declined"); - return; - } - if (requiresConsent - || shouldShowRequestPermissionRationale( - Manifest.permission.READ_CONTACTS)) { - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); - final AtomicBoolean requestPermission = new AtomicBoolean(false); - if (QuickConversationsService.isQuicksy()) { - builder.setTitle(R.string.quicksy_wants_your_consent); - builder.setMessage( - Html.fromHtml( - getString(R.string.sync_with_contacts_quicksy_static))); - } else { - builder.setTitle(R.string.sync_with_contacts); - builder.setMessage( - getString( - R.string.sync_with_contacts_long, - getString(R.string.app_name))); - } - @StringRes int confirmButtonText; - if (requiresConsent) { - confirmButtonText = R.string.agree_and_continue; - } else { - confirmButtonText = R.string.next; - } - builder.setPositiveButton( - confirmButtonText, - (dialog, which) -> { - if (requiresConsent) { - PreferenceManager.getDefaultSharedPreferences( - getApplicationContext()) - .edit() - .putString( - PREF_KEY_CONTACT_INTEGRATION_CONSENT, "agreed") - .apply(); - } - if (requestPermission.compareAndSet(false, true)) { - requestPermissions( - new String[] {Manifest.permission.READ_CONTACTS}, - REQUEST_SYNC_CONTACTS); - } - }); - if (requiresConsent) { - builder.setNegativeButton(R.string.decline, (dialog, which) -> PreferenceManager.getDefaultSharedPreferences( - getApplicationContext()) - .edit() - .putString( - PREF_KEY_CONTACT_INTEGRATION_CONSENT, "declined") - .apply()); - } else { - builder.setOnDismissListener( - dialog -> { - if (requestPermission.compareAndSet(false, true)) { - requestPermissions( - new String[] { - Manifest.permission.READ_CONTACTS - }, - REQUEST_SYNC_CONTACTS); - } - }); - } - builder.setCancelable(requiresConsent); - final AlertDialog dialog = builder.create(); - dialog.setCanceledOnTouchOutside(requiresConsent); - dialog.setOnShowListener( - dialogInterface -> { - final TextView tv = dialog.findViewById(android.R.id.message); - if (tv != null) { - tv.setMovementMethod(LinkMovementMethod.getInstance()); - } - }); - dialog.show(); - } else { - requestPermissions( - new String[] {Manifest.permission.READ_CONTACTS}, - REQUEST_SYNC_CONTACTS); - } + private boolean askForContactsPermissions() { + if (!QuickConversationsService.isContactListIntegration(this)) { + return false; + } + if (checkSelfPermission(Manifest.permission.READ_CONTACTS) + == PackageManager.PERMISSION_GRANTED) { + return false; + } + if (mRequestedContactsPermission.compareAndSet(false, true)) { + final ImmutableList.Builder permissionBuilder = new ImmutableList.Builder<>(); + permissionBuilder.add(Manifest.permission.READ_CONTACTS); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + permissionBuilder.add(Manifest.permission.POST_NOTIFICATIONS); + } + final String[] permission = permissionBuilder.build().toArray(new String[0]); + final String consent = + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) + .getString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, null); + final boolean requiresConsent = + (QuickConversationsService.isQuicksy() + || QuickConversationsService.isPlayStoreFlavor()) + && !"agreed".equals(consent); + if (requiresConsent && "declined".equals(consent)) { + Log.d( + Config.LOGTAG, + "not asking for contacts permission because consent has been declined"); + return false; + } + if (requiresConsent + || shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) { + final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); + final AtomicBoolean requestPermission = new AtomicBoolean(false); + if (QuickConversationsService.isQuicksy()) { + builder.setTitle(R.string.quicksy_wants_your_consent); + builder.setMessage( + Html.fromHtml(getString(R.string.sync_with_contacts_quicksy_static))); + } else { + builder.setTitle(R.string.sync_with_contacts); + builder.setMessage( + getString( + R.string.sync_with_contacts_long, + getString(R.string.app_name))); + } + @StringRes int confirmButtonText; + if (requiresConsent) { + confirmButtonText = R.string.agree_and_continue; + } else { + confirmButtonText = R.string.next; } + builder.setPositiveButton( + confirmButtonText, + (dialog, which) -> { + if (requiresConsent) { + PreferenceManager.getDefaultSharedPreferences( + getApplicationContext()) + .edit() + .putString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, "agreed") + .apply(); + } + if (requestPermission.compareAndSet(false, true)) { + requestPermissions(permission, REQUEST_SYNC_CONTACTS); + } + }); + if (requiresConsent) { + builder.setNegativeButton( + R.string.decline, + (dialog, which) -> + PreferenceManager.getDefaultSharedPreferences( + getApplicationContext()) + .edit() + .putString( + PREF_KEY_CONTACT_INTEGRATION_CONSENT, + "declined") + .apply()); + } else { + builder.setOnDismissListener( + dialog -> { + if (requestPermission.compareAndSet(false, true)) { + requestPermissions(permission, REQUEST_SYNC_CONTACTS); + } + }); + } + builder.setCancelable(requiresConsent); + final AlertDialog dialog = builder.create(); + dialog.setCanceledOnTouchOutside(requiresConsent); + dialog.setOnShowListener( + dialogInterface -> { + final TextView tv = dialog.findViewById(android.R.id.message); + if (tv != null) { + tv.setMovementMethod(LinkMovementMethod.getInstance()); + } + }); + dialog.show(); + } else { + requestPermissions(permission, REQUEST_SYNC_CONTACTS); } } + return true; } @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); if (grantResults.length > 0) if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { @@ -885,10 +989,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne if (actionBar == null) { return; } - boolean openConversations = !createdByViewIntent && !xmppConnectionService.isConversationsListEmpty(null); + boolean openConversations = + !createdByViewIntent && !xmppConnectionService.isConversationsListEmpty(null); actionBar.setDisplayHomeAsUpEnabled(openConversations); actionBar.setDisplayHomeAsUpEnabled(openConversations); - } @Override @@ -900,7 +1004,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne xmppConnectionService.getQuickConversationsService().considerSyncBackground(false); } if (mPostponedActivityResult != null) { - onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); + onActivityResult( + mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); this.mPostponedActivityResult = null; } this.mActivatedAccounts.clear(); @@ -924,7 +1029,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne if (QuickConversationsService.isQuicksy()) { setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing()); } - if (QuickConversationsService.isConversations() && AccountUtils.hasEnabledAccounts(xmppConnectionService) && this.contacts.size() == 0 && this.conferences.size() == 0 && mOpenedFab.compareAndSet(false, true)) { + if (QuickConversationsService.isConversations() + && AccountUtils.hasEnabledAccounts(xmppConnectionService) + && this.contacts.size() == 0 + && this.conferences.size() == 0 + && mOpenedFab.compareAndSet(false, true)) { binding.speedDial.open(); } } @@ -947,7 +1056,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne case Intent.ACTION_VIEW: Uri uri = intent.getData(); if (uri != null) { - Invite invite = new Invite(intent.getData(), intent.getBooleanExtra("scanned", false)); + Invite invite = + new Invite(intent.getData(), intent.getBooleanExtra("scanned", false)); invite.account = intent.getStringExtra(EXTRA_ACCOUNT); invite.forceDialog = intent.getBooleanExtra("force_dialog", false); return invite.invite(); @@ -959,7 +1069,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } private boolean handleJid(Invite invite) { - List contacts = xmppConnectionService.findContacts(invite.getJid(), invite.account); + List contacts = + xmppConnectionService.findContacts(invite.getJid(), invite.account); if (invite.isAction(XmppUri.ACTION_JOIN)) { Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid()); if (muc != null && !invite.forceDialog) { @@ -978,8 +1089,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne displayVerificationWarningDialog(contact, invite); } else { if (invite.hasFingerprints()) { - if (xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints())) { - Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show(); + if (xmppConnectionService.verifyFingerprints( + contact, invite.getFingerprints())) { + Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT) + .show(); } } if (invite.account != null) { @@ -1007,15 +1120,23 @@ public class StartConversationActivity extends XmppActivity implements XmppConne View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null); final CheckBox isTrustedSource = view.findViewById(R.id.trusted_source); TextView warning = view.findViewById(R.id.warning); - warning.setText(JidDialog.style(this, R.string.verifying_omemo_keys_trusted_source, contact.getJid().asBareJid().toEscapedString(), contact.getDisplayName())); + warning.setText( + JidDialog.style( + this, + R.string.verifying_omemo_keys_trusted_source, + contact.getJid().asBareJid().toEscapedString(), + contact.getDisplayName())); builder.setView(view); - builder.setPositiveButton(R.string.confirm, (dialog, which) -> { - if (isTrustedSource.isChecked() && invite.hasFingerprints()) { - xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints()); - } - switchToConversationDoNotAppend(contact, invite.getBody()); - }); - builder.setNegativeButton(R.string.cancel, (dialog, which) -> StartConversationActivity.this.finish()); + builder.setPositiveButton( + R.string.confirm, + (dialog, which) -> { + if (isTrustedSource.isChecked() && invite.hasFingerprints()) { + xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints()); + } + switchToConversationDoNotAppend(contact, invite.getBody()); + }); + builder.setNegativeButton( + R.string.cancel, (dialog, which) -> StartConversationActivity.this.finish()); AlertDialog dialog = builder.create(); dialog.setCanceledOnTouchOutside(false); dialog.setOnCancelListener(dialog1 -> StartConversationActivity.this.finish()); @@ -1036,10 +1157,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne if (account.isEnabled()) { for (Contact contact : account.getRoster().getContacts()) { Presence.Status s = contact.getShownStatus(); - if (contact.showInContactList() && contact.match(this, needle) + if (contact.showInContactList() + && contact.match(this, needle) && (!this.mHideOfflineContacts - || (needle != null && !needle.trim().isEmpty()) - || s.compareTo(Presence.Status.OFFLINE) < 0)) { + || (needle != null && !needle.trim().isEmpty()) + || s.compareTo(Presence.Status.OFFLINE) < 0)) { this.contacts.add(contact); } } @@ -1090,7 +1212,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } private void navigateBack() { - if (!createdByViewIntent && xmppConnectionService != null && !xmppConnectionService.isConversationsListEmpty(null)) { + if (!createdByViewIntent + && xmppConnectionService != null + && !xmppConnectionService.isConversationsListEmpty(null)) { Intent intent = new Intent(this, ConversationsActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); startActivity(intent); @@ -1111,13 +1235,19 @@ public class StartConversationActivity extends XmppActivity implements XmppConne intent.putExtra(ChooseContactActivity.EXTRA_SHOW_ENTER_JID, false); 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()); + intent.putExtra( + ChooseContactActivity.EXTRA_ACCOUNT, + account.getJid().asBareJid().toEscapedString()); intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants); startActivityForResult(intent, REQUEST_CREATE_CONFERENCE); } @Override - public void onJoinDialogPositiveClick(final Dialog dialog, final AutoCompleteTextView spinner, final TextInputLayout layout, final AutoCompleteTextView jid) { + public void onJoinDialogPositiveClick( + final Dialog dialog, + final AutoCompleteTextView spinner, + final TextInputLayout layout, + final AutoCompleteTextView jid) { if (!xmppConnectionServiceBound) { return; } @@ -1152,8 +1282,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne bookmark.setNick(nick); } xmppConnectionService.createBookmark(account, bookmark); - final Conversation conversation = xmppConnectionService - .findOrCreateConversation(account, conferenceJid, true, true, true); + final Conversation conversation = + xmppConnectionService.findOrCreateConversation( + account, conferenceJid, true, true, true); bookmark.setConversation(conversation); switchToConversation(conversation); } @@ -1173,7 +1304,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } } - private void setRefreshing(boolean refreshing) { MyListFragment fragment = (MyListFragment) mListPagerAdapter.getItem(0); if (fragment != null) { @@ -1185,29 +1315,32 @@ public class StartConversationActivity extends XmppActivity implements XmppConne public void onCreatePublicChannel(Account account, String name, Jid address) { mToast = Toast.makeText(this, R.string.creating_channel, Toast.LENGTH_LONG); mToast.show(); - xmppConnectionService.createPublicChannel(account, name, address, new UiCallback() { - @Override - public void success(Conversation conversation) { - runOnUiThread(() -> { - hideToast(); - switchToConversation(conversation); - }); + xmppConnectionService.createPublicChannel( + account, + name, + address, + new UiCallback() { + @Override + public void success(Conversation conversation) { + runOnUiThread( + () -> { + hideToast(); + switchToConversation(conversation); + }); + } - } + @Override + public void error(int errorCode, Conversation conversation) { + runOnUiThread( + () -> { + replaceToast(getString(errorCode)); + switchToConversation(conversation); + }); + } - @Override - public void error(int errorCode, Conversation conversation) { - runOnUiThread(() -> { - replaceToast(getString(errorCode)); - switchToConversation(conversation); + @Override + public void userInputRequired(PendingIntent pi, Conversation object) {} }); - } - - @Override - public void userInputRequired(PendingIntent pi, Conversation object) { - - } - }); } public static class MyListFragment extends SwipeRefreshListFragment { @@ -1219,7 +1352,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } @Override - public void onListItemClick(final ListView l, final View v, final int position, final long id) { + public void onListItemClick( + final ListView l, final View v, final int position, final long id) { if (mOnItemClickListener != null) { mOnItemClickListener.onItemClick(l, v, position, id); } @@ -1239,7 +1373,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } @Override - public void onCreateContextMenu(@NonNull final ContextMenu menu, @NonNull final View v, final ContextMenuInfo menuInfo) { + public void onCreateContextMenu( + @NonNull final ContextMenu menu, + @NonNull final View v, + final ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); final StartConversationActivity activity = (StartConversationActivity) getActivity(); if (activity == null) { @@ -1268,7 +1405,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne if (contact.isSelf()) { showContactDetailsItem.setVisible(false); } - deleteContactMenuItem.setVisible(contact.showInRoster() && !contact.getOption(Contact.Options.SYNCED_VIA_OTHER)); + deleteContactMenuItem.setVisible( + contact.showInRoster() + && !contact.getOption(Contact.Options.SYNCED_VIA_OTHER)); final XmppConnection xmpp = contact.getAccount().getXmppConnection(); if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) { if (contact.isBlocked()) { @@ -1327,7 +1466,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } @Override - public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { + public void destroyItem( + @NonNull ViewGroup container, int position, @NonNull Object object) { FragmentTransaction trans = fragmentManager.beginTransaction(); trans.remove(fragments[position]); trans.commit(); @@ -1343,7 +1483,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne try { trans.commit(); } catch (IllegalStateException e) { - //ignore + // ignore } return fragment; } @@ -1377,11 +1517,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne if (position == 1) { listFragment.setListAdapter(mConferenceAdapter); listFragment.setContextMenu(R.menu.conference_context); - listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForBookmark(p)); + listFragment.setOnListItemClickListener( + (arg0, arg1, p, arg3) -> openConversationForBookmark(p)); } else { listFragment.setListAdapter(mContactsAdapter); listFragment.setContextMenu(R.menu.contact_context); - listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForContact(p)); + listFragment.setOnListItemClickListener( + (arg0, arg1, p, arg3) -> openConversationForContact(p)); if (QuickConversationsService.isQuicksy()) { listFragment.setOnRefreshListener(StartConversationActivity.this); } @@ -1405,7 +1547,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne boolean forceDialog = false; - Invite(final String uri) { super(uri); } @@ -1416,7 +1557,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne boolean invite() { if (!isValidJid()) { - Toast.makeText(StartConversationActivity.this, R.string.invalid_jid, Toast.LENGTH_SHORT).show(); + Toast.makeText( + StartConversationActivity.this, + R.string.invalid_jid, + Toast.LENGTH_SHORT) + .show(); return false; } if (getJid() != null) { From 0c5ebb03e5c0963a4aa3fe7316a6966d14cebfc7 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 14 May 2024 07:34:32 +0200 Subject: [PATCH 012/192] use new syntax for instanceof --- .../eu/siacs/conversations/ui/ConversationsActivity.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index fc03846af9eaf9a5b2936d128190384805b77d2b..2c7d71d79c91cbe0dd7dc700977fc8abfcf4b0a0 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -259,15 +259,15 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio private void notifyFragmentOfBackendConnected(@IdRes int id) { final Fragment fragment = getFragmentManager().findFragmentById(id); - if (fragment instanceof OnBackendConnected) { - ((OnBackendConnected) fragment).onBackendConnected(); + if (fragment instanceof OnBackendConnected callback) { + callback.onBackendConnected(); } } private void refreshFragment(@IdRes int id) { final Fragment fragment = getFragmentManager().findFragmentById(id); - if (fragment instanceof XmppFragment) { - ((XmppFragment) fragment).refresh(); + if (fragment instanceof XmppFragment xmppFragment) { + xmppFragment.refresh(); } } From b72bf4b59da873a3e3efc8793049cc8ef5e255f3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 14 May 2024 08:04:11 +0200 Subject: [PATCH 013/192] fix regression in settings. fixes #289 --- .../ui/fragment/settings/MainSettingsFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java index 0373d715fdf9aa2aa690406b8be038abe86913a8..4ab8ade3ceb472b2762017306aacda450ab2e82c 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java @@ -16,7 +16,7 @@ public class MainSettingsFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { setPreferencesFromResource(R.xml.preferences_main, rootKey); - final var about = findPreference("about2"); + final var about = findPreference("about"); final var connection = findPreference("connection"); if (about == null || connection == null) { throw new IllegalStateException( From 3a333e0382d1dc74705bad522fc82f21a9e25dd3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 14 May 2024 08:30:09 +0200 Subject: [PATCH 014/192] do not show ongoing call in ringing phase --- .../conversations/xmpp/jingle/JingleRtpConnection.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 f682f41607f1a369e02f43570e642c315fb926dc..a8b266ae43fc22500c74857cbaa5f6a74888a5b0 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -71,6 +71,7 @@ import java.util.concurrent.TimeUnit; public class JingleRtpConnection extends AbstractJingleConnection implements WebRTCWrapper.EventCallback, CallIntegration.Callback, OngoingRtpSession { + // TODO consider adding State.SESSION_INITIALIZED to ongoing call states for direct init mode public static final List STATES_SHOWING_ONGOING_CALL = Arrays.asList( State.PROPOSED, @@ -2790,6 +2791,12 @@ public class JingleRtpConnection extends AbstractJingleConnection private void updateOngoingCallNotification() { final State state = this.state; if (STATES_SHOWING_ONGOING_CALL.contains(state)) { + if (Arrays.asList(State.PROPOSED, State.SESSION_INITIALIZED).contains(state) + && isResponder()) { + Log.d(Config.LOGTAG, "do not set ongoing call during incoming call notification"); + xmppConnectionService.removeOngoingCall(); + return; + } final boolean reconnecting; if (state == State.SESSION_ACCEPTED) { reconnecting = From fd0ff7f836bc988c30b7a7a9740c0b52050597ff Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 14 May 2024 08:30:38 +0200 Subject: [PATCH 015/192] set caller display name --- .../conversations/xmpp/jingle/JingleConnectionManager.java | 6 ++++++ .../conversations/xmpp/jingle/JingleRtpConnection.java | 3 +++ 2 files changed, 9 insertions(+) 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 ba6ccf19d0ad9635afb6e0c6b6d9647e68a12cf8..c3080d692a4d336df858f502d9c2bb6be5aa59c7 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.xmpp.jingle; +import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.util.Base64; import android.util.Log; @@ -776,6 +777,11 @@ public class JingleConnectionManager extends AbstractConnectionManager { Media.audioOnly(media) ? VideoProfile.STATE_AUDIO_ONLY : VideoProfile.STATE_BIDIRECTIONAL); + callIntegration.setAddress( + CallIntegration.address(with.asBareJid()), TelecomManager.PRESENTATION_ALLOWED); + final var contact = account.getRoster().getContact(with); + callIntegration.setCallerDisplayName( + contact.getDisplayName(), TelecomManager.PRESENTATION_ALLOWED); callIntegration.setInitialAudioDevice(CallIntegration.initialAudioDevice(media)); callIntegration.startAudioRouting(); final RtpSessionProposal proposal = 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 a8b266ae43fc22500c74857cbaa5f6a74888a5b0..9041418e5eb05b3d76305b8e81d454faa0c7a43a 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -112,6 +112,9 @@ public class JingleRtpConnection extends AbstractJingleConnection .getApplicationContext())); this.callIntegration.setAddress( CallIntegration.address(id.with.asBareJid()), TelecomManager.PRESENTATION_ALLOWED); + final var contact = id.getContact(); + this.callIntegration.setCallerDisplayName( + contact.getDisplayName(), TelecomManager.PRESENTATION_ALLOWED); this.callIntegration.setInitialized(); } From a36903d23fd90ffd2d6fac0fbe81c36857b6469a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 14 May 2024 11:28:18 +0200 Subject: [PATCH 016/192] catch IAE as thrown by termux --- .../persistance/FileBackend.java | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index eea7486ab40e369b01d70aa29fb6b1b16d662522..4fe2494f66b55544ea55f8211f1387cc12e48e39 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -36,6 +36,20 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.DownloadableFile; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.AttachFileToConversationRunnable; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.adapter.MediaAdapter; +import eu.siacs.conversations.ui.util.Attachment; +import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.FileUtils; +import eu.siacs.conversations.utils.FileWriterException; +import eu.siacs.conversations.utils.MimeUtils; +import eu.siacs.conversations.xmpp.pep.Avatar; + import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; @@ -58,20 +72,6 @@ import java.util.List; import java.util.Locale; import java.util.UUID; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.DownloadableFile; -import eu.siacs.conversations.entities.Message; -import eu.siacs.conversations.services.AttachFileToConversationRunnable; -import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.ui.adapter.MediaAdapter; -import eu.siacs.conversations.ui.util.Attachment; -import eu.siacs.conversations.utils.CryptoHelper; -import eu.siacs.conversations.utils.FileUtils; -import eu.siacs.conversations.utils.FileWriterException; -import eu.siacs.conversations.utils.MimeUtils; -import eu.siacs.conversations.xmpp.pep.Avatar; - public class FileBackend { private static final Object THUMBNAIL_LOCK = new Object(); @@ -664,13 +664,17 @@ public class FileBackend { } private void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException { - Log.d( - Config.LOGTAG, - "copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath()); - file.getParentFile().mkdirs(); + final var parentDirectory = file.getParentFile(); + if (parentDirectory != null && parentDirectory.mkdirs()) { + Log.d(Config.LOGTAG,"created directory "+parentDirectory.getAbsolutePath()); + } try { - file.createNewFile(); - } catch (IOException e) { + if (file.createNewFile()) { + Log.d( + Config.LOGTAG, + "copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath()); + } + } catch (final IOException e) { throw new FileCopyException(R.string.error_unable_to_create_temporary_file); } try (final OutputStream os = new FileOutputStream(file); @@ -681,12 +685,12 @@ public class FileBackend { } try { ByteStreams.copy(is, os); - } catch (IOException e) { + } catch (final IOException e) { throw new FileWriterException(file); } try { os.flush(); - } catch (IOException e) { + } catch (final IOException e) { throw new FileWriterException(file); } } catch (final FileNotFoundException e) { @@ -695,7 +699,7 @@ public class FileBackend { } catch (final FileWriterException e) { cleanup(file); throw new FileCopyException(R.string.error_unable_to_create_temporary_file); - } catch (final SecurityException | IllegalStateException e) { + } catch (final SecurityException | IllegalStateException | IllegalArgumentException e) { cleanup(file); throw new FileCopyException(R.string.error_security_exception); } catch (final IOException e) { @@ -706,7 +710,7 @@ public class FileBackend { public void copyFileToPrivateStorage(Message message, Uri uri, String type) throws FileCopyException { - String mime = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type); + final String mime = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type); Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime=" + mime + ")"); String extension = MimeUtils.guessExtensionFromMimeType(mime); if (extension == null) { From 4c517ca2a6eb7b4a9ef772fac33bee9225edd463 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 14 May 2024 12:36:51 +0200 Subject: [PATCH 017/192] add changelog for upcoming release --- CHANGELOG.md | 6 ++++++ fastlane/metadata/android/en-US/changelogs/4211104.txt | 3 +++ 2 files changed, 9 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/4211104.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index fedfe63c244ed38ba2a34ff84ea6d151d1474249..188087c53278529a6f292ea0f47f0bdf61fd65a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +### Version 2.16.0 + +* Schedule regular backups +* Exclude all realme devices up to Android 11 from call integration +* Minor UI (message bubble) improvements + ### Version 2.15.3 * fix call integration on some Android 14 devices diff --git a/fastlane/metadata/android/en-US/changelogs/4211104.txt b/fastlane/metadata/android/en-US/changelogs/4211104.txt new file mode 100644 index 0000000000000000000000000000000000000000..a6dc59556d58cf3573f8272847a494f8367eba93 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4211104.txt @@ -0,0 +1,3 @@ +* Schedule regular backups +* Exclude all realme devices up to Android 11 from call integration +* Minor UI (message bubble) improvements From ce99bbd8198d3ed938e9585d5eac7a1a884c3cee Mon Sep 17 00:00:00 2001 From: ffunk Date: Mon, 13 May 2024 06:53:43 +0000 Subject: [PATCH 018/192] Translated using Weblate (Czech) Currently translated at 95.5% (976 of 1021 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/cs/ --- src/main/res/values-cs/strings.xml | 129 ++++++++++++++++++++++------- 1 file changed, 100 insertions(+), 29 deletions(-) diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 74b4545a7dab5194fe0d38f0b5c8abe239ebc459..5f1953c536875f64e2cfdd09021b2c23f2c8d3e5 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -26,9 +26,9 @@ před minutou před %d minutami - %d nepřečtená konverzace - %d nepřečtené konverzace - %d nepřečtených konverzací + %d nepřečtený chat + %d nepřečtené chaty + %d nepřečtených chatů odesílám… Dešifrování zprávy. Chvíli strpení… @@ -40,7 +40,7 @@ Moderátor Účastník Návštěvník - Přejete si odstranit %s ze seznamu kontaktů? Předešlé rozhovory nebudou odstraněny. + Přejete si odstranit %s ze seznamu kontaktů? Předešlé chaty s tímto kontaktem nebudou odstraněny. Chcete zablokovat příjem zpráv od %s? Chcete odblokovat příjem zpráv od %s? Zablokovat všechny kontakty z %s? @@ -78,7 +78,7 @@ Připravuji odeslání obrázků Sdílení souborů. Chvíli strpení… Smazat historii - Smaže historii konverzací + Smazat historii chatů Opravdu chcete smazat všechny zprávy v této konverzace? \n \nVarováníToto neovlivní zprávy uložené na jiných zařízeních či serverech. @@ -174,7 +174,7 @@ Skutečně chcete odstranit Váš současný veřejný OpenPGP klíč?\nVaše kontakty Vám nebudou moci nadále posílat zprávy šifrované pomocí OpenPGP. OpenPGP veřejný klíč zveřejněn. Povolit účet - Opravdu chcete svůj účet smazat? Smazáním Vašeho účtu dojde k vymazání celé Vaší historie konverzací + Opravdu chcete svůj účet smazat? Smazáním Vašeho účtu dojde k vymazání celé Vaší chatové historie Nahrát hlas Adresa XMPP Blokovat XMPP adresu @@ -272,7 +272,7 @@ Ignorovat Varování: Odeslání bez povolení vzájemného informování o změně stavu může způsobit nečekané potíže.\n\nJděte do \"Detaily kontaktu\" a ověřte nastavení aktualizace stavu. Zabezpečení - Povolit opravu zpráv + Oprava zpráv Povolí kontaktům zpětné upravování jejich zpráv Expertní nastavení S tímto zacházejte velmi opatrně @@ -461,8 +461,8 @@ Při ztišeném vyzvánění označí váš stav jako \"nedostupný\" Vibrační mód brát stejně jako tichý Při nastavení pouze na vibrace označí váš stav jako \"nedostupný\" - Rozšířená nastavení připojení - Zobrazovat nastavení hostname a port při vytváření účtu + Jméno hostitele a port + Zobrazit rozšířené možnosti připojení při nastavování účtu xmpp.server.cz Přihlásit se pomocí certifikátu Nelze analyzovat certifikát @@ -500,10 +500,10 @@ Povolit %1$s přístup k externímu úložišti Povolit %1$s přístup ke kameře Synchronizovat s kontakty - %1$s požaduje přístup k Vašim kontaktům za účelem spárování s Vašimi XMPP kontakty. -\nU kontaktů se pak zobrazí celé jméno a avatar. + %1$s zpracovává Váš seznam kontaktů místně na Vašem zařízení za účelem zobrazení jmen a profilových obrázků odpovídajících kontaktů na XMPP. \n -\n%1$s bude kontakty pouze číst a párovat místně v zařízení, aniž by došlo k nahrání těchto dat na server. +\n +\nŽádná data kontaktů nikdy neopouštějí Vaše zařízení! Upozorňovat na všechny zprávy Upozornit pouze, když mě někdo zmíní Upozornění vypnuta @@ -523,7 +523,7 @@ Toto pole je vyžadováno Opravit zprávu Odeslat opravenou zprávu - Tento osobní otisk byl již bezpečně ověřen. Ťuknutím na \"Hotovo\" pouze potvrzujete, že %s je členem tohoto skupinového chatu. + Tento osobní otisk byl již označen jako důvěryhodný. Ťuknutím na \"Hotovo\" pouze potvrzujete, že %s je členem tohoto skupinového chatu. Tento účet byl vypnut Bezpečnostní chyba: Neplatný přístup k souboru! Nebyla nalezena aplikace umožňující sdílení URI @@ -554,8 +554,8 @@ Krátký Střední Dlouhý - Informovat o používání - Tato možnost dává vědět Vašim kontaktům, kdy používáte Conversations + Naposledy spatřen + Umožnit svým kontaktům vidět, kdy jste naposledy použili aplikaci Soukromí Vzhled Vybrat paletu barev @@ -601,7 +601,7 @@ Slepě důvěřovat před ověřením Důvěřovat novým zařízením neověřených kontaktů, ale požadovat ruční potvrzení nových zařízení u ověřených kontaktů. Nedůvěryhodný - Neplatný 2D kód + Neplatný QR kód Vyčistit složku dočasných souborů (užitých aplikací fotoaparátu) Vyčistit dočasné soubory Vyčistit soukromé úložiště @@ -698,14 +698,14 @@ Nelze získat seznam zařízení Nelze získat šifrovací klíče Tip: V některých případech může být řešení vzájemné přidání kontaktů do seznamu kontaktů. - Opravdu chcete vypnout OMEMO šifrování pro tuto konverzaci? + Opravdu chcete vypnout OMEMO šifrování pro tento chat? \nTím umožníte správci Vašeho serveru číst Vaše zprávy. Zároveň to však může být jediný způsob, jak komunikovat s kontakty, které používají zastaralé verze klientů. Vypnout hned Koncept: OMEMO šifrování OMEMO bude vždy použito k šifrování zpráv v jednotlivých konverzacích i v soukromých skupinách. - OMEMO bude použito jako výchozí pro nové konverzace. - OMEMO bude nutné zapnout ručně pro každou novou konverzaci. + OMEMO bude použito jako výchozí pro nové chaty. + OMEMO bude nutné zapnout ručně pro každý nový chat. Vytvořit zástupce Zapnuto jako výchozí Vypnuto jako výchozí @@ -724,14 +724,14 @@ Povolit %1$s přístup k mikrofonu Prohledat zprávy GIF - Zobrazit konverzaci + Zobrazit chat Plugin pro sdílení pozice Použít Plugin pro sdílení pozice namísto interní mapy Kopírovat webovou adresu Kopírovat XMPP adresu HTTP sdílení souborů pro S3 Přímé vyhledávání - Na úvodní obrazovce otevřít klávesnici a umístit kurzor do vyhledávacího pole + Na obrazovce \'Nový chat\' otevřít klávesnici a umístit kurzor do vyhledávacího pole Avatar skupinového chatu Hostitel nepodporuje avatary pro skupinový chat Pouze vlastník může změnit avatar skupinového chatu @@ -782,19 +782,19 @@ Ověřit %s %s.]]> Poslali jsme Vám další SMS se 6místným kódem. - Prosím, vložte 6místný pin. + Prosím, vložte 6místný PIN. Poslat SMS znovu Poslat SMS znovu (%s) Chvíli strpení (%s) - zpět - Automaticky vložen pravděpodobný pin ze schránky. - Prosím, vložte svůj 6místný pin. + Zpět + Automaticky vložen pravděpodobný PIN ze schránky. + Prosím, vložte svůj 6místný PIN. Opravdu si přejete přerušit registraci? Ano Ne Ověřuji… - Pin, který jste zadali, je nesprávný. - Pin, který jsme Vám poslali, vypršel. + Zadaný PIN je nesprávný. + Platnost PINu, který jsme Vám poslali, vypršela. Neznámá chyba sítě. Neznámá odpověď serveru. Nebylo možné se připojit k serveru. @@ -917,8 +917,8 @@ Odepnout shora GPX trasa Nebylo možné opravit zprávu - Všechny konverzace - Tato konverzace + Všechny chaty + Tento chat Váš avatar Avatar uživatele %s Šifrováno pomocí OMEMO @@ -1001,4 +1001,75 @@ Nebyla nalezena žádná XMPP adresa Smazat avatar Dočasné selhání autentizace + Audiokniha + Odhlášen + Používáte neověřená zařízení. Naskenujte QR kód vašich dalších zařízení, abyste provedli ověření a zabránili tak aktivním MITM útokům. + Odesílat zprávy o pádu + Vítejte v Quicksy! + Chybí povolení k telefonnímu hovoru + Kód neobsahuje otisky k tomuto chatu. + Funkce Objevování kanálů využívá službu třetí strany nazvanou <a href=https://search.jabber.network>search.jabber.network</a>.<br><br>Použitím této funkce odešlete Vaši IP adresu a hledané fráze do této služby. Pro více informací si přečtěte <a href=https://search.jabber.network/privacy>Zásady ochrany osobních údajů</a>. + Pokoušíte se naimportovat zálohu v zastaralém formátu + Push server + Odhlásit se + Žádný (deaktivováno) + Odmítnout + Odstranit účet ze serveru + Nahlásit spam a blokovat jeho odesílatele + Rozhraní + Smazat a archivovat chat + Zahájit chat + Nebyl vybrán žádný klientský certifikát! + Služba oznámení pro další aplikace kompatibilní s UnifiedPush + Oznámení + Velikost souborů, komprese obrázků, kvalita videa + Časová lhůta, zvuk, vibrace, neznámí uživatelé + Automatické stahování + Vzhled + Světlý/tmavý vzhled + Povolit snímky obrazovky + Zobrazit obsah aplikace v přepínači aplikací a povolit snímky obrazovky + Koncové šifrování + Certifikační autority + Požadovat channel binding + Důvěřovat systémovým CA certifikátům + Oznámení o psaní, naposledy spatřen, dostupnost + Zásady ochrany osobních údajů + Propojení se seznamem kontaktů není k dispozici + Quicksy žádá o souhlas s použitím Vašich dat + Přejete si odstranit záložku pro %s a archivovat chat? + Nastavit příznak \"autojoin\" při vstupu či opuštění skupiny a reagovat na změny provedené jinými klienty. + Odhlásili jste se z tohoto účtu + Znovu se připojit k jinému hostu + Žádost o SMS… + Neobnovujte zálohy, které jste sami nevytvořili! + Přepnout na chat + Hovory nejsou dostupné při použití Toru + UnifiedPush Distributor + XMPP účet + Účet, který bude použit pro přijímání zpráv push. + Skrýt oznámení + Nahlásit spam + Zabezpečení + E2E šifrování, slepá důvěra před ověřením, detekce MITM + Channel binding může detekovat některé útoky typu machine-in-the-middle + Téma, barvy, snímky obrazovky, vstup + Sdílet s… + Barevné bubliny chatu + Odlišit barvy odeslaných a přijatých zpráv + Dynamické barvy + Systémové barvy (Material You) + Přidat další stopy? + Přejete si odstranit záložku pro %s? + Nebylo možné odstranit účet ze serveru + Přihlásit se + Váš kontakt používá neověřená zařízení. Naskenujte QR kód kontaktu, abyste provedli ověření a zabránili tak aktivním MITM útokům. + Klávesnice + Archivovat chat + Nový chat + Poté smazat chat + Odeslat šifrovanou zprávu + Příslušné chaty archivovány. + Kontakt není dostupný + Chat archivován \ No newline at end of file From 4ebfbf2de30469260a7f7bdd7324c99082c2e69c Mon Sep 17 00:00:00 2001 From: ffunk Date: Mon, 13 May 2024 12:36:34 +0000 Subject: [PATCH 019/192] Translated using Weblate (Czech) Currently translated at 96.8% (989 of 1021 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/cs/ --- src/main/res/values-cs/strings.xml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 5f1953c536875f64e2cfdd09021b2c23f2c8d3e5..0aa72a93b18da19c5dbe5c92f3fa6b1ae775667e 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -1040,12 +1040,12 @@ Přejete si odstranit záložku pro %s a archivovat chat? Nastavit příznak \"autojoin\" při vstupu či opuštění skupiny a reagovat na změny provedené jinými klienty. Odhlásili jste se z tohoto účtu - Znovu se připojit k jinému hostu + Znovu připojit k jinému hostiteli Žádost o SMS… Neobnovujte zálohy, které jste sami nevytvořili! Přepnout na chat Hovory nejsou dostupné při použití Toru - UnifiedPush Distributor + Distributor UnifiedPush XMPP účet Účet, který bude použit pro přijímání zpráv push. Skrýt oznámení @@ -1072,4 +1072,22 @@ Příslušné chaty archivovány. Kontakt není dostupný Chat archivován + Neplatný uživatelský vstup + Integrace hovorů není k dispozici! + Název hostitele a port, Tor, objevování kanálů + Název hostitele a port, Tor + Vytvořit jednou, naplánovat další + Pokud je funkce distribuce UnifiedPush povolena, spolehlivé a na baterii nenáročné XMPP spojení bude využíváno k probouzení jiných aplikací kompatibilních s UnifiedPush, jako jsou např. Tusky, Ltt.rs, FluffyChat a další. + Aplikace + Interakce + Na tomto zařízení + Vytvořit jednorázovou zálohu + Opakovaná záloha + Oznámení na celou obrazovku + Nepodporovaná operace + Povolit této aplikaci zobrazit oznámení o příchozích hovorech přes celou obrazovku, když je zařízení uzamčeno. + Pozvánky od neznámých + Přijímat pozvánky do skupinových chatů od neznámých kontaktů + Velké písmo + Zvětšit písmo v chatových bublinách \ No newline at end of file From b7954704f754c434539c07750492c5a1713bbf98 Mon Sep 17 00:00:00 2001 From: jinyu Date: Mon, 13 May 2024 19:09:58 +0000 Subject: [PATCH 020/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1021 of 1021 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 8383c6c5ccdb5f657835c70001bcad6f6b9ec195..c18c0102c3727fa7bf8e20b77f71b6a8cf9e788b 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -123,7 +123,7 @@ 静默期 在其他设备上检测到活动之后,通知在此期间静音。 高级 - 通过发送堆栈跟踪,您正在帮助此应用的开发 + 通过发送堆栈跟踪,您可以帮助进一步的开发 确认消息 让您的联系人知道您已收到并阅读了其消息 防止截屏 @@ -1069,7 +1069,7 @@ 发送加密消息 大字体 增加消息气泡中的字体大小 - 来自陌生人的邀请 + 解受陌生人的邀请 接受来自陌生人的群聊邀请 创建一次性备份 定期备份 From 2db0b01d134779389f38168c1c79e9a24bdf6614 Mon Sep 17 00:00:00 2001 From: Hund Date: Mon, 13 May 2024 18:06:12 +0000 Subject: [PATCH 021/192] Translated using Weblate (Swedish) Currently translated at 100.0% (13 of 13 strings) Translation: Conversations/Android App (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-conversations/sv/ --- src/conversations/res/values-sv/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/conversations/res/values-sv/strings.xml b/src/conversations/res/values-sv/strings.xml index 062a0c26fd9e2c5779bf239e9505298d027863ec..d90427a1696cd7eb657d77132385f7960129cb65 100644 --- a/src/conversations/res/values-sv/strings.xml +++ b/src/conversations/res/values-sv/strings.xml @@ -12,8 +12,8 @@ Dela inbjudan med… Du har blivit inbjuden till %1$s. Ett användarnamn har redan valts åt dig. Vi guidar dig genom processen för att skapa ett konto. \nDu kommer att kunna kommunicera med användare av andra leverantörer genom att ge dem din fullständiga XMPP-adress. - XMPP är ett leverantörsoberoende snabbmeddelandenätverk. Du kan använda den här klienten med vilken XMPP-server du än väljer. -\nMen för din bekvämlighet har vi gjort det enkelt att skapa ett konto på conversations.im; en leverantör som är speciellt lämpad för användning med Conversations. + XMPP är ett leverantörsoberoende snabbmeddelandenätverk. Du kan använda den här appen med en valfri XMPP-server som du själv väljer. +\nMen för din bekvämlighet har vi gjort det enkelt att skapa ett konto på conversations.im; en leverantör som är speciellt lämpad för appen Conversations. Du har blivit inbjuden till %1$s. Vi guidar dig genom processen för att skapa ett konto. \nNär du väljer %1$s som leverantör kommer du att kunna kommunicera med användare av andra leverantörer genom att ge dem din fullständiga XMPP-adress. \ No newline at end of file From 637c54e3d4c2e33ffc818a09d797b161025453ac Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Tue, 14 May 2024 01:37:56 +0000 Subject: [PATCH 022/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index c18c0102c3727fa7bf8e20b77f71b6a8cf9e788b..719a40794d4d4bfe51823fe42f48b2d222fd8845 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -784,7 +784,7 @@ 电话号码 验证您的电话号码 Quicksy 将发送短信(运营商可能收费)来验证电话号码。请输入您的国家/地区代码和电话号码: - 我们将验证这个电话号码

%s

可以吗?是否编辑号码?
+ 我们将验证这个电话号码

%s

可以吗?是否编辑号码?
%s 不是有效的电话号码。 请输入您的电话号码。 搜索国家/地区 @@ -1069,7 +1069,7 @@ 发送加密消息 大字体 增加消息气泡中的字体大小 - 解受陌生人的邀请 + 来自陌生人的邀请 接受来自陌生人的群聊邀请 创建一次性备份 定期备份 @@ -1077,4 +1077,5 @@ 当设备锁定时,允许此应用显示占据全屏的来电通知。 不支持的操作 创建一次性、计划定期备份 + 允许私信 \ No newline at end of file From 79660bf57660cb1a27c79ce094616c1385a20a19 Mon Sep 17 00:00:00 2001 From: jinyu Date: Tue, 14 May 2024 01:31:48 +0000 Subject: [PATCH 023/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 719a40794d4d4bfe51823fe42f48b2d222fd8845..9b4153e4cbc317bc592b442dbd4b42c222af98f3 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -9,7 +9,7 @@ 添加账号 编辑名称 添加到通讯录 - 从联系人列表删除 + 删除联系人 屏蔽联系人 解除屏蔽联系人 屏蔽域名 From 0b263a67a37bea755a4a7c20ec532428fcdc2171 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Tue, 14 May 2024 01:52:40 +0000 Subject: [PATCH 024/192] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/uk/ --- src/main/res/values-uk/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 1304601d117b869cc756ca14ba355c55d864e838..52c661d95c55762e34b369872546923256462c95 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -1119,4 +1119,5 @@ Показувати сповіщення про вхідні виклики на весь екран, коли пристрій заблоковано. Створити резервну копію, запланувати повторюване резервування Створити резервну копію + Дозволити приватні повідомлення \ No newline at end of file From 430cd049d03b6aa6cbe096cd4b44b9d83e01ad53 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Tue, 14 May 2024 01:51:11 +0000 Subject: [PATCH 025/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 9b4153e4cbc317bc592b442dbd4b42c222af98f3..719a40794d4d4bfe51823fe42f48b2d222fd8845 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -9,7 +9,7 @@ 添加账号 编辑名称 添加到通讯录 - 删除联系人 + 从联系人列表删除 屏蔽联系人 解除屏蔽联系人 屏蔽域名 From ad107f256232f4534327f7b5840a71324a42fe87 Mon Sep 17 00:00:00 2001 From: Dirk Date: Tue, 14 May 2024 09:20:59 +0000 Subject: [PATCH 026/192] Translated using Weblate (German) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/de/ --- src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index ae2f109ed0d31b275d0c43854c11185dafe264b4..fbeb01e2e9666e9482fb791940ae575068399445 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -1071,4 +1071,5 @@ Nicht unterstützte Operation Erlaube dieser App, Benachrichtigungen über eingehende Anrufe anzuzeigen, die den gesamten Bildschirm einnehmen, wenn das Gerät gesperrt ist. Einmalige Sicherung erstellen + Private Nachrichten erlauben \ No newline at end of file From 28cad9a99dff79df3d27407ceb9b8547d9106d41 Mon Sep 17 00:00:00 2001 From: random_r Date: Tue, 14 May 2024 07:17:28 +0000 Subject: [PATCH 027/192] Translated using Weblate (Italian) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/it/ --- src/main/res/values-it/strings.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 85a223bed9504036143ef6c740b1de81b80bc0fe..25ce3502f62dd965509155bbfa72a907293bb17e 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -1024,7 +1024,7 @@ Condividi con … Archivia la chat Nuova chat - Archivia questa chat + Elimina chat in seguito Chat archiviata Unisciti a Conversation Messaggi di chat colorati @@ -1079,4 +1079,11 @@ Aumenta la dimensione dei caratteri nei messaggi Inviti da estranei Accetta inviti a chat di gruppo da estranei + Crea una volta sola, Programma ricorrenze + Backup ricorrente + Notifiche a schermo intero + Operazione non supportata + Consenti messaggi privati + Consenti a questa app di mostrare notifiche di chiamate in arrivo che occupano l\'intero schermo quando il dispositivo è bloccato. + Crea backup una volta \ No newline at end of file From d4ccc06183a100efb653ba0e44430504181ad1ee Mon Sep 17 00:00:00 2001 From: licaon-kter Date: Tue, 14 May 2024 07:15:53 +0000 Subject: [PATCH 028/192] Translated using Weblate (Romanian) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/ro/ --- src/main/res/values-ro-rRO/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index 5e7e8d7e500b107bbcfa4e3d5b6e494562d8f206..431a0e6cd43627db6e3af9c6325d26508d374498 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -1090,4 +1090,5 @@ Notificări pe tot ecranul Operațiune neacceptată Atunci când dispozitivul este blocat permite aplicației să arate notificările apelurilor pe tot ecranul. + Permite mesaje private \ No newline at end of file From 7e7989741e891ceb18ef908571410d1e3954cee2 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Tue, 14 May 2024 02:07:36 +0000 Subject: [PATCH 029/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 719a40794d4d4bfe51823fe42f48b2d222fd8845..699cfd0d58d31a3fafc20fa092417a5bd0891671 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -186,7 +186,7 @@ 录制语音 XMPP 地址 屏蔽 XMPP 地址 - username@example.com + 用户名@example.com 密码 此 XMPP 地址无效 空间不足。图片太大 @@ -234,7 +234,7 @@ 选择 此联系人已存在 加入 - channel@conference.example.com/nick + channel@conference.example.com/昵称 channel@conference.example.com 保存为书签 删除书签 From f2c8516a0cccb4baa9dfbcfad8baa17cde34193f Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Tue, 14 May 2024 11:51:53 +0000 Subject: [PATCH 030/192] Translated using Weblate (Spanish) Currently translated at 100.0% (1022 of 1022 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 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index af95feb8163f06f646b9398d346f1c96d9b71b06..d9d38ca776d093c3fa97b960056acc4c65bae160 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -1030,7 +1030,7 @@ Compartir con… Archivar chat Chat nuevo - Guardar este chat + Eliminar este chat después Chat guardado Unirse a Conversation Burbujas de chat de colores @@ -1085,4 +1085,5 @@ Operación no soportada Notificaciones a pantalla completa Permite que esta aplicación muestre notificaciones de llamadas entrantes que ocupan toda la pantalla cuando el dispositivo está bloqueado. + Permitir mensajes privados \ No newline at end of file From b14c4a15183739dc4fe22f59db728343beb68c72 Mon Sep 17 00:00:00 2001 From: ghose Date: Tue, 14 May 2024 12:03:43 +0000 Subject: [PATCH 031/192] Translated using Weblate (Galician) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/gl/ --- src/main/res/values-gl/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index 2dcdfad3a3f3b6721a941c635f506c4d8b61f1b2..a934119bd715261a43fba1101f4c2b20be683d16 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -1073,4 +1073,5 @@ Permitir que a app mostre a notificación de chamada entrante a pantalla completa cando o dispositivo está bloqueado. Crear única, Programar recurrentes Crear unha copia de apoio + Permitir mensaxes privadas \ No newline at end of file From 55f7925d5ad79cfb127b061da58f3f0182ed96f4 Mon Sep 17 00:00:00 2001 From: Besnik_b Date: Tue, 14 May 2024 11:48:55 +0000 Subject: [PATCH 032/192] Translated using Weblate (Albanian) Currently translated at 99.1% (1013 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/sq/ --- src/main/res/values-sq-rAL/strings.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/res/values-sq-rAL/strings.xml b/src/main/res/values-sq-rAL/strings.xml index 41cb53a3a435e128bd83e960535df86c2b2ad673..fbceb198bb5a3082ae942fac496eeb8226cec3a1 100644 --- a/src/main/res/values-sq-rAL/strings.xml +++ b/src/main/res/values-sq-rAL/strings.xml @@ -1031,7 +1031,7 @@ Nisni fjalosje Arkivoje fjalosjen Fjalosje e re - Arkivoje këtë fjalosje + Fshije fjalosjen më pas Kodi me vija s’përmban shenja gishtash për këtë fjalosje. Kaloni te fjalosja Fjalosje e arkivuar @@ -1081,4 +1081,6 @@ Lejojeni këtë aplikacion të shfaqë njoftime për thirrje ardhëse që zënë krejt ekranin, kur pajisja është e kyçur. Veprim i pambuluar Kopjeruajtje ripërsëritëse + Krijoni një kopjeruajtje një here të vetme + Lejoni mesazhe private \ No newline at end of file From 9ed7f9743cb2b7da9b947fa6fab0212be93bea10 Mon Sep 17 00:00:00 2001 From: ghose Date: Tue, 14 May 2024 12:06:08 +0000 Subject: [PATCH 033/192] Translated using Weblate (Galician) Currently translated at 56.9% (37 of 65 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/4211104.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/gl-ES/changelogs/4211104.txt diff --git a/fastlane/metadata/android/gl-ES/changelogs/4211104.txt b/fastlane/metadata/android/gl-ES/changelogs/4211104.txt new file mode 100644 index 0000000000000000000000000000000000000000..bb3a29cb3829cd31b5e4f9bc6f1564a5fbd20938 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/4211104.txt @@ -0,0 +1,3 @@ +* Programar copia de apoio +* Excluír a integración de chamadas en todos os dispositivos realme ata a versión Android 11 +* Pequenas melloras na interface (burbulla das mensaxes) From e45908760590a5d3477d3ca50bd829f011841984 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Tue, 14 May 2024 10:58:54 +0000 Subject: [PATCH 034/192] Translated using Weblate (Ukrainian) Currently translated at 100.0% (65 of 65 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/uk/ --- fastlane/metadata/android/uk/changelogs/4211104.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/4211104.txt diff --git a/fastlane/metadata/android/uk/changelogs/4211104.txt b/fastlane/metadata/android/uk/changelogs/4211104.txt new file mode 100644 index 0000000000000000000000000000000000000000..e0a5702fe3356ccfc949e2849786847ab3b58a26 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4211104.txt @@ -0,0 +1,3 @@ +* Планування регулярного резервного копіювання +* Виключення всіх пристроїв Realme до Android 11 з інтеграції викликів +* Незначні покращення інтерфейсу повідомлень From 845281de9959ff9cf13e82f19c463c60e0543d30 Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Tue, 14 May 2024 11:52:43 +0000 Subject: [PATCH 035/192] Translated using Weblate (Spanish) Currently translated at 100.0% (65 of 65 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/4211104.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/es-ES/changelogs/4211104.txt diff --git a/fastlane/metadata/android/es-ES/changelogs/4211104.txt b/fastlane/metadata/android/es-ES/changelogs/4211104.txt new file mode 100644 index 0000000000000000000000000000000000000000..2744732e2101a964042774ba6f3a587a5e22d84a --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/4211104.txt @@ -0,0 +1,3 @@ +* Programe copias de seguridad periódicas +* Excluir todos los dispositivos realme hasta Android 11 de la integración de llamadas +* Mejoras menores en la interfaz de usuario (burbuja de mensaje) From d6fbb650f58456aa0041c1dd89503fe61419a9c2 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Tue, 14 May 2024 10:51:25 +0000 Subject: [PATCH 036/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (65 of 65 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/4211104.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/zh-CN/changelogs/4211104.txt diff --git a/fastlane/metadata/android/zh-CN/changelogs/4211104.txt b/fastlane/metadata/android/zh-CN/changelogs/4211104.txt new file mode 100644 index 0000000000000000000000000000000000000000..badae82ccd6081a6df4297d792265e4c021d3f21 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4211104.txt @@ -0,0 +1,3 @@ +* 计划定期备份 +* 将所有安卓 11 以下的 realme 设备排除在呼叫集成之外 +* 用户界面(消息气泡)小幅改进 From 41c5287099546e811a68f6dad3f19267e075254f Mon Sep 17 00:00:00 2001 From: inputmice Date: Tue, 14 May 2024 14:45:46 +0000 Subject: [PATCH 037/192] Translated using Weblate (German) Currently translated at 100.0% (65 of 65 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/de/ --- fastlane/metadata/android/de-DE/changelogs/4211104.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/de-DE/changelogs/4211104.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/4211104.txt b/fastlane/metadata/android/de-DE/changelogs/4211104.txt new file mode 100644 index 0000000000000000000000000000000000000000..80db378170e7d475fff12f70327e5e6bbbde7e1c --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4211104.txt @@ -0,0 +1,3 @@ +* Wiederkehrende Sicherungen planen +* Realme Geräte von der Anrufintegration ausgenommen +* Kleine Designverbesserungen (Chatblasen) From a358f108bbd88d69cda9802c0fd213b0b322cb82 Mon Sep 17 00:00:00 2001 From: dqjxelg2srtivm Date: Wed, 15 May 2024 04:18:50 +0000 Subject: [PATCH 038/192] Translated using Weblate (Japanese) Currently translated at 99.1% (1013 of 1022 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 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index c815cba899e7af544852da90309c77361a1abf28..901ae1071b59ddc4a7fb4b5ac0a44a198b6f658b 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -1047,4 +1047,6 @@ 端末がロックされているとき、このアプリが全画面を占める着信通知を表示することを許可する。 UnifiedPush互換サードパーティアプリの通知中継 対応されていない動作 + 通知の種類、最終確認日時、在席状況 + 非公開メッセージを許可 \ No newline at end of file From 32e07569d738b1be79130b67d52bfa9d6704f9f4 Mon Sep 17 00:00:00 2001 From: jinyu Date: Wed, 15 May 2024 07:40:13 +0000 Subject: [PATCH 039/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 699cfd0d58d31a3fafc20fa092417a5bd0891671..4af07cea103b5b7c8916765815da314f994b9cd7 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -686,7 +686,7 @@ 消息 已禁用私信 受保护的应用 - 为了在屏幕关闭时也能收到消息提醒,您需要将 Conversations 加入受保护的应用列表。 + 为了在屏幕关闭时也可收到消息提醒,您需要将 Conversations 加入受保护的应用列表。 接受未知证书? 此服务器证书不是由已知的证书颁发机构签发的。 接受不匹配的服务器名称? From 2b9564316d06e03aa67a2de786194e45136d183c Mon Sep 17 00:00:00 2001 From: jinyu Date: Wed, 15 May 2024 08:41:44 +0000 Subject: [PATCH 040/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 4af07cea103b5b7c8916765815da314f994b9cd7..489eb50e321ee54a1c655e91761fb724bd54b8a2 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -405,7 +405,7 @@ 私人群聊配置 公开频道配置 私人,仅成员进入 - 使 XMPP 地址对任何人可见 + 公开成员的真实JID 对频道进行审核 您未参与 群聊选项修改成功! From d3b7d76383f13b5a16754a7fa2269f82f1a3d5f0 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Wed, 15 May 2024 10:49:42 +0000 Subject: [PATCH 041/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 489eb50e321ee54a1c655e91761fb724bd54b8a2..2565097b44f64941917f025172ba016e3f84dfbc 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -357,7 +357,7 @@ 未找到可以打开链接的应用 未找到可以查看联系人的应用 动态标签 - 在联系人下方显示只读标签 + 在联系人下方显示动态标签汇总 启用通知 群聊服务器未找到 无法创建群聊 @@ -405,7 +405,7 @@ 私人群聊配置 公开频道配置 私人,仅成员进入 - 公开成员的真实JID + 使 XMPP 地址对任何人可见 对频道进行审核 您未参与 群聊选项修改成功! From 522e21bc9cc5b9b8f7a07928bbd170485fe20adb Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Wed, 15 May 2024 09:40:31 +0000 Subject: [PATCH 042/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (65 of 65 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/4211104.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/zh-CN/changelogs/4211104.txt b/fastlane/metadata/android/zh-CN/changelogs/4211104.txt index badae82ccd6081a6df4297d792265e4c021d3f21..aa433554d626bbdd7be2b18dbf891cc54d43c749 100644 --- a/fastlane/metadata/android/zh-CN/changelogs/4211104.txt +++ b/fastlane/metadata/android/zh-CN/changelogs/4211104.txt @@ -1,3 +1,3 @@ * 计划定期备份 -* 将所有安卓 11 以下的 realme 设备排除在呼叫集成之外 +* 将所有 Android 11 以下的 realme 设备排除在呼叫集成之外 * 用户界面(消息气泡)小幅改进 From 3632f7f59f28f8db7d814ec78b3ae319a6a8bff3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 15 May 2024 09:49:29 +0200 Subject: [PATCH 043/192] version bump to 2.16.0 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 9f1ae2334e49c2991576558a8e860f2fd4efd1fc..a09ea4cef40cf1597156a9b75294b482388ca91f 100644 --- a/build.gradle +++ b/build.gradle @@ -100,8 +100,8 @@ android { defaultConfig { minSdkVersion 23 targetSdkVersion 34 - versionCode 42110 - versionName "2.15.3" + versionCode 42111 + versionName "2.16.0" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId From 18b03c9469eb9008f106010c5ef1b9dbebbd379b Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 15 May 2024 10:03:22 +0200 Subject: [PATCH 044/192] remove unused strings --- src/main/res/values-ar/strings.xml | 2 -- src/main/res/values-bg/strings.xml | 2 -- src/main/res/values-ca/strings.xml | 3 --- src/main/res/values-cs/strings.xml | 2 -- src/main/res/values-da-rDK/strings.xml | 2 -- src/main/res/values-de/strings.xml | 2 -- src/main/res/values-el/strings.xml | 2 -- src/main/res/values-es/strings.xml | 2 -- src/main/res/values-eu/strings.xml | 2 -- src/main/res/values-fa-rIR/strings.xml | 2 -- src/main/res/values-fi/strings.xml | 2 -- src/main/res/values-fr/strings.xml | 2 -- src/main/res/values-gl/strings.xml | 2 -- src/main/res/values-hu/strings.xml | 2 -- src/main/res/values-it/strings.xml | 2 -- src/main/res/values-ja/strings.xml | 2 -- src/main/res/values-nb-rNO/strings.xml | 2 -- src/main/res/values-nl/strings.xml | 2 -- src/main/res/values-pl/strings.xml | 2 -- src/main/res/values-pt-rBR/strings.xml | 2 -- src/main/res/values-ro-rRO/strings.xml | 2 -- src/main/res/values-ru/strings.xml | 2 -- src/main/res/values-sq-rAL/strings.xml | 2 -- src/main/res/values-sv/strings.xml | 2 -- src/main/res/values-szl/strings.xml | 2 -- src/main/res/values-tr-rTR/strings.xml | 2 -- src/main/res/values-uk/strings.xml | 2 -- src/main/res/values-vi/strings.xml | 2 -- src/main/res/values-zh-rCN/strings.xml | 2 -- src/main/res/values-zh-rTW/strings.xml | 2 -- src/main/res/values/strings.xml | 2 -- 31 files changed, 63 deletions(-) diff --git a/src/main/res/values-ar/strings.xml b/src/main/res/values-ar/strings.xml index 982d188dfb2adcf91452393f5b79bfa7a6418602..82691c3d78e06222e036ae9e3f212444969a2325 100644 --- a/src/main/res/values-ar/strings.xml +++ b/src/main/res/values-ar/strings.xml @@ -528,7 +528,6 @@ تم نسخ الرسالة إلى الحافظة رسالة الرسائل الخاصة معطلة - التطبيقات المُؤمَّنَة تقبُّل الشهادات المجهولة ؟ إنّ شهادة الخادوم غير مُوقَّعَة مِن طرف هيئة شهادات معروفة. بالرغم مِن ذلك هل تريد مواصلة الإتصال ؟ @@ -635,7 +634,6 @@ يرجى إدخال الكلمة السرية للحساب مشغول خيارات أخرى - للمواصلة في إستقبال التنبيهات، حتى والشاشة مغلقة، يجب عليك أن تضيف تطبيق Conversations إلى قائمة التطبيقات المحميّة. إنشاء الحسابات غير مدعومة مِن طرف الخادم خادمك لا يدعم نشر الصور الرمزية XEP-0215: استكشاف خدمة خارجية diff --git a/src/main/res/values-bg/strings.xml b/src/main/res/values-bg/strings.xml index 00e645282db39a073d5018de70042a090ed5abc6..6e126a864819f1218345d3e9c45744c2fd9c792d 100644 --- a/src/main/res/values-bg/strings.xml +++ b/src/main/res/values-bg/strings.xml @@ -663,8 +663,6 @@ Съобщението е копирано Съобщение Личните съобщения са изключени - Защитени приложения - Ако искате да продължите да получавате известия дори когато екранът е заключен, трябва да добавите „Conversations“ към списъка от защитени приложения. Приемане на непознатия сертификат? Сървърният сертификат не е подписан от познат център за сертификация. Приемане на несъвпадащото име на сървъра? diff --git a/src/main/res/values-ca/strings.xml b/src/main/res/values-ca/strings.xml index 3f681dffa1233f87f7073d3a02689e7b1c2f5276..ac107b9f6a8cf78240886fbce347ef2640ebb65f 100644 --- a/src/main/res/values-ca/strings.xml +++ b/src/main/res/values-ca/strings.xml @@ -647,9 +647,6 @@ Missatge copiat al portapapers Missatge Els missatges privats estan desactivats - Aplicacions protegides - Per continuar rebent notificacions, fins i tot quan la pantalla està apagada, heu -d\'afegir Converses a la llista d\'aplicacions protegides. Voleu acceptar un certificat desconegut? El certificat del servidor no està signat per una autoritat de certificació coneguda. Voleu acceptar el nom del servidor associat? diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 0aa72a93b18da19c5dbe5c92f3fa6b1ae775667e..afe234e000883972f2e17a92346295dbfc2b497f 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -679,8 +679,6 @@ Zpráva zkopírována do schránky Zpráva Soukromé zprávy jsou zakázány - Chráněné aplikace - Abyste mohli dostávat upozornění i při vypnuté obrazovce, musíte přidat Conversations mezi chráněné aplikace. Přijmout neznámý certifikát? Certifikát není podepsaný žádnou známou certifikační autoritou. Přijmout nesouhlasící jméno serveru? diff --git a/src/main/res/values-da-rDK/strings.xml b/src/main/res/values-da-rDK/strings.xml index f9cde7131d87bd1548a8808a971f17aa641e7c79..b77a551f75f1d91c6da9adb4c3bdc1d94aae06a9 100644 --- a/src/main/res/values-da-rDK/strings.xml +++ b/src/main/res/values-da-rDK/strings.xml @@ -673,8 +673,6 @@ Besked kopieret til udklipsholder Besked Private beskeder er deaktiveret - Beskyttet apps - For at modtage underretninger, selv når skærmen er slukket, skal du tilføje Conversations til listen over beskyttede apps. Accepter ukendt certifikat? Serverens certifikat er ikke underskrevet af en kendt Certifikat Autoritet. Accepter fejlbehæftet servernavn? diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index fbeb01e2e9666e9482fb791940ae575068399445..ab85cf80faa19fb97cd077030cbc45ce105a1872 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -674,8 +674,6 @@ Nachricht in die Zwischenablage kopiert Nachricht Private Nachrichten sind deaktiviert - Geschützte Apps - Um weiterhin Benachrichtigungen zu erhalten, auch wenn der Bildschirm ausgeschaltet ist, musst du Conversations zur Liste der geschützten Apps hinzufügen. Unbekanntes Zertifikat akzeptieren? Das Serverzertifikat wurde nicht von einer bekannten Zertifizierungsstelle signiert. Nicht übereinstimmenden Servernamen akzeptieren? diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml index 703fe3b99c3557b401205ca523271388d16e0cbf..8902f9c86ea1f2e4a172659d853e22082303cd72 100644 --- a/src/main/res/values-el/strings.xml +++ b/src/main/res/values-el/strings.xml @@ -664,8 +664,6 @@ Το μήνυμα αντιγράφηκε στο πρόχειρο Μήνυμα Τα ιδιωτικά μηνύματα είναι απενεργοποιημένα - Προστατευμένες εφαρμογές - Για να συνεχίσετε να λαμβάνετε ειδοποιήσεις, ακόμα κι όταν η οθόνη είναι σβηστή, χρειάζεται να προσθέσετε το Conversations στον κατάλογο με τις προστατευμένες εφαρμογές. Αποδοχή άγνωστου πιστοποιητικού; Το πιστοποιητικό του διακομιστή δεν είναι υπογεγραμμένο από κάποια γνωστή Αρχή Πιστοποίησης. Αποδοχή αναντίστοιχου ονόματος διακομιστή; diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index d9d38ca776d093c3fa97b960056acc4c65bae160..2bc1809ea691b316ccfb17cdfeb4c46e342bfc46 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -683,8 +683,6 @@ Mensaje copiado en el portapapeles Mensaje Los mensajes privados están deshabilitados - Aplicaciones protegidas - Para seguir recibiendo notificaciones, aunque la pantalla esté apagada, tienes que añadir Conversations a la lista de aplicaciones protegidas. ¿Aceptar certificado desconocido? El certificado del servidor no está firmado por una Autoridad Certificadora conocida. ¿Aceptar nombre del servidor no coincidente? diff --git a/src/main/res/values-eu/strings.xml b/src/main/res/values-eu/strings.xml index 80b4af04217d00383085c281d2ecd8b21d3d9b5d..b9d3831c780dcfd8b03d527b4b21205e2f2182ab 100644 --- a/src/main/res/values-eu/strings.xml +++ b/src/main/res/values-eu/strings.xml @@ -541,8 +541,6 @@ Mezua arbelera kopiatu da Mezua Mezu pribatuak ezgaituta daude - Babestutako aplikazioak - Jakinarazpenak jasotzen jarraitu nahi naduzu, baita pantaila itzalita dagoenean ere, Conversations babestutako aplikazioen zerrendan gehitu behar duzu. Ziurtagiri ezezaguna onartu? Zerbitzariaren ziurtagiria ez dago ezaguna den Ziurtagiri jaulkitzaile batez sinatuta. Bat ez datorren zerbitzari izena onartu? diff --git a/src/main/res/values-fa-rIR/strings.xml b/src/main/res/values-fa-rIR/strings.xml index df8f407e446f17dcdc5d4be2f5a74040e3431bf6..63727938f6d752dfa3e6fc28d6c1f4028a872b81 100644 --- a/src/main/res/values-fa-rIR/strings.xml +++ b/src/main/res/values-fa-rIR/strings.xml @@ -672,14 +672,12 @@ بارکد دوبعدی نامعتبر بگذارید همه دیگران را دعوت کنند نصب Orbot - برنامه‌های محافظت‌شده کش را خالی کن کپی اثر انگشت دیجیتال اعلان‌های شناور انتخاب حساب این گفتگوی گروهی خصوصی هیچ عضوی ندارد. بازیابی پشتیبان - برای دریافت اعلان‌ها حتی وقتی که صفحه خاموش است باید این برنامه را به فهرست برنامه‌های محافظت‌شده بیفزایید. بارکد را به کمک دوربین اسکن کنید برای تنظیم نام دکمهٔ ویرایش را به‌کار ببرید. همیشه برای گفتگوهای تکی و گروهای خصوصی OMEMO به کار برود. diff --git a/src/main/res/values-fi/strings.xml b/src/main/res/values-fi/strings.xml index c1ef9fd48bff8c6867fb75d87973366f209cc237..02ceecb1869627013fac0b525381d627959d776b 100644 --- a/src/main/res/values-fi/strings.xml +++ b/src/main/res/values-fi/strings.xml @@ -638,8 +638,6 @@ Viesti kopioitu leikepöydälle Viesti Yksityisviestit on poistettu käytöstä - Suojatut sovellukset - Saadaksesi ilmoituksia silloinkin kun näyttö on sammutettu, Conversations pitää lisätä suojattujen sovellusten luetteloon. Hyväksytäänkö tuntematon varmenne? Palvelimen varmenne ei ole luotetun myöntäjän allekirjoittama. Hyväksytäänkö eriävä palvelimen nimi? diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 1c81d4d1cae652cedd4a6ce8ae9f33f4fb4547b8..39731e001bdf799c84a292c0bc71523e653fec8d 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -676,8 +676,6 @@ Message copié dans le presse-papier Message Les messages privés sont désactivés - Applications protégées - Pour recevoir les notifications, même lorsque l\'écran est éteint, vous devez ajouter Conversations à la liste des applications protégées. Accepter les certificats inconnus ? Le certificat du serveur n\'est pas signé par une Autorité de Certification connue. Accepter un nom de serveur qui ne correspond pas ? diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index a934119bd715261a43fba1101f4c2b20be683d16..657f6c8fd9975bba3e4d87f5028067e983f175dd 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -676,8 +676,6 @@ Mensaxe copiada ao portapapeis Mensaxe As mensaxes privadas están desactivadas - Apps protexidos - Para seguir recibindo notificacións, incluso cando a pantalla está apagada, tes que engadir Conversations á lista de apps protexidas. ¿Aceptar certificado descoñecido? O certificado do servidor non está asinado por unha autoridade de certificación coñecida. ¿Aceptar un nome de servidor que non coincida? diff --git a/src/main/res/values-hu/strings.xml b/src/main/res/values-hu/strings.xml index 4e20e694733acaa40b8a1be6befdae9136d40a45..85633b909148d3a44ad74a7e1be156a897fe715a 100644 --- a/src/main/res/values-hu/strings.xml +++ b/src/main/res/values-hu/strings.xml @@ -631,8 +631,6 @@ Üzenet a vágólapra másolva Üzenet A személyes üzenetek le vannak tiltva - Védett alkalmazások - Ha akkor is szeretne értesítéseket kapni, amikor a kijelző ki van kapcsolva, hozzá kell adnia a Conversations alkalmazást a védett alkalmazások listájához. Elfogadja az ismeretlen tanúsítványt? A kiszolgáló tanúsítványa nincs aláírva egy ismert hitelesítésszolgáltató által. Elfogadja a nem egyező kiszolgálónevet? diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 25ce3502f62dd965509155bbfa72a907293bb17e..ca5c68be5af3657d883331e45339e775d75ad83e 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -683,8 +683,6 @@ Messaggio copiato negli appunti Messaggio I messaggi privati sono disattivati - App protette - Per ricevere notifiche anche quando lo schermo è spento, devi aggiungere Conversations all\'elenco delle app protette. Accetti il certificato sconosciuto? Il certificato del server non è firmato da una Certificate Authority nota. Accetti il nome del server non corrispondente? diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 901ae1071b59ddc4a7fb4b5ac0a44a198b6f658b..5a16f867338b4a9b32424b807761c90b1630ab91 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -663,8 +663,6 @@ メッセージをクリップボードにコピーしました メッセージ 非公開メッセージを無効化しました - 保護されたアプリ - 画面がオフになっているときでも通知を受信し続けるには、保護されたアプリの一覧に Conversations を追加する必要があります。 未知の証明書を受け入れますか? サーバー証明書が既知の認証局によって署名されていません。 不一致のサーバー名を受け入れますか? diff --git a/src/main/res/values-nb-rNO/strings.xml b/src/main/res/values-nb-rNO/strings.xml index c2d44f8981eeb5e7f13deef2e8602ff7feff2171..05214a1c33d851ce31b4acd9b225a9b037559489 100644 --- a/src/main/res/values-nb-rNO/strings.xml +++ b/src/main/res/values-nb-rNO/strings.xml @@ -448,8 +448,6 @@ Melding kopiert til utklippstavle Melding Private meldinger er skrudd av - Beskyttede programmer - For å motta merknader, selv når skjermen er skrudd av, må du legge til Conversations i listen over beskyttede programmer. Vis plasseringsdata Gruppesludringsnavn Opprett gruppesludring diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index f08e9e4777ac3ea66fa33515c1ed2dea0313ac14..ca3090319bdeb0fd2233a98ed0132c46d0100d36 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -578,8 +578,6 @@ Bericht gekopieerd naar klembord Bericht Privéberichten zijn uitgeschakeld - Beschermde apps - Om meldingen te blijven ontvangen, zelfs wanneer het scherm uit staat, moet je Conversations toevoegen aan de lijst met beschermde apps. Onbekend certificaat aanvaarden? Het servercertificaat is niet ondertekend door een gekende certificaatautoriteit. Verkeerde servernaam aanvaarden? diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 442f24367ee8fa67b3ba4590d63218ad465cfe43..db29fb1fe85e15177362c9f7c06ef1798b7dcca7 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -696,8 +696,6 @@ Wiadomość skopiowana do schowka Wiadomość Prywatne wiadomości są wyłączone - Aplikacje chronione - Aby otrzymywać powiadomienia nawet kiedy ekran jest wyłączony musisz dodać Conversations do listy chronionych aplikacji. Zaakceptować nieznany certyfikat? Certyfikat serwera nie jest podpisany przez znany Urząd Certyfikacji. Czy zaakceptować niepasującą nazwę serwera? diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index fa046f7607faf1649ea1d656d4349c57bfc45a13..329dfd8c80470a666ce8ffaa5217b4b9520d7be3 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -678,8 +678,6 @@ A mensagem foi copiada para a área de transferência Mensagem As mensagens privadas estão desabilitadas - Apps protegidos - Para continuar recebendo notificações, mesmo com a tela apagada, você precisa adicionar o Conversations à lista de apps protegidos. Aceitar certificado desconhecido? O servidor do certificado não está assinado por uma autoridade certificadora reconhecida. Aceitar nome de servidor não correspondente? diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index 431a0e6cd43627db6e3af9c6325d26508d374498..de6ff2e90369970f751b4237e9f1fdbef12adab3 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -688,8 +688,6 @@ Mesaj copiat în clipboard Mesaj Mesajele private sunt dezactivate - Aplicații protejate - Pentru a continua să primiți notificări, chiar și când ecranul este oprit, trebuie să adăugați Conversations în lista de aplicații protejate. Acceptați certificatul necunoscut? Certificatul serverului nu este semnat de o autoritate de certificare (CA) cunoscută. Acceptați numele serverului ce nu corespunde? diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 771346875350ce3d1463e425d889b2e57ad109ad..851d79037dd84ce21a8506108b082f2a5819d484 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -685,8 +685,6 @@ Сообщение скопировано в буфер обмена Сообщение Личные сообщения выключены - Защищенные приложения - Чтобы продолжать получать уведомления, даже если экран выключен, вам необходимо добавить Conversations в список защищенных приложений. Принять Неизвестный Сертификат? Этот сертификат сервера не подписан ни одним из известных центров сертификации. Принять несовпадающее имя сервера? diff --git a/src/main/res/values-sq-rAL/strings.xml b/src/main/res/values-sq-rAL/strings.xml index fbceb198bb5a3082ae942fac496eeb8226cec3a1..2a54f1d04ea2d6569330138d5259940a715518fc 100644 --- a/src/main/res/values-sq-rAL/strings.xml +++ b/src/main/res/values-sq-rAL/strings.xml @@ -513,7 +513,6 @@ Mesazhi u kopjua në të papastër Mesazh Mesazhet private janë të çaktivizuara - Aplikacione të Mbrojtur Të pranohet Dëshmi e Panjohur\? Dëshmia e shërbyesit s’është nënshkruar prej një Autoriteti të njohur Dëshmish. Të Pranohet Emër Shërbyesi i Ngatërruar\? @@ -785,7 +784,6 @@ Mesazh Gabimi S’u krijua dot kartelë e përkohshme Kjo pajisje u verifikua - Që të vazhdoni të merrni njoftime, edhe kur ekrani juaj është i fikur, duhet të shtoni Conversations te lista e aplikacioneve të mbrojtur. Kjo duket si adresë kanali Thirrje audio Thirrje video diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index ed055de47a99b456fd6238837c6138c77ae02ae5..ff7696c9192242d1cc81c8731c7bd6e62a18eeee 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -687,8 +687,6 @@ Meddelandet har kopierats till urklipp Meddelande Privata meddelanden är inaktiverade - Skyddade appar - För att fortsätta ta emot aviseringar, även när skärmen är avstängd, måste du lägga till Conversations i listan över skyddade appar. Acceptera okänt certifikat\? Servercertifikatet är inte signerat av en känd certifikatutfärdare. Acceptera servernamn som inte matchar? diff --git a/src/main/res/values-szl/strings.xml b/src/main/res/values-szl/strings.xml index e1de036bb53961aabb955f79e1a280c4da086cfd..32ef80fffca0b476ed33006e1b59d8c3863b30c4 100644 --- a/src/main/res/values-szl/strings.xml +++ b/src/main/res/values-szl/strings.xml @@ -699,8 +699,6 @@ Wiadōmość skopiyrowano do skrytki Wiadōmość Prywatne wiadōmości sōm zastawiōne - Aplikacyje chrōniōne - Coby dostować wiadōmości, kedy ekran je zastawiōny, musisz przidać Conversations do listy chrōniōnych aplikacyji. Zaakceptować niyznōmy certyfikat\? Certyfikat ôd serwera niy ma podpisany ôd znōmego Amtu Certyfikacyje. Zaakceptować niypasujōnce miano ôd serwera\? diff --git a/src/main/res/values-tr-rTR/strings.xml b/src/main/res/values-tr-rTR/strings.xml index 00711aaf2155dcfd37be1230da6291d88ad617de..e9b585765f3cf6c64eb6319878525ac7639d84bb 100644 --- a/src/main/res/values-tr-rTR/strings.xml +++ b/src/main/res/values-tr-rTR/strings.xml @@ -677,8 +677,6 @@ İleti panoya kopyalandı İleti Özel iletiler devre dışı bırakıldı - Korunan uygulamalar - Ekranınız kapalıyken bile bildirim almak için Conversations\'ı korunan uygulamalara eklemelisiniz. Bilinmeyen sertifikayı kabul et? Sunucu sertifikası bilinen bir Sertifika Yetkilisi tarafından imzalanmamış. Uyuşmayan Sunucu isimlerini kabul et? diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 52c661d95c55762e34b369872546923256462c95..3ddda15f3125e9360f76b8976312acde8833cb0e 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -668,8 +668,6 @@ Повідомлення скопійовано Повідомлення Приватні повідомлення вимкнено - Захищені програми - Щоб отримувати сповіщення навіть коли екран погас, необхідно додати Conversations до списку захищених програм. Прийняти незнайомий сертифікат? Сертифікат сервера не підтверджено відомим центром сертифікації. Прийняти сервер з невідповідним ім\'ям? diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml index fca05da8567270d43c82903a4a95c101dfeb72fd..72e7b808a9dec213fafa2d83dd9681aece518929 100644 --- a/src/main/res/values-vi/strings.xml +++ b/src/main/res/values-vi/strings.xml @@ -660,8 +660,6 @@ Đã chép tin nhắn vào clipboard Tin nhắn Tin nhắn riêng tư bị tắt - Ứng dụng được bảo vệ - Để tiếp tục nhận các thông báo, kể cả khi màn hình đã tắt, bạn cần thêm Conversations vào danh sách các ứng dụng được bảo vệ. Chấp nhận chứng chỉ không xác định? Chứng chỉ máy chủ này không được một người có quyền chứng chỉ đã biết ký. Chấp nhận tên máy chủ không khớp? diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 2565097b44f64941917f025172ba016e3f84dfbc..6fc573d4f1b46a276fa182633d735a70f88b4308 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -685,8 +685,6 @@ 消息已复制到剪贴板 消息 已禁用私信 - 受保护的应用 - 为了在屏幕关闭时也可收到消息提醒,您需要将 Conversations 加入受保护的应用列表。 接受未知证书? 此服务器证书不是由已知的证书颁发机构签发的。 接受不匹配的服务器名称? diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml index 58d616b20698163221c79fc40f27d2bb9edcbffb..73e533b3257046bf5086f84422001a24fcc1165e 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -620,7 +620,6 @@ 訊息已複製到剪貼簿 訊息 私密訊息已停用 - 受保護的應用程式 接受未知憑證? 伺服器憑證是由不明的憑證簽發單位所簽署。 接受不相符的伺服器名稱? @@ -918,7 +917,6 @@ 我們將驗證

%s

電話號碼是否正確,或者您想編輯這個號碼嗎?
您確定要移除此裝置的驗證嗎? \n此裝置和來自此裝置的訊息將被標示為「未受信任」。 - 若要在螢幕關閉時保持接收通知,您需要將 Conversations 加入受保護的應用程式清單。 由於「%s」,伺服器無法驗證,憑證僅對此有效: 您將要驗證您自己帳戶的 OMEMO 金鑰。僅有從可信來源 (僅有您能夠發布此連結) 跟隨此連結時才是安全的。 此通知群組用於顯示不應觸發任何音效的通知,例如在另一個裝置上啟用時 (寬限期)。 diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index ecd0e1ee6c900abda76bc09494540a89892cb3c9..658b78983578c6509142fc134a9b3da29046fe16 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -691,8 +691,6 @@ Message copied to clipboard Message Private messages are disabled - Protected Apps - To keep receiving notifications, even when the screen is turned off, you need to add Conversations to the list of protected apps. Accept Unknown Certificate? The server certificate is not signed by a known Certificate Authority. Accept Mismatching Server Name? From 185c6ea460e64a90dd349941708e2826f2984665 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 15 May 2024 10:07:07 +0200 Subject: [PATCH 045/192] bump dependencies --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index a09ea4cef40cf1597156a9b75294b482388ca91f..501284cb301947e20571f6265b773deec9b9b010 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.3.1' + classpath 'com.android.tools.build:gradle:8.3.2' } } @@ -35,7 +35,7 @@ dependencies { implementation 'androidx.viewpager:viewpager:1.0.0' - playstoreImplementation('com.google.firebase:firebase-messaging:23.4.1') { + playstoreImplementation('com.google.firebase:firebase-messaging:24.0.0') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' @@ -49,7 +49,7 @@ dependencies { implementation 'androidx.cardview:cardview:1.0.0' implementation "androidx.preference:preference:1.2.1" implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' - implementation 'com.google.android.material:material:1.11.0' + implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.work:work-runtime:2.9.0' implementation "androidx.emoji2:emoji2:1.4.0" From 1fdfbf4b7f73916ea47d216096e1d7411b088049 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 15 May 2024 20:25:37 +0200 Subject: [PATCH 046/192] disable call integration for umidigi devices --- .../eu/siacs/conversations/services/CallIntegration.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index 4b9485876deae096fa0c2fcf30cdf3652f613310..dbe22359955900167b6437317c54eae7ce7e4443 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegration.java @@ -527,6 +527,13 @@ public class CallIntegration extends Connection { && Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { return false; } + // we only know of one Umidigi device (BISON_GT2_5G) that doesn't work (audio is not being + // routed properly) However with those devices being extremely rare it's impossible to gauge + // how many might be effected and no Naomi Wu around to clarify with the company directly + if ("umidigi".equalsIgnoreCase(Build.MANUFACTURER) + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) { + return false; + } return true; } From e2be02fa0b31c149325640e720ba62a0a9a3b9bf Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Thu, 16 May 2024 03:15:28 +0000 Subject: [PATCH 047/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 2565097b44f64941917f025172ba016e3f84dfbc..f7dedd0de1d96117555e8bed9ef84e4780db718f 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -62,7 +62,7 @@ 保存 完成 %1$s 已崩溃 - 使用 XMPP 账号发送堆栈跟踪有助于 %1$s 的持续开发。 + 使用您的 XMPP 账号发送崩溃报告有助于 %1$s 的持续开发。 立即发送 不再询问 无法连接到账号 @@ -123,7 +123,7 @@ 静默期 在其他设备上检测到活动之后,通知在此期间静音。 高级 - 通过发送堆栈跟踪,您可以帮助进一步的开发 + 通过发送崩溃报告,您可以帮助此应用的进一步开发 确认消息 让您的联系人知道您已收到并阅读了其消息 防止截屏 @@ -405,7 +405,7 @@ 私人群聊配置 公开频道配置 私人,仅成员进入 - 使 XMPP 地址对任何人可见 + 公开用户的 XMPP 地址 对频道进行审核 您未参与 群聊选项修改成功! @@ -686,7 +686,7 @@ 消息 已禁用私信 受保护的应用 - 为了在屏幕关闭时也可收到消息提醒,您需要将 Conversations 加入受保护的应用列表。 + 为了在屏幕关闭后继续接收通知,您需要将 Conversations 加入受保护的应用列表。 接受未知证书? 此服务器证书不是由已知的证书颁发机构签发的。 接受不匹配的服务器名称? @@ -862,9 +862,9 @@ 管理员可以编辑话题。 所有者可以邀请别人。 任何人可以邀请别人。 - XMPP 地址对管理员可见。 - XMPP 地址对任何人可见。 - 此公开频道无参与者。邀请联系人或使用分享按钮分发其 XMPP 地址。 + 用户的 XMPP 地址仅管理员可见。 + 用户的 XMPP 地址对任何人可见。 + 此公开频道无参与者。邀请联系人或使用分享按钮分发频道的 XMPP 地址。 此私人群聊无参与者。 管理权限 搜索参与者 From 09e85a13d93dbb29105d2946afc5c04299e15100 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Thu, 16 May 2024 04:00:49 +0000 Subject: [PATCH 048/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (9 of 9 strings) Translation: Conversations/Android App (Quicksy) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-quicksy/zh_Hans/ --- src/quicksy/res/values-zh-rCN/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/quicksy/res/values-zh-rCN/strings.xml b/src/quicksy/res/values-zh-rCN/strings.xml index 839ab36ed180f5092e8398315a064f7537accd44..cf4f46cf6cec6627b21eaf4fc2e632441b40ca48 100644 --- a/src/quicksy/res/values-zh-rCN/strings.xml +++ b/src/quicksy/res/values-zh-rCN/strings.xml @@ -1,9 +1,9 @@ - 发现另一台设备上的活动后,Quicksy 在此期间保持安静 - 通过发送堆栈跟踪,您正在帮助 Quicksy 的持续开发 + 发现另一台设备上的活动后,Quicksy 在此期间保持静音 + 通过发送崩溃报告,您可以帮助 Quicksy 的持续开发 让您的所有联系人知道您最后使用 Quicksy 的时间 - 为了在屏幕关闭时也能收到消息提醒,您需要将 Quicksy 加入受保护的应用列表。 + 为了在屏幕关闭后继续接收通知,您需要将 Quicksy 加入受保护的应用列表。 Quicksy 个人资料图片 Quicksy 在您所在的国家/地区无法使用。 无法验证服务器身份。 From 790ccec2ece99cd236856e3cd0df6c903c64b791 Mon Sep 17 00:00:00 2001 From: jinyu Date: Thu, 16 May 2024 07:34:36 +0000 Subject: [PATCH 049/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index f7dedd0de1d96117555e8bed9ef84e4780db718f..c6edc69a3ea3925ef457c272ef63c482d8d3b8dd 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -388,11 +388,11 @@ 被驱逐者 成员 高级模式 - 授予成员权限 + 设为成员 撤销成员权限 - 授予管理员权限 + 设为管理员 撤销管理员权限 - 授予所有者权限 + 设为所有者 撤销所有者权限 从群聊中移除 从频道中移除 @@ -408,8 +408,8 @@ 公开用户的 XMPP 地址 对频道进行审核 您未参与 - 群聊选项修改成功! - 无法修改群聊选项 + 群聊配置修改成功! + 无法修改群聊配置 从不 直至另行通知 稍后提醒 From ecb5f18bac4be4f5a7d116e7a54afa6d98538088 Mon Sep 17 00:00:00 2001 From: dqjxelg2srtivm Date: Thu, 16 May 2024 13:11:21 +0000 Subject: [PATCH 050/192] Translated using Weblate (Japanese) Currently translated at 99.4% (1016 of 1022 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, 3 insertions(+) diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 5a16f867338b4a9b32424b807761c90b1630ab91..a419e9591ef855d5f44cdd3e3d4d3df090d810c7 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -1047,4 +1047,7 @@ 対応されていない動作 通知の種類、最終確認日時、在席状況 非公開メッセージを許可 + 繰り返し作成 + 一回限りもしくは繰り返し計画を作成 + 一回限りのバックアップを作成 \ No newline at end of file From 027cc5475ef34d1c70236eed1cc60a6dd0f82f14 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Thu, 16 May 2024 12:27:04 +0000 Subject: [PATCH 051/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1022 of 1022 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 4381c79f087fe0353f9dedb3d233417a4f8d637b..e73a297199d58e4f1539734eb36df02536815311 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -388,11 +388,11 @@ 被驱逐者 成员 高级模式 - 设为成员 + 授予成员权限 撤销成员权限 - 设为管理员 + 授予管理员权限 撤销管理员权限 - 设为所有者 + 授予所有者权限 撤销所有者权限 从群聊中移除 从频道中移除 @@ -707,7 +707,7 @@ 立即禁用 草稿: OMEMO 加密 - OMEMO 将始终用于一对一和私人群聊。 + OMEMO 将始终用于一对一聊天和私人群聊。 新聊天将默认使用 OMEMO。 新聊天必须明确开启 OMEMO。 创建快捷方式 @@ -853,13 +853,13 @@ 此频道已存在 您已加入现有的频道 无法保存频道配置 - 允许任何人编辑话题 - 允许任何人邀请别人 - 任何人可以编辑话题。 + 允许参与者(非访客)编辑话题 + 允许任何参与者邀请其他用户 + 参与者(非访客)可以编辑话题。 所有者可以编辑话题。 管理员可以编辑话题。 - 所有者可以邀请别人。 - 任何人可以邀请别人。 + 所有者可以邀请其他用户。 + 任何参与者可以邀请其他用户。 用户的 XMPP 地址仅管理员可见。 用户的 XMPP 地址对任何人可见。 此公开频道无参与者。邀请联系人或使用分享按钮分发频道的 XMPP 地址。 @@ -887,7 +887,7 @@ 无法执行此操作 加入公开频道… 共享应用未授予访问此文件的权限。 - 群聊和频道 + 群聊 jabber.network 本地服务器 大多数用户应该选择“jabber.network”以便从整个公共 XMPP 生态系统中获得更好的建议。 From 01af62648a2f99e5360542bdf8da3969d14c63d9 Mon Sep 17 00:00:00 2001 From: random_r Date: Fri, 17 May 2024 07:55:02 +0000 Subject: [PATCH 052/192] Translated using Weblate (Italian) Currently translated at 100.0% (65 of 65 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/it/ --- fastlane/metadata/android/it-IT/changelogs/4211104.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/4211104.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/4211104.txt b/fastlane/metadata/android/it-IT/changelogs/4211104.txt new file mode 100644 index 0000000000000000000000000000000000000000..9894b33eb06f59adacea076ae2cecad8859d64bc --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/4211104.txt @@ -0,0 +1,3 @@ +* Programmazione di backup regolari +* Esclusione di tutti i dispositivi realme fino ad Android 11 dall'integrazione delle chiamate +* Piccoli miglioramenti dell'interfaccia (messaggi) From 250d1d3b586975c9f934955ef242e94c64e34c85 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Fri, 17 May 2024 03:56:13 +0000 Subject: [PATCH 053/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (65 of 65 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/4210404.txt | 2 +- fastlane/metadata/android/zh-CN/changelogs/4210904.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastlane/metadata/android/zh-CN/changelogs/4210404.txt b/fastlane/metadata/android/zh-CN/changelogs/4210404.txt index 5fc634f3398044f42f98258768a4dc453611664e..ad241a2c7aa91e567f6fae13b4b73d4b19c57cf8 100644 --- a/fastlane/metadata/android/zh-CN/changelogs/4210404.txt +++ b/fastlane/metadata/android/zh-CN/changelogs/4210404.txt @@ -1,3 +1,3 @@ -* 修复安卓 8 上的音频/视频通话 +* 修复 Android 8 上的音频/视频通话 * 修复新呼叫集成中的竞态条件 * 修复视频压缩问题 diff --git a/fastlane/metadata/android/zh-CN/changelogs/4210904.txt b/fastlane/metadata/android/zh-CN/changelogs/4210904.txt index f503dd304a91ca428d35a40efd659e30e7d7eb8e..fce371955e67d9971ad8ea55a370f1876d1c377a 100644 --- a/fastlane/metadata/android/zh-CN/changelogs/4210904.txt +++ b/fastlane/metadata/android/zh-CN/changelogs/4210904.txt @@ -1,2 +1,2 @@ * 修复 Android 6/7 上的 Quicksy 注册问题 -* 在通知通道上播放来电铃声 +* 在通知渠道上播放来电铃声 From da5c5e5be6419f34a6ed2646943c4961d757b8d2 Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Sat, 18 May 2024 17:00:41 +0000 Subject: [PATCH 054/192] Translated using Weblate (Polish) Currently translated at 100.0% (1020 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/pl/ --- src/main/res/values-pl/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index db29fb1fe85e15177362c9f7c06ef1798b7dcca7..8a92894cdb861e2cc7226018ba5653f2fe985601 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -1101,4 +1101,5 @@ Pełnoekranowe powiadomienia Nieobsługiwana operacja Pozwól tej aplikacji na pokazywanie powiadomień o przychodzącym połączeniu, które zajmują cały ekran gdy urządzenie jest zablokowane. + Pozwól na prywatne wiadomości \ No newline at end of file From 1f0bc1c637da7a088eb47902f067c642d0b8cbf6 Mon Sep 17 00:00:00 2001 From: 0ko <0ko@users.noreply.translate.codeberg.org> Date: Sun, 19 May 2024 07:32:49 +0000 Subject: [PATCH 055/192] Translated using Weblate (Russian) Currently translated at 96.3% (983 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/ru/ --- src/main/res/values-ru/strings.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 851d79037dd84ce21a8506108b082f2a5819d484..e3808038011e3442c4ed578da1a8267864a592ac 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -1055,7 +1055,7 @@ Обзор каналов использует сторонний сервис <a href=https://search.jabber.network>search.jabber.network</a>.<br><br>Эта функция передаст Ваш IP-адрес и ваш поисковый запрос этому сервису. Ознакомьтесь с его <a href=https://search.jabber.network/privacy>Политикой конфиденциальности</a> для получения подробностей. Архивировать беседу Новая беседа - Архивировать эту беседу + Затем удалить беседу Беседа архивирована Присоединиться к общению Штрих-код не содержит отпечатков для этой беседы. @@ -1084,4 +1084,8 @@ Соответствующие беседы архивированы. Доверять сертификатам системных УЦ Имя хоста и порт, Тор, обзор каналов + Разрешить личные сообщения + Принимать приглашения в групповые беседы от незнакомцев + Операция не поддерживается + Полноэкранные уведомления \ No newline at end of file From 75c997275bc066011bb16326535f1187800cb895 Mon Sep 17 00:00:00 2001 From: ghose Date: Sun, 19 May 2024 05:12:04 +0000 Subject: [PATCH 056/192] Translated using Weblate (Galician) Currently translated at 100.0% (9 of 9 strings) Translation: Conversations/Android App (Quicksy) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-quicksy/gl/ --- src/quicksy/res/values-gl/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quicksy/res/values-gl/strings.xml b/src/quicksy/res/values-gl/strings.xml index 0f1343d66ebce6390612b5a6aa5dfe003f8ae508..57bf791696730f36bb4b01d291538227164a96e1 100644 --- a/src/quicksy/res/values-gl/strings.xml +++ b/src/quicksy/res/values-gl/strings.xml @@ -4,7 +4,7 @@ Enviando trazas do rexistro estás axudando ao desenvolvemento de Quicksy Permitir a todos os teus contactos saber cando estás a utilizar Quicksy Para seguir recibindo notificacións, mesmo coa pantalla apagada, tes que engadir a Quicksy á lista de apps protexidas. - Imaxe de perfil Quicksy + Imaxe de perfil en Quicksy Quicksy non está dispoñible no teu país. Non se puido verificar a identidade do servidor. Fallo de seguridade descoñecido. From d6050c4434799585640572a2ee25e7aa1743faec Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Sat, 18 May 2024 17:02:49 +0000 Subject: [PATCH 057/192] Translated using Weblate (Polish) Currently translated at 13.8% (9 of 65 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/4211104.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/pl-PL/changelogs/4211104.txt diff --git a/fastlane/metadata/android/pl-PL/changelogs/4211104.txt b/fastlane/metadata/android/pl-PL/changelogs/4211104.txt new file mode 100644 index 0000000000000000000000000000000000000000..fba143dc9faf7b26fda6e2ffad954125bde98e9b --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4211104.txt @@ -0,0 +1,3 @@ +* Planowanie regularnej kopii zapasowej +* Wyłączenie wszystkich urządzeń Realme do Androida 11 z integracji rozmów +* Drobne poprawki interfejsu użytkownika (dymków wiadomości) From 60443f52b5018a418294d478db54e741cae24c65 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 May 2024 10:00:27 +0200 Subject: [PATCH 058/192] disable mute via callintegration fixes #299 --- .../conversations/xmpp/jingle/JingleRtpConnection.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 9041418e5eb05b3d76305b8e81d454faa0c7a43a..26e62373c4f3df402363b8b35e91a4dbdc656331 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -2328,7 +2328,8 @@ public class JingleRtpConnection extends AbstractJingleConnection this.jingleConnectionManager.ensureConnectionIsRegistered(this); this.webRTCWrapper.setup(this.xmppConnectionService); this.webRTCWrapper.initializePeerConnection(media, iceServers, trickle); - this.webRTCWrapper.setMicrophoneEnabledOrThrow(callIntegration.isMicrophoneEnabled()); + // this.webRTCWrapper.setMicrophoneEnabledOrThrow(callIntegration.isMicrophoneEnabled()); + this.webRTCWrapper.setMicrophoneEnabledOrThrow(true); } private void acceptCallFromProposed() { @@ -2770,7 +2771,12 @@ public class JingleRtpConnection extends AbstractJingleConnection @Override public void onCallIntegrationMicrophoneEnabled(final boolean enabled) { - this.webRTCWrapper.setMicrophoneEnabled(enabled); + // this is called every time we switch audio devices. Thus it would re-enable a microphone + // that was previous disabled by the user. A proper implementation would probably be to + // track user choice and enable the microphone with a userEnabled() && + // callIntegration.isMicrophoneEnabled() condition + Log.d(Config.LOGTAG, "ignoring onCallIntegrationMicrophoneEnabled(" + enabled + ")"); + // this.webRTCWrapper.setMicrophoneEnabled(enabled); } @Override From 1ee5f47bbc39b5734d1f354c7cef4f655b27ff80 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 May 2024 10:30:02 +0200 Subject: [PATCH 059/192] catch invalid DNSName (newly introduced in minidns) --- src/main/java/de/gultsch/minidns/NetworkDataSource.java | 3 ++- src/main/java/eu/siacs/conversations/utils/Resolver.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/gultsch/minidns/NetworkDataSource.java b/src/main/java/de/gultsch/minidns/NetworkDataSource.java index 0714594feaaa2c1afa4f8d757be8863c30a3d39e..ee8266780d7895cf5eca20e2859c70fc165ca473 100644 --- a/src/main/java/de/gultsch/minidns/NetworkDataSource.java +++ b/src/main/java/de/gultsch/minidns/NetworkDataSource.java @@ -15,6 +15,7 @@ import eu.siacs.conversations.Config; import org.minidns.MiniDnsException; import org.minidns.dnsmessage.DnsMessage; +import org.minidns.dnsname.InvalidDnsNameException; import org.minidns.dnsqueryresult.DnsQueryResult; import org.minidns.dnsqueryresult.StandardDnsQueryResult; import org.minidns.util.MultipleIoException; @@ -172,7 +173,7 @@ public class NetworkDataSource extends org.minidns.source.NetworkDataSource { public static DnsMessage readDNSMessage(final byte[] bytes) throws IOException { try { return new DnsMessage(bytes); - } catch (final IllegalArgumentException e) { + } catch (final InvalidDnsNameException | IllegalArgumentException e) { throw new IOException(Throwables.getRootCause(e)); } } diff --git a/src/main/java/eu/siacs/conversations/utils/Resolver.java b/src/main/java/eu/siacs/conversations/utils/Resolver.java index 99be0592448eee99845bc05ffb58ca066e4bb997..9d089ad05de24f8a7ca22ab73722321380a53947 100644 --- a/src/main/java/eu/siacs/conversations/utils/Resolver.java +++ b/src/main/java/eu/siacs/conversations/utils/Resolver.java @@ -27,6 +27,7 @@ import eu.siacs.conversations.xmpp.Jid; import org.minidns.dnsmessage.Question; import org.minidns.dnsname.DnsName; +import org.minidns.dnsname.InvalidDnsNameException; import org.minidns.dnsqueryresult.DnsQueryResult; import org.minidns.record.A; import org.minidns.record.AAAA; @@ -97,7 +98,7 @@ public class Resolver { try { DnsName.from(hostname); return false; - } catch (IllegalArgumentException e) { + } catch (final InvalidDnsNameException | IllegalArgumentException e) { return true; } } From 71b0d4eb6f92919f39c51c13e5a8b3cf1136f452 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 May 2024 12:48:04 +0200 Subject: [PATCH 060/192] version bump to 2.16.1 + changelog --- CHANGELOG.md | 5 +++++ build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/4211204.txt | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/4211204.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 188087c53278529a6f292ea0f47f0bdf61fd65a0..ca61483edaeaa765b397b0ba9e54c523b9bebc35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Version 2.16.1 + +* Fix call getting un-muted when switching output devices +* Exclude all Umidigi devices from call integration + ### Version 2.16.0 * Schedule regular backups diff --git a/build.gradle b/build.gradle index 501284cb301947e20571f6265b773deec9b9b010..918c6004358c72ae78b01003aa9b8746633a5ffe 100644 --- a/build.gradle +++ b/build.gradle @@ -100,8 +100,8 @@ android { defaultConfig { minSdkVersion 23 targetSdkVersion 34 - versionCode 42111 - versionName "2.16.0" + versionCode 42112 + versionName "2.16.1" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/4211204.txt b/fastlane/metadata/android/en-US/changelogs/4211204.txt new file mode 100644 index 0000000000000000000000000000000000000000..0e469b1efe1dbbb815eecc1fde8c262e220c3dc4 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* Fix call getting un-muted when switching output devices +* Exclude all Umidigi devices from call integration From 87fe20d1c5fbb61c05c91f992fa46301a40214f1 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 May 2024 19:20:11 +0200 Subject: [PATCH 061/192] explicitly run backup in foreground service --- src/main/AndroidManifest.xml | 4 --- .../receiver/WorkManagerEventReceiver.java | 32 ------------------- .../worker/ExportBackupWorker.java | 23 ++++++------- 3 files changed, 10 insertions(+), 49 deletions(-) delete mode 100644 src/main/java/eu/siacs/conversations/receiver/WorkManagerEventReceiver.java diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 749b593c607c4b4cdaa8f0e8e2cf4f9b9d2c4273..382f0ebd9a9ab39c92fa7a58bbaa16e20bb2873d 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -142,10 +142,6 @@ - - diff --git a/src/main/java/eu/siacs/conversations/receiver/WorkManagerEventReceiver.java b/src/main/java/eu/siacs/conversations/receiver/WorkManagerEventReceiver.java deleted file mode 100644 index 71ec74f53bf121ab981a37d8d4ae07a14245c19d..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/receiver/WorkManagerEventReceiver.java +++ /dev/null @@ -1,32 +0,0 @@ -package eu.siacs.conversations.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -import androidx.work.WorkManager; - -import com.google.common.base.Strings; - -import eu.siacs.conversations.Config; -import eu.siacs.conversations.ui.fragment.settings.BackupSettingsFragment; - -public class WorkManagerEventReceiver extends BroadcastReceiver { - - public static final String ACTION_STOP_BACKUP = "eu.siacs.conversations.receiver.STOP_BACKUP"; - - @Override - public void onReceive(final Context context, final Intent intent) { - final var action = Strings.nullToEmpty(intent == null ? null : intent.getAction()); - if (action.equals(ACTION_STOP_BACKUP)) { - stopBackup(context); - } - } - - private void stopBackup(final Context context) { - Log.d(Config.LOGTAG, "trying to stop one-off backup worker"); - final var workManager = WorkManager.getInstance(context); - workManager.cancelUniqueWork(BackupSettingsFragment.CREATE_ONE_OFF_BACKUP); - } -} diff --git a/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java b/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java index 75ef9036c2fbf32bde15ca77f2cc9707813cc88e..02fc1632689a8c7be38b80e76b34d40e0a5d4b79 100644 --- a/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java +++ b/src/main/java/eu/siacs/conversations/worker/ExportBackupWorker.java @@ -17,6 +17,7 @@ import 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; @@ -33,7 +34,6 @@ import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.FileBackend; -import eu.siacs.conversations.receiver.WorkManagerEventReceiver; import eu.siacs.conversations.utils.BackupFileHeader; import eu.siacs.conversations.utils.Compatibility; @@ -95,6 +95,7 @@ public class ExportBackupWorker extends Worker { @NonNull @Override public Result doWork() { + setForegroundAsync(getForegroundInfo()); final List files; try { files = export(); @@ -223,18 +224,14 @@ public class ExportBackupWorker extends Worker { IV, salt); final var notification = getNotification(); - if (!recurringBackup) { - final var cancel = new Intent(context, WorkManagerEventReceiver.class); - cancel.setAction(WorkManagerEventReceiver.ACTION_STOP_BACKUP); - final var cancelPendingIntent = - PendingIntent.getBroadcast(context, 197, cancel, PENDING_INTENT_FLAGS); - notification.addAction( - new NotificationCompat.Action.Builder( - R.drawable.ic_cancel_24dp, - context.getString(R.string.cancel), - cancelPendingIntent) - .build()); - } + final var cancelPendingIntent = + WorkManager.getInstance(context).createCancelPendingIntent(getId()); + notification.addAction( + new NotificationCompat.Action.Builder( + R.drawable.ic_cancel_24dp, + context.getString(R.string.cancel), + cancelPendingIntent) + .build()); final Progress progress = new Progress(notification, max, count); final File directory = file.getParentFile(); if (directory != null && directory.mkdirs()) { From f1d5436ef22c3603f5dda63370288b744a8874f5 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 22 May 2024 09:10:24 +0200 Subject: [PATCH 062/192] null check intent extras --- .../siacs/conversations/ui/RtpSessionActivity.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 7a2bb09385d5a036da2a1182d6fc3bd4c54c7aaa..1bb11c97391ece66e24a5d2bfc5683ba94dc8d34 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -34,6 +34,7 @@ import androidx.databinding.DataBindingUtil; import com.google.common.base.Optional; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -487,7 +488,12 @@ public class RtpSessionActivity extends XmppActivity final String action = intent.getAction(); Log.d(Config.LOGTAG, "initializeWithIntent(" + event + "," + action + ")"); final Account account = extractAccount(intent); - final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH)); + final var extraWith = intent.getStringExtra(EXTRA_WITH); + final Jid with = Strings.isNullOrEmpty(extraWith) ? null : Jid.ofEscaped(extraWith); + if (with == null || account == null) { + Log.e(Config.LOGTAG, "intent is missing extras (account or with)"); + return; + } final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID); if (sessionId != null) { if (initializeActivityWithRunningRtpSession(account, with, sessionId)) { @@ -1038,8 +1044,7 @@ public class RtpSessionActivity extends XmppActivity final CallIntegration.AudioDevice selectedAudioDevice, final int numberOfChoices) { switch (selectedAudioDevice) { case EARPIECE -> { - this.binding.inCallActionRight.setImageResource( - R.drawable.ic_volume_off_24dp); + this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_off_24dp); if (numberOfChoices >= 2) { this.binding.inCallActionRight.setOnClickListener(this::switchToSpeaker); } else { @@ -1062,8 +1067,7 @@ public class RtpSessionActivity extends XmppActivity } } case BLUETOOTH -> { - this.binding.inCallActionRight.setImageResource( - R.drawable.ic_bluetooth_audio_24dp); + this.binding.inCallActionRight.setImageResource(R.drawable.ic_bluetooth_audio_24dp); this.binding.inCallActionRight.setOnClickListener(null); this.binding.inCallActionRight.setClickable(false); } From a42f6874627d97610686f4c6d5d223a58c6f832a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 22 May 2024 14:57:54 +0200 Subject: [PATCH 063/192] remove unnecessary log. fixes #300 --- src/main/java/eu/siacs/conversations/parser/MessageParser.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 6accc1f12a01ff880d2b6e86191e6c7856930ddb..011942c5037e6f2b1e6cafcca2c9bd70b904f189 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -668,7 +668,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam final boolean duplicate = conversation.hasDuplicateMessage(message); if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches) && !duplicate) { - Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'"); synchronized (replacedMessage) { final String uuid = replacedMessage.getUuid(); replacedMessage.setUuid(UUID.randomUUID().toString()); From 89fbd59d9cbae15e8300809ec6af2a0e1032bdff Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 22 May 2024 14:58:37 +0200 Subject: [PATCH 064/192] version bump to 2.16.2 + changelog --- CHANGELOG.md | 4 ++++ build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/4211304.txt | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/4211304.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index ca61483edaeaa765b397b0ba9e54c523b9bebc35..cdcc3b23fd44d3d34f5c226947745f01b75d8bb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### Version 2.16.2 + +* Run Backup as foreground service to prevent process being stopped after 10 minutes + ### Version 2.16.1 * Fix call getting un-muted when switching output devices diff --git a/build.gradle b/build.gradle index 918c6004358c72ae78b01003aa9b8746633a5ffe..beb73f787e4ed82d9b51b84522faafd60ac8d266 100644 --- a/build.gradle +++ b/build.gradle @@ -100,8 +100,8 @@ android { defaultConfig { minSdkVersion 23 targetSdkVersion 34 - versionCode 42112 - versionName "2.16.1" + versionCode 42113 + versionName "2.16.2" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/4211304.txt b/fastlane/metadata/android/en-US/changelogs/4211304.txt new file mode 100644 index 0000000000000000000000000000000000000000..4e9637aa144a7685c75666c2c585dc2c51c43051 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4211304.txt @@ -0,0 +1 @@ +* Run Backup as foreground service to prevent process being stopped after 10 minutes From ee641872273ad2bd8697b6e2ba9fd5e4cd1b2974 Mon Sep 17 00:00:00 2001 From: kolAflash Date: Wed, 22 May 2024 17:42:12 +0200 Subject: [PATCH 065/192] Documentation for the meaning of the XEP-0333 check markers. A discussion at xmpp:conversations@conference.siacs.eu yielded, that previous to this there was no documentation or in-app information about the meaning of the check marks. --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b006810bdd53f42ba997fa7f149c11b29cdd2727..b03efffbff0793fce7e0bd3d6e2052e90808e7cb 100644 --- a/README.md +++ b/README.md @@ -242,13 +242,17 @@ everybody in your contact list to know that you have been using your computer at In the past status has been used to judge the likelihood of whether or not your messages are being read. This is no longer necessary. With Chat Markers (XEP-0333, supported by Conversations since 0.4) we have the ability to **know** -whether or not your messages are being read. Similar things can be said for -priorities. In the past priorities have been used (by servers, not by clients!) -to route your messages to one specific client. With carbon messages (XEP-0280, -supported by Conversations since 0.1) this is no longer necessary. Using -priorities to route OTR messages isn't practical either because they are not -changeable on the fly. Metrics like last active client (the client which sent -the last message) are much better. +whether or not your messages are being read. +* one check mark ✓: message has been send (arrived at server) +* two check marks ✓✓: message has arrived at receiver +* text _"has read up to this point"_: receiver has read the message (receiver might has read notifications turned off) + +Similar things can be said for priorities. In the past priorities have been used +(by servers, not by clients!) to route your messages to one specific client. +With carbon messages (XEP-0280, supported by Conversations since 0.1) this is no +longer necessary. Using priorities to route OTR messages isn't practical either +because they are not changeable on the fly. Metrics like last active client (the +client which sent the last message) are much better. Unfortunately these modern replacements for legacy XMPP features are not widely adopted. However Conversations should be an instant messenger for the future and From 67326817e6eabe1b04611730a212ecb9d58e810b Mon Sep 17 00:00:00 2001 From: iNPUTmice Date: Thu, 23 May 2024 07:27:21 +0000 Subject: [PATCH 066/192] no reason to mention OTR in readme --- README.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/README.md b/README.md index b03efffbff0793fce7e0bd3d6e2052e90808e7cb..d786660bf22def23ec1baff6573eef36405d7896 100644 --- a/README.md +++ b/README.md @@ -250,19 +250,7 @@ whether or not your messages are being read. Similar things can be said for priorities. In the past priorities have been used (by servers, not by clients!) to route your messages to one specific client. With carbon messages (XEP-0280, supported by Conversations since 0.1) this is no -longer necessary. Using priorities to route OTR messages isn't practical either -because they are not changeable on the fly. Metrics like last active client (the -client which sent the last message) are much better. - -Unfortunately these modern replacements for legacy XMPP features are not widely -adopted. However Conversations should be an instant messenger for the future and -instead of making Conversations compatible with the past we should work on -implementing new, improved technologies and getting them into other XMPP clients -as well. - -Making these status and priority optional isn't a solution either because -Conversations is trying to get rid of old behaviours and set an example for -other clients. +longer necessary. #### Translations Translations are managed on [Weblate](https://translate.codeberg.org/projects/conversations/). From 40cc114d9905b6834cd50aa1340826b5b669b3ff Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Thu, 23 May 2024 07:03:52 +0000 Subject: [PATCH 067/192] Translated using Weblate (Polish) Currently translated at 15.1% (10 of 66 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/pl/ --- fastlane/metadata/android/pl-PL/changelogs/4211204.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/pl-PL/changelogs/4211204.txt diff --git a/fastlane/metadata/android/pl-PL/changelogs/4211204.txt b/fastlane/metadata/android/pl-PL/changelogs/4211204.txt new file mode 100644 index 0000000000000000000000000000000000000000..da160403c511b45e100df8eafd45d89258bd7c65 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* Naprawienie wyłączania wyciszenia rozmowy przy przełączaniu urządzeń wyjściowych +* Wyłączenie wszystkich urządzeń Umidigi z integracji rozmów From 6be393d74143cd33c7c13e449f28725141f4e1d9 Mon Sep 17 00:00:00 2001 From: Besnik_b Date: Thu, 23 May 2024 08:53:27 +0000 Subject: [PATCH 068/192] Translated using Weblate (Albanian) Currently translated at 100.0% (66 of 66 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/sq/ --- fastlane/metadata/android/sq/changelogs/4211104.txt | 3 +++ fastlane/metadata/android/sq/changelogs/4211204.txt | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 fastlane/metadata/android/sq/changelogs/4211104.txt create mode 100644 fastlane/metadata/android/sq/changelogs/4211204.txt diff --git a/fastlane/metadata/android/sq/changelogs/4211104.txt b/fastlane/metadata/android/sq/changelogs/4211104.txt new file mode 100644 index 0000000000000000000000000000000000000000..ac42b4e67e39f40206122c81ba5be1189a751c8b --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/4211104.txt @@ -0,0 +1,3 @@ +* Planifikim kopjeruajtjesh periodike +* Përjashtim i krejt pajisjeve “realme”, deri te Android 11, nga integrim thirrjesh +* Përmirësime të vockla UI (flluskë mesazhi) diff --git a/fastlane/metadata/android/sq/changelogs/4211204.txt b/fastlane/metadata/android/sq/changelogs/4211204.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b20cd394ca02858dc1b1b62c818194a1a1a23ce --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* Ndreqje çheshtimi thirrjeje, kur ndërrohet pajisje dëgjimi +* Përjashtim i krejt pajisjeve Umidigi nga integrim thirrjesh From b77f052a1c1530227b12453bfbbe133ad8dcc925 Mon Sep 17 00:00:00 2001 From: ghose Date: Thu, 23 May 2024 03:47:03 +0000 Subject: [PATCH 069/192] Translated using Weblate (Galician) Currently translated at 57.5% (38 of 66 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/gl/ --- fastlane/metadata/android/gl-ES/changelogs/4211204.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/gl-ES/changelogs/4211204.txt diff --git a/fastlane/metadata/android/gl-ES/changelogs/4211204.txt b/fastlane/metadata/android/gl-ES/changelogs/4211204.txt new file mode 100644 index 0000000000000000000000000000000000000000..332791c8cb077494fea480b41f03bff6854570fc --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* Arranxo da chamada que deixa de estar silenciada ao cambiar o dispositivo de saída +* Exclusión da integración de chamadas para todos os dispositivos Umidigi From 4b15ff9528406bec5926a2514c7ba28c6a7d6782 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Thu, 23 May 2024 08:50:28 +0000 Subject: [PATCH 070/192] Translated using Weblate (Ukrainian) Currently translated at 100.0% (66 of 66 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/uk/ --- fastlane/metadata/android/uk/changelogs/4211204.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/4211204.txt diff --git a/fastlane/metadata/android/uk/changelogs/4211204.txt b/fastlane/metadata/android/uk/changelogs/4211204.txt new file mode 100644 index 0000000000000000000000000000000000000000..76b030cf621b1f737b38f9784772faafa77718b9 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* Виправлено ввімкнення звуку виклику при перемиканні пристроїв виводу +* Виключення всіх пристроїв Umidigi з інтеграції викликів From a5e5bcef2c2095848cb6c1b0da3148d4770cdadc Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Wed, 22 May 2024 13:09:34 +0000 Subject: [PATCH 071/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (66 of 66 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/zh_Hans/ --- fastlane/metadata/android/zh-CN/changelogs/42037.txt | 6 +++--- fastlane/metadata/android/zh-CN/changelogs/4211104.txt | 2 +- fastlane/metadata/android/zh-CN/changelogs/4211204.txt | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 fastlane/metadata/android/zh-CN/changelogs/4211204.txt diff --git a/fastlane/metadata/android/zh-CN/changelogs/42037.txt b/fastlane/metadata/android/zh-CN/changelogs/42037.txt index bde3749452d5ba4ebbb8aa9011c67e35c837a54c..9af497aabebf2b274990cbaecf198bc15434c263 100644 --- a/fastlane/metadata/android/zh-CN/changelogs/42037.txt +++ b/fastlane/metadata/android/zh-CN/changelogs/42037.txt @@ -1,11 +1,11 @@ -版本2.10.9 +版本 2.10.9 * 进行音视频通话时请求蓝牙权限(如果您不使用蓝牙耳机可以拒绝) * 修复呼叫 Movim 时的错误 -* 修复群组聊天的显示错误头像的问题 +* 修复群聊显示错误头像的问题 * 始终要求选择退出电池优化 * 在“x 个已连接账号”通知上设置仅本地标志 * 修复与 Google 地图分享位置插件的交互 * 移除有关服务器费用的脚注 * 将文件存储在适合 Android 11 的位置 * 网络切换后尝试重新连接通话 -* 在来电屏幕中显示来电者JID和帐户JID +* 在来电屏幕中显示来电者 JID 和账号JID diff --git a/fastlane/metadata/android/zh-CN/changelogs/4211104.txt b/fastlane/metadata/android/zh-CN/changelogs/4211104.txt index aa433554d626bbdd7be2b18dbf891cc54d43c749..6ae5e9f8ee89ddee3862a01eab0687da972dc1a1 100644 --- a/fastlane/metadata/android/zh-CN/changelogs/4211104.txt +++ b/fastlane/metadata/android/zh-CN/changelogs/4211104.txt @@ -1,3 +1,3 @@ * 计划定期备份 -* 将所有 Android 11 以下的 realme 设备排除在呼叫集成之外 +* 从呼叫集成中排除所有 Android 11 以下的 realme 设备 * 用户界面(消息气泡)小幅改进 diff --git a/fastlane/metadata/android/zh-CN/changelogs/4211204.txt b/fastlane/metadata/android/zh-CN/changelogs/4211204.txt new file mode 100644 index 0000000000000000000000000000000000000000..e3a4d283970b03a2a46af286276a9e303b8f162d --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* 修复切换输出设备时呼叫未静音的问题 +* 从呼叫集成中排除所有 Umidigi 设备 From cb305c336b0da7247929482120c8582c4d164637 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Thu, 23 May 2024 13:15:01 +0000 Subject: [PATCH 072/192] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1020 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/uk/ --- src/main/res/values-uk/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 3ddda15f3125e9360f76b8976312acde8833cb0e..de997fcb0f6c7dac85a4642b4a7054bfb28fd2cd 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -313,7 +313,7 @@ Деталі облікового запису Підтвердити Спробуйте ще - Фонова служба + Процес на передньому плані Не дає операційній системі припиняти Ваш зв\'язок Створити резервну копію Резервні копії зберігатимуться до %s From c1a01a2f04e0027829b21baf4f4ad1df05748f3e Mon Sep 17 00:00:00 2001 From: SomeTr Date: Thu, 23 May 2024 13:18:21 +0000 Subject: [PATCH 073/192] Translated using Weblate (Ukrainian) Currently translated at 100.0% (67 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/uk/ --- fastlane/metadata/android/uk/changelogs/4211304.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/uk/changelogs/4211304.txt diff --git a/fastlane/metadata/android/uk/changelogs/4211304.txt b/fastlane/metadata/android/uk/changelogs/4211304.txt new file mode 100644 index 0000000000000000000000000000000000000000..e50a9572ac8a7bd7e1b4c9495764b246a730bdd5 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4211304.txt @@ -0,0 +1 @@ +* Резервне копіювання запускається як процес на передньому плані, щоб запобігти його зупинці через 10 хвилин From 81c220f5c3ca4c3f176bf327340a6e5d998b5b46 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Thu, 23 May 2024 12:05:41 +0000 Subject: [PATCH 074/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (67 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/zh_Hans/ --- fastlane/metadata/android/zh-CN/changelogs/4211304.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/zh-CN/changelogs/4211304.txt diff --git a/fastlane/metadata/android/zh-CN/changelogs/4211304.txt b/fastlane/metadata/android/zh-CN/changelogs/4211304.txt new file mode 100644 index 0000000000000000000000000000000000000000..fe176e4bf43ca62fde2b576629273af07faa5f9b --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4211304.txt @@ -0,0 +1 @@ +* 作为前台服务运行备份,防止进程在 10 分钟后停止 From 4406ac930ecbff7102d579925ec8c8ba8365452c Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Fri, 24 May 2024 06:00:21 +0000 Subject: [PATCH 075/192] Translated using Weblate (Polish) Currently translated at 16.4% (11 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/pl/ --- fastlane/metadata/android/pl-PL/changelogs/4211304.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/pl-PL/changelogs/4211304.txt diff --git a/fastlane/metadata/android/pl-PL/changelogs/4211304.txt b/fastlane/metadata/android/pl-PL/changelogs/4211304.txt new file mode 100644 index 0000000000000000000000000000000000000000..318c2f3124f2597713eabbe47c9a3c2e55e023b5 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4211304.txt @@ -0,0 +1 @@ +* Uruchamianie kopii zapasowej jako usługi na pierwszym planie, żeby proces nie był zatrzymywany po 10 minutach From ae516274b367e49e39ac9ac5669899ab165c5565 Mon Sep 17 00:00:00 2001 From: ghose Date: Fri, 24 May 2024 02:47:29 +0000 Subject: [PATCH 076/192] Translated using Weblate (Galician) Currently translated at 58.2% (39 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/gl/ --- fastlane/metadata/android/gl-ES/changelogs/4211304.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/gl-ES/changelogs/4211304.txt diff --git a/fastlane/metadata/android/gl-ES/changelogs/4211304.txt b/fastlane/metadata/android/gl-ES/changelogs/4211304.txt new file mode 100644 index 0000000000000000000000000000000000000000..7ca805abbefeea8aa10b592dc94e81f53f493fd8 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/4211304.txt @@ -0,0 +1 @@ +* Crear a Copia de Apoio usando o servizo en primeiro plano para evitar que sexa detido após 10 minutos From 8d3a7dda52a6148a3b59480316a28dc5e3c6fc3d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 24 May 2024 08:19:42 +0200 Subject: [PATCH 077/192] exclude Lenovo Smart Tab YT-X705F from call integration --- .../eu/siacs/conversations/services/CallIntegration.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index dbe22359955900167b6437317c54eae7ce7e4443..121c2cae6de367606d2e68cf636c1cb5e7952923 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegration.java @@ -42,8 +42,12 @@ public class CallIntegration extends Connection { * *

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

Lenovo Yoga Smart Tab YT-X705F claims to have FEATURE_CONNECTION_SERVICE but throws + * SecurityException */ - private static final List BROKEN_DEVICE_MODELS = Arrays.asList("OnePlus6", "gtaxlwifi"); + private static final List BROKEN_DEVICE_MODELS = + Arrays.asList("OnePlus6", "gtaxlwifi", "YT-X705F"); public static final int DEFAULT_TONE_VOLUME = 60; private static final int DEFAULT_MEDIA_PLAYER_VOLUME = 90; From fd55e637699e0e1104ff70867c7b1a9e1bcb8a70 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 23 May 2024 13:59:33 +0200 Subject: [PATCH 078/192] provide alternative api for XMPP extensions --- build.gradle | 4 + libs/annotation-processor/build.gradle | 20 + .../processor/XmlElementProcessor.java | 185 ++++ libs/annotation/build.gradle | 6 + .../android/annotation/XmlElement.java | 15 + .../android/annotation/XmlPackage.java | 12 + proguard-rules.pro | 1 + settings.gradle | 2 + .../services/PushManagementService.java | 11 +- .../crypto/axolotl/AxolotlService.java | 375 ++++---- .../entities/ServiceDiscoveryResult.java | 36 +- .../conversations/generator/IqGenerator.java | 162 ++-- .../generator/MessageGenerator.java | 92 +- .../generator/PresenceGenerator.java | 39 +- .../conversations/http/SlotRequester.java | 14 +- .../conversations/parser/AbstractParser.java | 14 +- .../siacs/conversations/parser/IqParser.java | 82 +- .../conversations/parser/MessageParser.java | 68 +- .../conversations/parser/PresenceParser.java | 17 +- .../services/ChannelDiscoveryService.java | 48 +- .../services/MessageArchiveService.java | 20 +- .../services/UnifiedPushBroker.java | 15 +- .../services/XmppConnectionService.java | 824 ++++++++---------- .../siacs/conversations/ui/XmppActivity.java | 11 +- .../eu/siacs/conversations/xml/Element.java | 17 +- .../conversations/xml/LocalizedContent.java | 2 +- .../eu/siacs/conversations/xml/Namespace.java | 49 +- .../eu/siacs/conversations/xml/TagWriter.java | 15 +- .../eu/siacs/conversations/xml/XmlReader.java | 25 +- .../siacs/conversations/xmpp/InvalidJid.java | 6 +- .../xmpp/OnIqPacketReceived.java | 8 - .../xmpp/OnMessagePacketReceived.java | 7 +- .../xmpp/OnPresencePacketReceived.java | 8 - .../conversations/xmpp/PacketReceived.java | 5 - .../conversations/xmpp/XmppConnection.java | 711 ++++++++------- .../xmpp/jingle/AbstractContentMap.java | 10 +- .../xmpp/jingle/AbstractJingleConnection.java | 49 +- .../xmpp/jingle/FileTransferContentMap.java | 5 +- .../conversations/xmpp/jingle/IceServers.java | 6 +- .../xmpp/jingle/JingleConnectionManager.java | 60 +- .../jingle/JingleFileTransferConnection.java | 166 ++-- .../xmpp/jingle/JingleRtpConnection.java | 191 ++-- .../xmpp/jingle/OnJinglePacketReceived.java | 7 +- .../xmpp/jingle/RtpContentMap.java | 6 +- .../stanzas/FileTransferDescription.java | 11 +- .../InbandBytestreamsTransport.java | 14 +- .../transports/SocksByteStreamsTransport.java | 21 +- .../WebRTCDataChannelTransport.java | 8 +- .../xmpp/pep/PublishOptions.java | 6 +- .../AbstractAcknowledgeableStanza.java | 42 - .../xmpp/stanzas/AbstractStanza.java | 53 -- .../conversations/xmpp/stanzas/IqPacket.java | 75 -- .../xmpp/stanzas/MessagePacket.java | 100 --- .../xmpp/stanzas/PresencePacket.java | 8 - .../xmpp/stanzas/csi/ActivePacket.java | 11 - .../xmpp/stanzas/csi/InactivePacket.java | 11 - .../xmpp/stanzas/streammgmt/AckPacket.java | 14 - .../xmpp/stanzas/streammgmt/EnablePacket.java | 14 - .../stanzas/streammgmt/RequestPacket.java | 13 - .../xmpp/stanzas/streammgmt/ResumePacket.java | 15 - .../im/conversations/android/xmpp/Entity.java | 34 + .../android/xmpp/EntityCapabilities.java | 133 +++ .../android/xmpp/EntityCapabilities2.java | 185 ++++ .../android/xmpp/ExtensionFactory.java | 78 ++ .../android/xmpp/NodeConfiguration.java | 112 +++ .../im/conversations/android/xmpp/Page.java | 31 + .../im/conversations/android/xmpp/Range.java | 40 + .../android/xmpp/Timestamps.java | 44 + .../model/AuthenticationStreamFeature.java | 12 + .../android/xmpp/model/ByteContent.java | 33 + .../android/xmpp/model/DeliveryReceipt.java | 10 + .../xmpp/model/DeliveryReceiptRequest.java | 8 + .../android/xmpp/model/Extension.java | 62 ++ .../android/xmpp/model/Hash.java | 46 + .../android/xmpp/model/StreamElement.java | 8 + .../android/xmpp/model/StreamFeature.java | 8 + .../xmpp/model/addressing/Address.java | 11 + .../xmpp/model/addressing/Addresses.java | 11 + .../xmpp/model/addressing/package-info.java | 6 + .../android/xmpp/model/avatar/Data.java | 14 + .../android/xmpp/model/avatar/Info.java | 37 + .../android/xmpp/model/avatar/Metadata.java | 13 + .../android/xmpp/model/axolotl/Bundle.java | 60 ++ .../android/xmpp/model/axolotl/Device.java | 22 + .../xmpp/model/axolotl/DeviceList.java | 35 + .../model/axolotl/ECPublicKeyContent.java | 23 + .../android/xmpp/model/axolotl/Encrypted.java | 24 + .../android/xmpp/model/axolotl/Header.java | 45 + .../android/xmpp/model/axolotl/IV.java | 13 + .../xmpp/model/axolotl/IdentityKey.java | 12 + .../android/xmpp/model/axolotl/Key.java | 29 + .../android/xmpp/model/axolotl/Payload.java | 13 + .../android/xmpp/model/axolotl/PreKey.java | 21 + .../android/xmpp/model/axolotl/PreKeys.java | 12 + .../xmpp/model/axolotl/SignedPreKey.java | 21 + .../model/axolotl/SignedPreKeySignature.java | 13 + .../xmpp/model/axolotl/package-info.java | 5 + .../android/xmpp/model/bind/Bind.java | 34 + .../android/xmpp/model/bind/Jid.java | 13 + .../android/xmpp/model/bind/Resource.java | 16 + .../android/xmpp/model/bind/package-info.java | 5 + .../android/xmpp/model/bind2/Bind.java | 24 + .../android/xmpp/model/bind2/Bound.java | 11 + .../android/xmpp/model/bind2/Feature.java | 12 + .../android/xmpp/model/bind2/Inline.java | 12 + .../xmpp/model/bind2/package-info.java | 5 + .../android/xmpp/model/blocking/Block.java | 12 + .../xmpp/model/blocking/Blocklist.java | 11 + .../android/xmpp/model/blocking/Item.java | 17 + .../android/xmpp/model/blocking/Unblock.java | 12 + .../xmpp/model/blocking/package-info.java | 5 + .../xmpp/model/bookmark/Conference.java | 32 + .../xmpp/model/bookmark/Extensions.java | 12 + .../android/xmpp/model/bookmark/Nick.java | 12 + .../xmpp/model/bookmark/package-info.java | 5 + .../xmpp/model/capabilties/Capabilities.java | 43 + .../model/capabilties/EntityCapabilities.java | 39 + .../model/capabilties/LegacyCapabilities.java | 45 + .../android/xmpp/model/carbons/Enable.java | 12 + .../android/xmpp/model/carbons/Received.java | 17 + .../android/xmpp/model/carbons/Sent.java | 17 + .../xmpp/model/carbons/package-info.java | 5 + .../xmpp/model/correction/Replace.java | 24 + .../android/xmpp/model/csi/Active.java | 12 + .../xmpp/model/csi/ClientStateIndication.java | 12 + .../android/xmpp/model/csi/Inactive.java | 12 + .../android/xmpp/model/csi/package-info.java | 5 + .../android/xmpp/model/data/Data.java | 110 +++ .../android/xmpp/model/data/Field.java | 29 + .../android/xmpp/model/data/Option.java | 12 + .../android/xmpp/model/data/Value.java | 12 + .../android/xmpp/model/data/package-info.java | 5 + .../android/xmpp/model/delay/Delay.java | 30 + .../xmpp/model/disco/external/Service.java | 12 + .../xmpp/model/disco/external/Services.java | 12 + .../model/disco/external/package-info.java | 5 + .../xmpp/model/disco/info/Feature.java | 19 + .../xmpp/model/disco/info/Identity.java | 39 + .../xmpp/model/disco/info/InfoQuery.java | 38 + .../xmpp/model/disco/info/package-info.java | 5 + .../android/xmpp/model/disco/items/Item.java | 22 + .../xmpp/model/disco/items/ItemsQuery.java | 19 + .../xmpp/model/disco/items/package-info.java | 5 + .../android/xmpp/model/error/Condition.java | 188 ++++ .../android/xmpp/model/error/Error.java | 55 ++ .../android/xmpp/model/error/Text.java | 13 + .../android/xmpp/model/fast/Fast.java | 11 + .../android/xmpp/model/fast/Mechanism.java | 11 + .../android/xmpp/model/fast/RequestToken.java | 17 + .../android/xmpp/model/fast/Token.java | 12 + .../android/xmpp/model/fast/package-info.java | 5 + .../android/xmpp/model/forward/Forwarded.java | 18 + .../android/xmpp/model/hints/Store.java | 12 + .../xmpp/model/hints/package-info.java | 6 + .../android/xmpp/model/jabber/Body.java | 21 + .../android/xmpp/model/jabber/Priority.java | 12 + .../android/xmpp/model/jabber/Show.java | 11 + .../android/xmpp/model/jabber/Status.java | 13 + .../android/xmpp/model/jabber/Subject.java | 12 + .../android/xmpp/model/jabber/Thread.java | 12 + .../xmpp/model/jabber/package-info.java | 5 + .../android/xmpp/model/jingle/Jingle.java} | 110 +-- .../model/jingle/error/JingleCondition.java | 44 + .../xmpp/model/jingle/package-info.java | 5 + .../android/xmpp/model/jmi/Accept.java | 11 + .../android/xmpp/model/jmi/JingleMessage.java | 14 + .../android/xmpp/model/jmi/Proceed.java | 24 + .../android/xmpp/model/jmi/Propose.java | 38 + .../android/xmpp/model/jmi/Reject.java | 11 + .../android/xmpp/model/jmi/Retract.java | 11 + .../android/xmpp/model/jmi/package-info.java | 5 + .../android/xmpp/model/mam/End.java | 15 + .../android/xmpp/model/mam/Fin.java | 16 + .../android/xmpp/model/mam/Metadata.java | 20 + .../android/xmpp/model/mam/Query.java | 16 + .../android/xmpp/model/mam/Result.java | 25 + .../android/xmpp/model/mam/Start.java | 16 + .../android/xmpp/model/mam/package-info.java | 5 + .../android/xmpp/model/markers/Displayed.java | 16 + .../android/xmpp/model/markers/Markable.java | 12 + .../android/xmpp/model/markers/Received.java | 20 + .../xmpp/model/markers/package-info.java | 5 + .../android/xmpp/model/mds/Displayed.java | 12 + .../android/xmpp/model/muc/Affiliation.java | 9 + .../android/xmpp/model/muc/History.java | 20 + .../android/xmpp/model/muc/MultiUserChat.java | 12 + .../android/xmpp/model/muc/Role.java | 8 + .../android/xmpp/model/muc/package-info.java | 5 + .../android/xmpp/model/muc/user/Item.java | 58 ++ .../android/xmpp/model/muc/user/MucUser.java | 27 + .../android/xmpp/model/muc/user/Status.java | 16 + .../xmpp/model/muc/user/package-info.java | 5 + .../android/xmpp/model/nick/Nick.java | 13 + .../xmpp/model/occupant/OccupantId.java | 19 + .../android/xmpp/model/oob/OutOfBandData.java | 18 + .../android/xmpp/model/oob/URL.java | 12 + .../android/xmpp/model/oob/package-info.java | 5 + .../android/xmpp/model/pars/PreAuth.java | 17 + .../android/xmpp/model/pgp/Encrypted.java | 14 + .../android/xmpp/model/pgp/Signed.java | 15 + .../android/xmpp/model/ping/Ping.java | 13 + .../android/xmpp/model/pubsub/Item.java | 10 + .../android/xmpp/model/pubsub/Items.java | 52 ++ .../android/xmpp/model/pubsub/PubSub.java | 64 ++ .../android/xmpp/model/pubsub/Publish.java | 16 + .../xmpp/model/pubsub/PublishOptions.java | 21 + .../android/xmpp/model/pubsub/Retract.java | 20 + .../xmpp/model/pubsub/error/PubSubError.java | 19 + .../xmpp/model/pubsub/error/package-info.java | 5 + .../xmpp/model/pubsub/event/Event.java | 56 ++ .../xmpp/model/pubsub/event/Purge.java | 16 + .../xmpp/model/pubsub/event/Retract.java | 16 + .../xmpp/model/pubsub/event/package-info.java | 5 + .../xmpp/model/pubsub/owner/Configure.java | 21 + .../xmpp/model/pubsub/owner/PubSubOwner.java | 12 + .../xmpp/model/pubsub/owner/package-info.java | 5 + .../xmpp/model/pubsub/package-info.java | 5 + .../xmpp/model/reactions/Reaction.java | 17 + .../xmpp/model/reactions/Reactions.java | 36 + .../xmpp/model/reactions/package-info.java | 5 + .../android/xmpp/model/receipts/Received.java | 20 + .../android/xmpp/model/receipts/Request.java | 12 + .../xmpp/model/receipts/package-info.java | 5 + .../xmpp/model/register/Instructions.java | 10 + .../android/xmpp/model/register/Password.java | 10 + .../android/xmpp/model/register/Register.java | 21 + .../android/xmpp/model/register/Remove.java | 10 + .../android/xmpp/model/register/Username.java | 12 + .../xmpp/model/register/package-info.java | 5 + .../android/xmpp/model/roster/Group.java | 12 + .../android/xmpp/model/roster/Item.java | 61 ++ .../android/xmpp/model/roster/Query.java | 21 + .../xmpp/model/roster/package-info.java | 5 + .../android/xmpp/model/rsm/After.java | 12 + .../android/xmpp/model/rsm/Before.java | 12 + .../android/xmpp/model/rsm/Count.java | 23 + .../android/xmpp/model/rsm/First.java | 12 + .../android/xmpp/model/rsm/Last.java | 12 + .../android/xmpp/model/rsm/Max.java | 16 + .../android/xmpp/model/rsm/Set.java | 55 ++ .../android/xmpp/model/rsm/package-info.java | 5 + .../android/xmpp/model/sasl/Auth.java | 12 + .../android/xmpp/model/sasl/Mechanism.java | 12 + .../android/xmpp/model/sasl/Mechanisms.java | 29 + .../android/xmpp/model/sasl/Response.java | 12 + .../android/xmpp/model/sasl/Success.java | 13 + .../android/xmpp/model/sasl/package-info.java | 5 + .../xmpp/model/sasl2/Authenticate.java | 12 + .../xmpp/model/sasl2/Authentication.java | 30 + .../model/sasl2/AuthorizationIdentifier.java | 28 + .../android/xmpp/model/sasl2/Inline.java | 34 + .../android/xmpp/model/sasl2/Mechanism.java | 12 + .../android/xmpp/model/sasl2/Response.java | 12 + .../android/xmpp/model/sasl2/Success.java | 23 + .../xmpp/model/sasl2/package-info.java | 5 + .../android/xmpp/model/sm/Ack.java | 23 + .../android/xmpp/model/sm/Enable.java | 13 + .../android/xmpp/model/sm/Enabled.java | 35 + .../android/xmpp/model/sm/Failed.java | 17 + .../android/xmpp/model/sm/Request.java | 12 + .../android/xmpp/model/sm/Resume.java | 18 + .../android/xmpp/model/sm/Resumed.java | 18 + .../xmpp/model/sm/StreamManagement.java | 12 + .../android/xmpp/model/sm/package-info.java | 5 + .../android/xmpp/model/stanza/Iq.java | 77 ++ .../android/xmpp/model/stanza/Message.java | 64 ++ .../android/xmpp/model/stanza/Presence.java | 12 + .../android/xmpp/model/stanza/Stanza.java | 74 ++ .../xmpp/model/stanza/package-info.java | 5 + .../android/xmpp/model/state/Active.java | 11 + .../model/state/ChatStateNotification.java | 10 + .../android/xmpp/model/state/Composing.java | 11 + .../android/xmpp/model/state/Gone.java | 11 + .../android/xmpp/model/state/Inactive.java | 11 + .../android/xmpp/model/state/Paused.java | 11 + .../xmpp/model/state/package-info.java | 5 + .../android/xmpp/model/streams/Features.java | 33 + .../xmpp/model/streams/package-info.java | 5 + .../android/xmpp/model/tls/Proceed.java | 13 + .../android/xmpp/model/tls/Required.java | 11 + .../android/xmpp/model/tls/StartTls.java | 15 + .../android/xmpp/model/tls/package-info.java | 5 + .../android/xmpp/model/unique/OriginId.java | 12 + .../android/xmpp/model/unique/StanzaId.java | 21 + .../xmpp/model/unique/package-info.java | 5 + .../android/xmpp/model/upload/Get.java | 22 + .../android/xmpp/model/upload/Header.java | 16 + .../android/xmpp/model/upload/Put.java | 27 + .../android/xmpp/model/upload/Request.java | 24 + .../android/xmpp/model/upload/Slot.java | 12 + .../xmpp/model/upload/package-info.java | 5 + .../android/xmpp/model/vcard/BinaryValue.java | 13 + .../android/xmpp/model/vcard/Photo.java | 11 + .../android/xmpp/model/vcard/VCard.java | 12 + .../xmpp/model/vcard/package-info.java | 5 + .../xmpp/model/vcard/update/Photo.java | 12 + .../xmpp/model/vcard/update/VCardUpdate.java | 21 + .../xmpp/model/vcard/update/package-info.java | 5 + .../android/xmpp/model/version/Version.java | 25 + .../android/xmpp/processor/BindProcessor.java | 90 ++ .../services/PushManagementService.java | 143 +-- .../services/QuickConversationsService.java | 10 +- 302 files changed, 7037 insertions(+), 2146 deletions(-) create mode 100644 libs/annotation-processor/build.gradle create mode 100644 libs/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java create mode 100644 libs/annotation/build.gradle create mode 100644 libs/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java create mode 100644 libs/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/OnIqPacketReceived.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/OnPresencePacketReceived.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/PacketReceived.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java delete mode 100644 src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java create mode 100644 src/main/java/im/conversations/android/xmpp/Entity.java create mode 100644 src/main/java/im/conversations/android/xmpp/EntityCapabilities.java create mode 100644 src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java create mode 100644 src/main/java/im/conversations/android/xmpp/ExtensionFactory.java create mode 100644 src/main/java/im/conversations/android/xmpp/NodeConfiguration.java create mode 100644 src/main/java/im/conversations/android/xmpp/Page.java create mode 100644 src/main/java/im/conversations/android/xmpp/Range.java create mode 100644 src/main/java/im/conversations/android/xmpp/Timestamps.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/AuthenticationStreamFeature.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/ByteContent.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/Extension.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/Hash.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/StreamElement.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/StreamFeature.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/addressing/Address.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/addressing/Addresses.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/addressing/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/avatar/Data.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/avatar/Info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/Key.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind/Bind.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind/Jid.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind/Resource.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind2/Bound.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind2/Feature.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind2/Inline.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind2/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/blocking/Block.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/blocking/Item.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bookmark/Extensions.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bookmark/Nick.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/carbons/Received.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/correction/Replace.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/csi/Active.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/csi/ClientStateIndication.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/csi/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/data/Data.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/data/Field.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/data/Option.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/data/Value.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/data/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/delay/Delay.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/external/Service.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/external/Services.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/external/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/error/Condition.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/error/Error.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/error/Text.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/fast/Fast.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/fast/Mechanism.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/fast/RequestToken.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/fast/Token.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/fast/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/hints/Store.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/hints/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/Body.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/Priority.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/Show.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/Status.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/Subject.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java rename src/main/java/{eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java => im/conversations/android/xmpp/model/jingle/Jingle.java} (62%) create mode 100644 src/main/java/im/conversations/android/xmpp/model/jingle/error/JingleCondition.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jingle/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/Accept.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/JingleMessage.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/Proceed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/Propose.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/Reject.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/Retract.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/jmi/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/End.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/Fin.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/Metadata.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/Query.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/Result.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/Start.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mam/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/markers/Markable.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/markers/Received.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/markers/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/mds/Displayed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/History.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/MultiUserChat.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/Role.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/user/Item.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/user/MucUser.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/user/Status.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/nick/Nick.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/oob/URL.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/oob/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pgp/Encrypted.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pgp/Signed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/ping/Ping.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/receipts/Received.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/receipts/Request.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/register/Instructions.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/register/Password.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/register/Register.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/register/Remove.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/register/Username.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/register/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/roster/Group.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/roster/Item.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/roster/Query.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/roster/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/After.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/Before.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/Count.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/First.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/Last.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/Max.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/Set.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/rsm/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/Mechanism.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/Mechanisms.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/Response.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/Success.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Authentication.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Mechanism.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Response.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Success.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Ack.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Enable.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Enabled.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Failed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Request.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Resume.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/Resumed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/StreamManagement.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sm/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/stanza/Message.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/Active.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/Composing.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/Gone.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/Inactive.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/Paused.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/state/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/streams/Features.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/streams/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/tls/Proceed.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/tls/Required.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/tls/StartTls.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/tls/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/unique/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/upload/Get.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/upload/Header.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/upload/Put.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/upload/Request.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/upload/Slot.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/upload/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/BinaryValue.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/VCard.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/update/Photo.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/update/VCardUpdate.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/vcard/update/package-info.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/version/Version.java create mode 100644 src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java diff --git a/build.gradle b/build.gradle index beb73f787e4ed82d9b51b84522faafd60ac8d266..7381247022b8a3125ac0d51b99aa3cb77440bcef 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,10 @@ configurations { dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' + implementation project(':libs:annotation') + annotationProcessor project(':libs:annotation-processor') + + implementation 'androidx.viewpager:viewpager:1.0.0' playstoreImplementation('com.google.firebase:firebase-messaging:24.0.0') { diff --git a/libs/annotation-processor/build.gradle b/libs/annotation-processor/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..6232f33c6e557d384e4e63db790d24e9ea0864c6 --- /dev/null +++ b/libs/annotation-processor/build.gradle @@ -0,0 +1,20 @@ +apply plugin: "java-library" + +repositories { + google() + mavenCentral() +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} +dependencies { + + implementation project(':libs:annotation') + + annotationProcessor 'com.google.auto.service:auto-service:1.0.1' + api 'com.google.auto.service:auto-service-annotations:1.0.1' + implementation 'com.google.guava:guava:31.1-jre' + +} \ No newline at end of file diff --git a/libs/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java b/libs/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..c42cc5340537f328541761290dcb04eebae5d819 --- /dev/null +++ b/libs/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java @@ -0,0 +1,185 @@ +package im.conversations.android.annotation.processor; + +import com.google.auto.service.AutoService; +import com.google.common.base.CaseFormat; +import com.google.common.base.Objects; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.annotation.XmlPackage; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementFilter; +import javax.tools.JavaFileObject; + +@AutoService(Processor.class) +@SupportedSourceVersion(SourceVersion.RELEASE_17) +@SupportedAnnotationTypes("im.conversations.android.annotation.XmlElement") +public class XmlElementProcessor extends AbstractProcessor { + + @Override + public boolean process(Set set, RoundEnvironment roundEnvironment) { + final Set elements = + roundEnvironment.getElementsAnnotatedWith(XmlElement.class); + final ImmutableMap.Builder builder = ImmutableMap.builder(); + for (final Element element : elements) { + if (element instanceof final TypeElement typeElement) { + final Id id = of(typeElement); + builder.put(id, typeElement.getQualifiedName().toString()); + } + } + final ImmutableMap maps = builder.build(); + if (maps.isEmpty()) { + return false; + } + final JavaFileObject extensionFile; + try { + extensionFile = + processingEnv + .getFiler() + .createSourceFile("im.conversations.android.xmpp.Extensions"); + } catch (final IOException e) { + throw new RuntimeException(e); + } + try (final PrintWriter out = new PrintWriter(extensionFile.openWriter())) { + out.println("package im.conversations.android.xmpp;"); + out.println("import com.google.common.collect.BiMap;"); + out.println("import com.google.common.collect.ImmutableBiMap;"); + out.println("import im.conversations.android.xmpp.ExtensionFactory;"); + out.println("import im.conversations.android.xmpp.model.Extension;"); + out.print("\n"); + out.println("public final class Extensions {"); + out.println( + "public static final BiMap>" + + " EXTENSION_CLASS_MAP;"); + out.println("static {"); + out.println( + "final var builder = new ImmutableBiMap.Builder>();"); + for (final Map.Entry entry : maps.entrySet()) { + Id id = entry.getKey(); + String clazz = entry.getValue(); + out.format( + "builder.put(new ExtensionFactory.Id(\"%s\",\"%s\"),%s.class);", + id.name, id.namespace, clazz); + out.print("\n"); + } + out.println("EXTENSION_CLASS_MAP = builder.build();"); + out.println("}"); + out.println(" private Extensions() {}"); + out.println("}"); + // writing generated file to out … + } catch (IOException e) { + throw new RuntimeException(e); + } + return true; + } + + private static Id of(final TypeElement typeElement) { + final XmlElement xmlElement = typeElement.getAnnotation(XmlElement.class); + final PackageElement packageElement = getPackageElement(typeElement); + final XmlPackage xmlPackage = + packageElement == null ? null : packageElement.getAnnotation(XmlPackage.class); + if (xmlElement == null) { + throw new IllegalStateException( + String.format( + "%s is not annotated as @XmlElement", + typeElement.getQualifiedName().toString())); + } + final String packageNamespace = xmlPackage == null ? null : xmlPackage.namespace(); + final String elementName = xmlElement.name(); + final String elementNamespace = xmlElement.namespace(); + final String namespace; + if (!Strings.isNullOrEmpty(elementNamespace)) { + namespace = elementNamespace; + } else if (!Strings.isNullOrEmpty(packageNamespace)) { + namespace = packageNamespace; + } else { + throw new IllegalStateException( + String.format( + "%s does not declare a namespace", + typeElement.getQualifiedName().toString())); + } + if (!hasEmptyDefaultConstructor(typeElement)) { + throw new IllegalStateException( + String.format( + "%s does not have an empty default constructor", + typeElement.getQualifiedName().toString())); + } + final String name; + if (Strings.isNullOrEmpty(elementName)) { + name = + CaseFormat.UPPER_CAMEL.to( + CaseFormat.LOWER_HYPHEN, typeElement.getSimpleName().toString()); + } else { + name = elementName; + } + return new Id(name, namespace); + } + + private static PackageElement getPackageElement(final TypeElement typeElement) { + final Element parent = typeElement.getEnclosingElement(); + if (parent instanceof PackageElement) { + return (PackageElement) parent; + } else { + final Element nextParent = parent.getEnclosingElement(); + if (nextParent instanceof PackageElement) { + return (PackageElement) nextParent; + } else { + return null; + } + } + } + + private static boolean hasEmptyDefaultConstructor(final TypeElement typeElement) { + final List constructors = + ElementFilter.constructorsIn(typeElement.getEnclosedElements()); + for (final ExecutableElement constructor : constructors) { + if (constructor.getParameters().isEmpty() + && constructor.getModifiers().contains(Modifier.PUBLIC)) { + return true; + } + } + return false; + } + + public static class Id { + public final String name; + public final String namespace; + + public Id(String name, String namespace) { + this.name = name; + this.namespace = namespace; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Id id = (Id) o; + return Objects.equal(name, id.name) && Objects.equal(namespace, id.namespace); + } + + @Override + public int hashCode() { + return Objects.hashCode(name, namespace); + } + } +} diff --git a/libs/annotation/build.gradle b/libs/annotation/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..13a27e90c8f86b1aa7a00a15005dba57406dc570 --- /dev/null +++ b/libs/annotation/build.gradle @@ -0,0 +1,6 @@ +apply plugin: "java-library" + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} \ No newline at end of file diff --git a/libs/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java b/libs/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java new file mode 100644 index 0000000000000000000000000000000000000000..68ff736352d3396aef81c6b6d9d57f5e72e547bb --- /dev/null +++ b/libs/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java @@ -0,0 +1,15 @@ +package im.conversations.android.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE}) +public @interface XmlElement { + + String name() default ""; + + String namespace() default ""; +} diff --git a/libs/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java b/libs/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java new file mode 100644 index 0000000000000000000000000000000000000000..462fc6965b7a885c17d55a99262662169dde346e --- /dev/null +++ b/libs/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java @@ -0,0 +1,12 @@ +package im.conversations.android.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.PACKAGE) +public @interface XmlPackage { + String namespace(); +} diff --git a/proguard-rules.pro b/proguard-rules.pro index 389e75904be67688b2650bbc60adc2b486c27593..c23fd72ae9e0740cf9826edbedf5784c2a836e2b 100644 --- a/proguard-rules.pro +++ b/proguard-rules.pro @@ -1,6 +1,7 @@ -dontobfuscate -keep class eu.siacs.conversations.** +-keep class im.conversations.** -keep class org.whispersystems.** diff --git a/settings.gradle b/settings.gradle index 4193570fa762a8d407f5d06beec954146d2254ea..3cecbc88944cf4f85d51fa2a2c363dd3b327c062 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,3 @@ +include ':libs:annotation', ':libs:annotation-processor:' + rootProject.name = 'Conversations' diff --git a/src/free/java/eu/siacs/conversations/services/PushManagementService.java b/src/free/java/eu/siacs/conversations/services/PushManagementService.java index f436da434e14d0e90f2ff14233cc1f5a83d0d811..c6c5d232466c9861062f2a9600c3d9fb0a4fbf65 100644 --- a/src/free/java/eu/siacs/conversations/services/PushManagementService.java +++ b/src/free/java/eu/siacs/conversations/services/PushManagementService.java @@ -1,7 +1,6 @@ package eu.siacs.conversations.services; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Conversation; public class PushManagementService { @@ -11,11 +10,7 @@ public class PushManagementService { this.mXmppConnectionService = service; } - void registerPushTokenOnServer(Account account) { - //stub implementation. only affects playstore flavor - } - - void unregisterChannel(Account account, String hash) { + public void registerPushTokenOnServer(Account account) { //stub implementation. only affects playstore flavor } @@ -26,8 +21,4 @@ public class PushManagementService { public boolean isStub() { return true; } - - public boolean availableAndUseful(Account account) { - return false; - } } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 3721f4cfeed67e57914529130bae30f63025823b..b1ecda3c23f20a8ce309e8b29c64299531d9d132 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -61,7 +61,6 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; -import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jingle.DescriptionTransport; import eu.siacs.conversations.xmpp.jingle.OmemoVerification; import eu.siacs.conversations.xmpp.jingle.OmemoVerifiedRtpContentMap; @@ -70,8 +69,7 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.pep.PublishOptions; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import im.conversations.android.xmpp.model.stanza.Iq; public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { @@ -392,20 +390,18 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... "); return; } - IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid()); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { - Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids."); - } else { - //TODO consider calling registerDevices only after item-not-found to account for broken PEPs - Element item = mXmppConnectionService.getIqParser().getItem(packet); - Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds); - registerDevices(account.getJid().asBareJid(), deviceIds); - } + Iq packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid()); + mXmppConnectionService.sendIqPacket(account, packet, response -> { + if (packet.getType() == Iq.Type.TIMEOUT) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids."); + } else { + //TODO consider calling registerDevices only after item-not-found to account for broken PEPs + final Element item = IqParser.getItem(packet); + final Set deviceIds = IqParser.deviceIds(item); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds); + registerDevices(account.getJid().asBareJid(), deviceIds); } + }); } @@ -455,40 +451,37 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { private void publishDeviceIdsAndRefineAccessModel(final Set ids, final boolean firstAttempt) { final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null; - IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions); - mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - final Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null; - final boolean preConditionNotMet = PublishOptions.preconditionNotMet(packet); - if (firstAttempt && preConditionNotMet) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for device list. pushing node configuration"); - mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() { - @Override - public void onPushSucceeded() { - publishDeviceIdsAndRefineAccessModel(ids, false); - } - - @Override - public void onPushFailed() { - publishDeviceIdsAndRefineAccessModel(ids, false); - } - }); - } else { - if (AxolotlService.this.changeAccessMode.compareAndSet(true, false)) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": done changing access mode"); - account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false); - mXmppConnectionService.databaseBackend.updateAccount(account); + final var publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions); + mXmppConnectionService.sendIqPacket(account, publish, response -> { + final Element error = response.getType() == Iq.Type.ERROR ? response.findChild("error") : null; + final boolean preConditionNotMet = PublishOptions.preconditionNotMet(response); + if (firstAttempt && preConditionNotMet) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for device list. pushing node configuration"); + mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() { + @Override + public void onPushSucceeded() { + publishDeviceIdsAndRefineAccessModel(ids, false); } - if (packet.getType() == IqPacket.TYPE.ERROR) { - if (preConditionNotMet) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": device list pre condition still not met on second attempt"); - } else if (error != null) { - pepBroken = true; - Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error")); - } + @Override + public void onPushFailed() { + publishDeviceIdsAndRefineAccessModel(ids, false); } + }); + } else { + if (AxolotlService.this.changeAccessMode.compareAndSet(true, false)) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": done changing access mode"); + account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false); + mXmppConnectionService.databaseBackend.updateAccount(account); + } + if (response.getType() == Iq.Type.ERROR) { + if (preConditionNotMet) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": device list pre condition still not met on second attempt"); + } else if (error != null) { + pepBroken = true; + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + response.findChild("error")); + } + } } }); @@ -506,26 +499,23 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { verifier.initSign(x509PrivateKey, SECURE_RANDOM); verifier.update(axolotlPublicKey.serialize()); byte[] signature = verifier.sign(); - IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId()); + final Iq packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId()); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device " + getOwnDeviceId()); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, IqPacket packet) { - String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId(); - mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() { - @Override - public void onPushSucceeded() { - Log.d(Config.LOGTAG, getLogprefix(account) + "configured verification node to be world readable"); - publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe); - } + mXmppConnectionService.sendIqPacket(account, packet, response -> { + String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId(); + mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() { + @Override + public void onPushSucceeded() { + Log.d(Config.LOGTAG, getLogprefix(account) + "configured verification node to be world readable"); + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe); + } - @Override - public void onPushFailed() { - Log.d(Config.LOGTAG, getLogprefix(account) + "unable to set access model on verification node"); - publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe); - } - }); - } + @Override + public void onPushFailed() { + Log.d(Config.LOGTAG, getLogprefix(account) + "unable to set access model on verification node"); + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe); + } + }); }); } catch (Exception e) { e.printStackTrace(); @@ -549,109 +539,106 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { if (this.changeAccessMode.get()) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server gained publish-options capabilities. changing access model"); } - IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId()); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { + final Iq packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, response -> { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { - return; //ignore timeout. do nothing - } + if (response.getType() == Iq.Type.TIMEOUT) { + return; //ignore timeout. do nothing + } - if (packet.getType() == IqPacket.TYPE.ERROR) { - Element error = packet.findChild("error"); - if (error == null || !error.hasChild("item-not-found")) { - pepBroken = true; - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet); - return; - } + if (response.getType() == Iq.Type.ERROR) { + Element error = response.findChild("error"); + if (error == null || !error.hasChild("item-not-found")) { + pepBroken = true; + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + response); + return; } + } - PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet); - Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); - boolean flush = false; - if (bundle == null) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet); - bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null); - flush = true; - } - if (keys == null) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet); + PreKeyBundle bundle = IqParser.bundle(response); + final Map keys = IqParser.preKeyPublics(response); + boolean flush = false; + if (bundle == null) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + response); + bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null); + flush = true; + } + if (keys == null) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + response); + } + try { + boolean changed = false; + // Validate IdentityKey + IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair(); + if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); + changed = true; } - try { - boolean changed = false; - // Validate IdentityKey - IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair(); - if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); - changed = true; - } - // Validate signedPreKeyRecord + ID - SignedPreKeyRecord signedPreKeyRecord; - int numSignedPreKeys = axolotlStore.getSignedPreKeysCount(); - try { - signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId()); - if (flush - || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) - || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); - signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); - axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); - changed = true; - } - } catch (InvalidKeyIdException e) { + // Validate signedPreKeyRecord + ID + SignedPreKeyRecord signedPreKeyRecord; + int numSignedPreKeys = axolotlStore.getSignedPreKeysCount(); + try { + signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId()); + if (flush + || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) + || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) { Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); changed = true; } + } catch (InvalidKeyIdException e) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); + axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); + changed = true; + } - // Validate PreKeys - Set preKeyRecords = new HashSet<>(); - if (keys != null) { - for (Integer id : keys.keySet()) { - try { - PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id); - if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) { - preKeyRecords.add(preKeyRecord); - } - } catch (InvalidKeyIdException ignored) { + // Validate PreKeys + Set preKeyRecords = new HashSet<>(); + if (keys != null) { + for (Integer id : keys.keySet()) { + try { + PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id); + if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) { + preKeyRecords.add(preKeyRecord); } + } catch (InvalidKeyIdException ignored) { } } - int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size(); - if (newKeys > 0) { - List newRecords = KeyHelper.generatePreKeys( - axolotlStore.getCurrentPreKeyId() + 1, newKeys); - preKeyRecords.addAll(newRecords); - for (PreKeyRecord record : newRecords) { - axolotlStore.storePreKey(record.getId(), record); - } - changed = true; - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP."); + } + int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size(); + if (newKeys > 0) { + List newRecords = KeyHelper.generatePreKeys( + axolotlStore.getCurrentPreKeyId() + 1, newKeys); + preKeyRecords.addAll(newRecords); + for (PreKeyRecord record : newRecords) { + axolotlStore.storePreKey(record.getId(), record); } + changed = true; + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP."); + } - if (changed || changeAccessMode.get()) { - if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) { - mXmppConnectionService.publishDisplayName(account); - publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); - } else { - publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); - } + if (changed || changeAccessMode.get()) { + if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) { + mXmppConnectionService.publishDisplayName(account); + publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); } else { - Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current"); - if (wipe) { - wipeOtherPepDevices(); - } else if (announce) { - Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); - publishOwnDeviceIdIfNeeded(); - } + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); + } + } else { + Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current"); + if (wipe) { + wipeOtherPepDevices(); + } else if (announce) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); + publishOwnDeviceIdIfNeeded(); } - } catch (InvalidKeyException e) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); } + } catch (InvalidKeyException e) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); } }); } @@ -669,44 +656,41 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { final boolean wipe, final boolean firstAttempt) { final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null; - final IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( + final Iq publish = mXmppConnectionService.getIqGenerator().publishBundles( signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), preKeyRecords, getOwnDeviceId(), publishOptions); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing..."); - mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, IqPacket packet) { - final boolean preconditionNotMet = PublishOptions.preconditionNotMet(packet); - if (firstAttempt && preconditionNotMet) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration"); - final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId(); - mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() { - @Override - public void onPushSucceeded() { - publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false); - } - - @Override - public void onPushFailed() { - publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false); - } - }); - } else if (packet.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. "); - if (wipe) { - wipeOtherPepDevices(); - } else if (announceAfter) { - Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); - publishOwnDeviceIdIfNeeded(); + mXmppConnectionService.sendIqPacket(account, publish, response -> { + final boolean preconditionNotMet = PublishOptions.preconditionNotMet(response); + if (firstAttempt && preconditionNotMet) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration"); + final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId(); + mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() { + @Override + public void onPushSucceeded() { + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false); } - } else if (packet.getType() == IqPacket.TYPE.ERROR) { - if (preconditionNotMet) { - Log.d(Config.LOGTAG, getLogprefix(account) + "bundle precondition still not met after second attempt"); - } else { - Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.toString()); + + @Override + public void onPushFailed() { + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false); } - pepBroken = true; + }); + } else if (response.getType() == Iq.Type.RESULT) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. "); + if (wipe) { + wipeOtherPepDevices(); + } else if (announceAfter) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); + publishOwnDeviceIdIfNeeded(); + } + } else if (response.getType() == Iq.Type.ERROR) { + if (preconditionNotMet) { + Log.d(Config.LOGTAG, getLogprefix(account) + "bundle precondition still not met after second attempt"); + } else { + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + response.toString()); } + pepBroken = true; } }); } @@ -759,9 +743,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return Futures.immediateFuture(session); } final SettableFuture future = SettableFuture.create(); - final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId()); - mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> { - Pair verification = mXmppConnectionService.getIqParser().verification(response); + final Iq packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, (response) -> { + Pair verification = IqParser.verification(response); if (verification != null) { try { Signature verifier = Signature.getInstance("sha256WithRSA"); @@ -846,7 +830,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) { - IqPacket packet; + final Iq packet; synchronized (this.fetchDeviceIdsMap) { List callbacks = this.fetchDeviceIdsMap.get(jid); if (callbacks != null) { @@ -866,11 +850,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } } if (packet != null) { - mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + mXmppConnectionService.sendIqPacket(account, packet, (response) -> { + if (response.getType() == Iq.Type.RESULT) { fetchDeviceListStatus.put(jid, true); - Element item = mXmppConnectionService.getIqParser().getItem(response); - Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + final Element item = IqParser.getItem(response); + final Set deviceIds = IqParser.deviceIds(item); registerDevices(jid, deviceIds); final List callbacks; synchronized (fetchDeviceIdsMap) { @@ -882,7 +866,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } } } else { - if (response.getType() == IqPacket.TYPE.TIMEOUT) { + if (response.getType() == Iq.Type.TIMEOUT) { fetchDeviceListStatus.remove(jid); } else { fetchDeviceListStatus.put(jid, false); @@ -929,16 +913,15 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } final Jid jid = Jid.of(address.getName()); final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid()); - IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId()); - mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + final Iq bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId()); + mXmppConnectionService.sendIqPacket(account, bundlesPacket, (packet) -> { + if (packet.getType() == Iq.Type.TIMEOUT) { fetchStatusMap.put(address, FetchStatus.TIMEOUT); sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout")); - } else if (packet.getType() == IqPacket.TYPE.RESULT) { + } else if (packet.getType() == Iq.Type.RESULT) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing..."); - final IqParser parser = mXmppConnectionService.getIqParser(); - final List preKeyBundleList = parser.preKeys(packet); - final PreKeyBundle bundle = parser.bundle(packet); + final List preKeyBundleList = IqParser.preKeys(packet); + final PreKeyBundle bundle = IqParser.bundle(packet); if (preKeyBundleList.isEmpty() || bundle == null) { Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet); fetchStatusMap.put(address, FetchStatus.ERROR); @@ -1544,7 +1527,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { axolotlMessage.addDevice(session, true); try { final Jid jid = Jid.of(session.getRemoteAddress().getName()); - MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage); + final var packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage); mXmppConnectionService.sendMessagePacket(account, packet); } catch (IllegalArgumentException e) { throw new Error("Remote addresses are created from jid and should convert back to jid", e); diff --git a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java index 8c63a2670a6dbe174e9ab4674a7d93d6e07136ed..3f2d2a5adf7f907a58c5866a29ead6d9e7a71585 100644 --- a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java +++ b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java @@ -24,7 +24,7 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Field; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; public class ServiceDiscoveryResult { public static final String TABLENAME = "discovery_results"; @@ -36,7 +36,7 @@ public class ServiceDiscoveryResult { protected final List features; protected final List forms; private final List identities; - public ServiceDiscoveryResult(final IqPacket packet) { + public ServiceDiscoveryResult(final Iq packet) { this.identities = new ArrayList<>(); this.features = new ArrayList<>(); this.forms = new ArrayList<>(); @@ -275,7 +275,7 @@ public class ServiceDiscoveryResult { return values; } - public static class Identity implements Comparable { + public static class Identity implements Comparable { protected final String type; protected final String lang; protected final String name; @@ -323,8 +323,21 @@ public class ServiceDiscoveryResult { return this.name; } - public int compareTo(@NonNull Object other) { - Identity o = (Identity) other; + JSONObject toJSON() { + try { + JSONObject o = new JSONObject(); + o.put("category", this.getCategory()); + o.put("type", this.getType()); + o.put("lang", this.getLang()); + o.put("name", this.getName()); + return o; + } catch (JSONException e) { + return null; + } + } + + @Override + public int compareTo(final Identity o) { int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory())); if (r == 0) { r = blankNull(this.getType()).compareTo(blankNull(o.getType())); @@ -338,18 +351,5 @@ public class ServiceDiscoveryResult { return r; } - - JSONObject toJSON() { - try { - JSONObject o = new JSONObject(); - o.put("category", this.getCategory()); - o.put("type", this.getType()); - o.put("lang", this.getLang()); - o.put("name", this.getName()); - return o; - } catch (JSONException e) { - return null; - } - } } } diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index df87932e5565bc5b47bfa540ce519599b1eb639f..85e3a0d7c762e0f4b9bb69be132761d6ae488408 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -34,7 +34,7 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.pep.Avatar; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; public class IqGenerator extends AbstractGenerator { @@ -42,8 +42,8 @@ public class IqGenerator extends AbstractGenerator { super(service); } - public IqPacket discoResponse(final Account account, final IqPacket request) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT); + public Iq discoResponse(final Account account, final Iq request) { + final var packet = new Iq(Iq.Type.RESULT); packet.setId(request.getId()); packet.setTo(request.getFrom()); final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info"); @@ -58,8 +58,8 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket versionResponse(final IqPacket request) { - final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT); + public Iq versionResponse(final Iq request) { + final var packet = request.generateResponse(Iq.Type.RESULT); Element query = packet.query("jabber:iq:version"); query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name)); query.addChild("version").setContent(getIdentityVersion()); @@ -71,8 +71,8 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket entityTimeResponse(IqPacket request) { - final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT); + public Iq entityTimeResponse(final Iq request) { + final Iq packet = request.generateResponse(Iq.Type.RESULT); Element time = packet.addChild("time", "urn:xmpp:time"); final long now = System.currentTimeMillis(); time.addChild("utc").setContent(getTimestamp(now)); @@ -91,14 +91,14 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket purgeOfflineMessages() { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public static Iq purgeOfflineMessages() { + final Iq packet = new Iq(Iq.Type.SET); packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge"); return packet; } - protected IqPacket publish(final String node, final Element item, final Bundle options) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + protected Iq publish(final String node, final Element item, final Bundle options) { + final var packet = new Iq(Iq.Type.SET); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); final Element publish = pubsub.addChild("publish"); publish.setAttribute("node", node); @@ -110,12 +110,12 @@ public class IqGenerator extends AbstractGenerator { return packet; } - protected IqPacket publish(final String node, final Element item) { + protected Iq publish(final String node, final Element item) { return publish(node, item, null); } - private IqPacket retrieve(String node, Element item) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + private Iq retrieve(String node, Element item) { + final var packet = new Iq(Iq.Type.GET); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); final Element items = pubsub.addChild("items"); items.setAttribute("node", node); @@ -125,30 +125,30 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket retrieveBookmarks() { + public Iq retrieveBookmarks() { return retrieve(Namespace.BOOKMARKS2, null); } - public IqPacket retrieveMds() { + public Iq retrieveMds() { return retrieve(Namespace.MDS_DISPLAYED, null); } - public IqPacket publishNick(String nick) { + public Iq publishNick(String nick) { final Element item = new Element("item"); item.setAttribute("id", "current"); item.addChild("nick", Namespace.NICK).setContent(nick); return publish(Namespace.NICK, item); } - public IqPacket deleteNode(final String node) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq deleteNode(final String node) { + final var packet = new Iq(Iq.Type.SET); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER); pubsub.addChild("delete").setAttribute("node", node); return packet; } - public IqPacket deleteItem(final String node, final String id) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq deleteItem(final String node, final String id) { + final var packet = new Iq(Iq.Type.SET); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); final Element retract = pubsub.addChild("retract"); retract.setAttribute("node", node); @@ -157,7 +157,7 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket publishAvatar(Avatar avatar, Bundle options) { + public Iq publishAvatar(Avatar avatar, Bundle options) { final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); final Element data = item.addChild("data", Namespace.AVATAR_DATA); @@ -165,14 +165,14 @@ public class IqGenerator extends AbstractGenerator { return publish(Namespace.AVATAR_DATA, item, options); } - public IqPacket publishElement(final String namespace, final Element element, String id, final Bundle options) { + public Iq publishElement(final String namespace, final Element element, String id, final Bundle options) { final Element item = new Element("item"); item.setAttribute("id", id); item.addChild(element); return publish(namespace, item, options); } - public IqPacket publishAvatarMetadata(final Avatar avatar, final Bundle options) { + public Iq publishAvatarMetadata(final Avatar avatar, final Bundle options) { final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); final Element metadata = item @@ -186,57 +186,57 @@ public class IqGenerator extends AbstractGenerator { return publish(Namespace.AVATAR_METADATA, item, options); } - public IqPacket retrievePepAvatar(final Avatar avatar) { + public Iq retrievePepAvatar(final Avatar avatar) { final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); - final IqPacket packet = retrieve(Namespace.AVATAR_DATA, item); + final var packet = retrieve(Namespace.AVATAR_DATA, item); packet.setTo(avatar.owner); return packet; } - public IqPacket retrieveVcardAvatar(final Avatar avatar) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq retrieveVcardAvatar(final Avatar avatar) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(avatar.owner); packet.addChild("vCard", "vcard-temp"); return packet; } - public IqPacket retrieveVcardAvatar(final Jid to) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq retrieveVcardAvatar(final Jid to) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(to); packet.addChild("vCard", "vcard-temp"); return packet; } - public IqPacket retrieveAvatarMetaData(final Jid to) { - final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null); + public Iq retrieveAvatarMetaData(final Jid to) { + final Iq packet = retrieve("urn:xmpp:avatar:metadata", null); if (to != null) { packet.setTo(to); } return packet; } - public IqPacket retrieveDeviceIds(final Jid to) { - final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null); + public Iq retrieveDeviceIds(final Jid to) { + final var packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null); if (to != null) { packet.setTo(to); } return packet; } - public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) { - final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null); + public Iq retrieveBundlesForDevice(final Jid to, final int deviceid) { + final var packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null); packet.setTo(to); return packet; } - public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) { - final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null); + public Iq retrieveVerificationForDevice(final Jid to, final int deviceid) { + final var packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null); packet.setTo(to); return packet; } - public IqPacket publishDeviceIds(final Set ids, final Bundle publishOptions) { + public Iq publishDeviceIds(final Set ids, final Bundle publishOptions) { final Element item = new Element("item"); item.setAttribute("id", "current"); final Element list = item.addChild("list", AxolotlService.PEP_PREFIX); @@ -286,7 +286,7 @@ public class IqGenerator extends AbstractGenerator { return displayed; } - public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, + public Iq publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, final Set preKeyRecords, final int deviceId, Bundle publishOptions) { final Element item = new Element("item"); item.setAttribute("id", "current"); @@ -310,7 +310,7 @@ public class IqGenerator extends AbstractGenerator { return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions); } - public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) { + public Iq publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) { final Element item = new Element("item"); item.setAttribute("id", "current"); final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX); @@ -328,8 +328,8 @@ public class IqGenerator extends AbstractGenerator { return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item); } - public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq queryMessageArchiveManagement(final MessageArchiveService.Query mam) { + final Iq packet = new Iq(Iq.Type.SET); final Element query = packet.query(mam.version.namespace); query.setAttribute("queryid", mam.getQueryId()); final Data data = new Data(); @@ -359,15 +359,15 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket generateGetBlockList() { - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + public Iq generateGetBlockList() { + final Iq iq = new Iq(Iq.Type.GET); iq.addChild("blocklist", Namespace.BLOCKING); return iq; } - public IqPacket generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) { - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + public Iq generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) { + final Iq iq = new Iq(Iq.Type.SET); final Element block = iq.addChild("block", Namespace.BLOCKING); final Element item = block.addChild("item").setAttribute("jid", jid); if (reportSpam) { @@ -383,15 +383,15 @@ public class IqGenerator extends AbstractGenerator { return iq; } - public IqPacket generateSetUnblockRequest(final Jid jid) { - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + public Iq generateSetUnblockRequest(final Jid jid) { + final Iq iq = new Iq(Iq.Type.SET); final Element block = iq.addChild("unblock", Namespace.BLOCKING); block.addChild("item").setAttribute("jid", jid); return iq; } - public IqPacket generateSetPassword(final Account account, final String newPassword) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq generateSetPassword(final Account account, final String newPassword) { + final Iq packet = new Iq(Iq.Type.SET); packet.setTo(account.getDomain()); final Element query = packet.addChild("query", Namespace.REGISTER); final Jid jid = account.getJid(); @@ -400,14 +400,14 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) { + public Iq changeAffiliation(Conversation conference, Jid jid, String affiliation) { List jids = new ArrayList<>(); jids.add(jid); return changeAffiliation(conference, jids, affiliation); } - public IqPacket changeAffiliation(Conversation conference, List jids, String affiliation) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq changeAffiliation(Conversation conference, List jids, String affiliation) { + final Iq packet = new Iq(Iq.Type.SET); packet.setTo(conference.getJid().asBareJid()); packet.setFrom(conference.getAccount().getJid()); Element query = packet.query("http://jabber.org/protocol/muc#admin"); @@ -419,8 +419,8 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket changeRole(Conversation conference, String nick, String role) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq changeRole(Conversation conference, String nick, String role) { + final Iq packet = new Iq(Iq.Type.SET); packet.setTo(conference.getJid().asBareJid()); packet.setFrom(conference.getAccount().getJid()); Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item"); @@ -429,8 +429,8 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) { - IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(host); Element request = packet.addChild("request", Namespace.HTTP_UPLOAD); request.setAttribute("filename", convertFilename(file.getName())); @@ -439,8 +439,8 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) { - IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(host); Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY); request.addChild("filename").setContent(convertFilename(file.getName())); @@ -466,8 +466,8 @@ public class IqGenerator extends AbstractGenerator { } } - public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) { - final IqPacket register = new IqPacket(IqPacket.TYPE.SET); + public static Iq generateCreateAccountWithCaptcha(final Account account, final String id, final Data data) { + final Iq register = new Iq(Iq.Type.SET); register.setFrom(account.getJid().asBareJid()); register.setTo(account.getDomain()); register.setId(id); @@ -478,12 +478,12 @@ public class IqGenerator extends AbstractGenerator { return register; } - public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) { + public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId) { return pushTokenToAppServer(appServer, token, deviceId, null); } - public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) { + final Iq packet = new Iq(Iq.Type.SET); packet.setTo(appServer); final Element command = packet.addChild("command", Namespace.COMMANDS); command.setAttribute("node", "register-push-fcm"); @@ -499,8 +499,8 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) { + final Iq packet = new Iq(Iq.Type.SET); packet.setTo(appServer); final Element command = packet.addChild("command", Namespace.COMMANDS); command.setAttribute("node", "unregister-push-fcm"); @@ -513,8 +513,8 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket enablePush(final Jid jid, final String node, final String secret) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq enablePush(final Jid jid, final String node, final String secret) { + final Iq packet = new Iq(Iq.Type.SET); Element enable = packet.addChild("enable", Namespace.PUSH); enable.setAttribute("jid", jid); enable.setAttribute("node", node); @@ -528,16 +528,16 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket disablePush(final Jid jid, final String node) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq disablePush(final Jid jid, final String node) { + Iq packet = new Iq(Iq.Type.SET); Element disable = packet.addChild("disable", Namespace.PUSH); disable.setAttribute("jid", jid); disable.setAttribute("node", node); return packet; } - public IqPacket queryAffiliation(Conversation conversation, String affiliation) { - IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq queryAffiliation(Conversation conversation, String affiliation) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(conversation.getJid().asBareJid()); packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation); return packet; @@ -570,16 +570,16 @@ public class IqGenerator extends AbstractGenerator { return options; } - public IqPacket requestPubsubConfiguration(Jid jid, String node) { + public Iq requestPubsubConfiguration(Jid jid, String node) { return pubsubConfiguration(jid, node, null); } - public IqPacket publishPubsubConfiguration(Jid jid, String node, Data data) { + public Iq publishPubsubConfiguration(Jid jid, String node, Data data) { return pubsubConfiguration(jid, node, data); } - private IqPacket pubsubConfiguration(Jid jid, String node, Data data) { - IqPacket packet = new IqPacket(data == null ? IqPacket.TYPE.GET : IqPacket.TYPE.SET); + private Iq pubsubConfiguration(Jid jid, String node, Data data) { + final Iq packet = new Iq(data == null ? Iq.Type.GET : Iq.Type.SET); packet.setTo(jid); Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner"); Element configure = pubsub.addChild("configure").setAttribute("node", node); @@ -589,15 +589,15 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket queryDiscoItems(Jid jid) { - IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq queryDiscoItems(final Jid jid) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(jid); packet.addChild("query",Namespace.DISCO_ITEMS); return packet; } - public IqPacket queryDiscoInfo(Jid jid) { - IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + public Iq queryDiscoInfo(final Jid jid) { + final Iq packet = new Iq(Iq.Type.GET); packet.setTo(jid); packet.addChild("query",Namespace.DISCO_INFO); return packet; diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index e217f7f1d41af5f3e46c4e5583abf787f60cf35e..f3823dee04c2848d6280e58e9b4a453f5386c790 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -22,7 +22,6 @@ import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; public class MessageGenerator extends AbstractGenerator { private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo"; @@ -32,25 +31,25 @@ public class MessageGenerator extends AbstractGenerator { super(service); } - private MessagePacket preparePacket(Message message) { + private im.conversations.android.xmpp.model.stanza.Message preparePacket(Message message) { Conversation conversation = (Conversation) message.getConversation(); Account account = conversation.getAccount(); - MessagePacket packet = new MessagePacket(); + im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); final boolean isWithSelf = conversation.getContact().isSelf(); if (conversation.getMode() == Conversation.MODE_SINGLE) { packet.setTo(message.getCounterpart()); - packet.setType(MessagePacket.TYPE_CHAT); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); if (!isWithSelf) { packet.addChild("request", "urn:xmpp:receipts"); } } else if (message.isPrivateMessage()) { packet.setTo(message.getCounterpart()); - packet.setType(MessagePacket.TYPE_CHAT); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); packet.addChild("x", "http://jabber.org/protocol/muc#user"); packet.addChild("request", "urn:xmpp:receipts"); } else { packet.setTo(message.getCounterpart().asBareJid()); - packet.setType(MessagePacket.TYPE_GROUPCHAT); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT); } if (conversation.isSingleOrPrivateAndNonAnonymous() && !message.isPrivateMessage()) { packet.addChild("markable", "urn:xmpp:chat-markers:0"); @@ -66,7 +65,7 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public void addDelay(MessagePacket packet, long timestamp) { + public void addDelay(im.conversations.android.xmpp.model.stanza.Message packet, long timestamp) { final SimpleDateFormat mDateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); @@ -75,8 +74,8 @@ public class MessageGenerator extends AbstractGenerator { delay.setAttribute("stamp", mDateFormat.format(date)); } - public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) { - MessagePacket packet = preparePacket(message); + public im.conversations.android.xmpp.model.stanza.Message generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) { + im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message); if (axolotlMessage == null) { return null; } @@ -89,17 +88,17 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) { - MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); + public im.conversations.android.xmpp.model.stanza.Message generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) { + im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); packet.setTo(to); packet.setAxolotlMessage(axolotlMessage.toElement()); packet.addChild("store", "urn:xmpp:hints"); return packet; } - public MessagePacket generateChat(Message message) { - MessagePacket packet = preparePacket(message); + public im.conversations.android.xmpp.model.stanza.Message generateChat(Message message) { + im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message); String content; if (message.hasFileOnRemoteHost()) { final Message.FileParams fileParams = message.getFileParams(); @@ -112,8 +111,8 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket generatePgpChat(Message message) { - MessagePacket packet = preparePacket(message); + public im.conversations.android.xmpp.model.stanza.Message generatePgpChat(Message message) { + final im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message); if (message.hasFileOnRemoteHost()) { Message.FileParams fileParams = message.getFileParams(); final String url = fileParams.url; @@ -134,10 +133,10 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket generateChatState(Conversation conversation) { + public im.conversations.android.xmpp.model.stanza.Message generateChatState(Conversation conversation) { final Account account = conversation.getAccount(); - MessagePacket packet = new MessagePacket(); - packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT); + final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); packet.setTo(conversation.getJid().asBareJid()); packet.setFrom(account.getJid()); packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); @@ -146,11 +145,11 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket confirm(final Message message) { + public im.conversations.android.xmpp.model.stanza.Message confirm(final Message message) { final boolean groupChat = message.getConversation().getMode() == Conversational.MODE_MULTI; final Jid to = message.getCounterpart(); - final MessagePacket packet = new MessagePacket(); - packet.setType(groupChat ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT); + final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(groupChat ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); packet.setTo(groupChat ? to.asBareJid() : to); final Element displayed = packet.addChild("displayed", "urn:xmpp:chat-markers:0"); if (groupChat) { @@ -168,18 +167,18 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket conferenceSubject(Conversation conversation, String subject) { - MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_GROUPCHAT); + public im.conversations.android.xmpp.model.stanza.Message conferenceSubject(Conversation conversation, String subject) { + im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT); packet.setTo(conversation.getJid().asBareJid()); packet.addChild("subject").setContent(subject); packet.setFrom(conversation.getAccount().getJid().asBareJid()); return packet; } - public MessagePacket directInvite(final Conversation conversation, final Jid contact) { - MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_NORMAL); + public im.conversations.android.xmpp.model.stanza.Message directInvite(final Conversation conversation, final Jid contact) { + im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.NORMAL); packet.setTo(contact); packet.setFrom(conversation.getAccount().getJid()); Element x = packet.addChild("x", "jabber:x:conference"); @@ -195,8 +194,8 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket invite(final Conversation conversation, final Jid contact) { - final MessagePacket packet = new MessagePacket(); + public im.conversations.android.xmpp.model.stanza.Message invite(final Conversation conversation, final Jid contact) { + final var packet = new im.conversations.android.xmpp.model.stanza.Message(); packet.setTo(conversation.getJid().asBareJid()); packet.setFrom(conversation.getAccount().getJid()); Element x = new Element("x"); @@ -208,8 +207,9 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket received(Account account, final Jid from, final String id, ArrayList namespaces, int type) { - final MessagePacket receivedPacket = new MessagePacket(); + public im.conversations.android.xmpp.model.stanza.Message received(Account account, final Jid from, final String id, ArrayList namespaces, im.conversations.android.xmpp.model.stanza.Message.Type type) { + final var receivedPacket = + new im.conversations.android.xmpp.model.stanza.Message(); receivedPacket.setType(type); receivedPacket.setTo(from); receivedPacket.setFrom(account.getJid()); @@ -220,8 +220,8 @@ public class MessageGenerator extends AbstractGenerator { return receivedPacket; } - public MessagePacket received(Account account, Jid to, String id) { - MessagePacket packet = new MessagePacket(); + public im.conversations.android.xmpp.model.stanza.Message received(Account account, Jid to, String id) { + im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); packet.setFrom(account.getJid()); packet.setTo(to); packet.addChild("received", "urn:xmpp:receipts").setAttribute("id", id); @@ -229,10 +229,10 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket sessionFinish( + public im.conversations.android.xmpp.model.stanza.Message sessionFinish( final Jid with, final String sessionId, final Reason reason) { - final MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); + final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); packet.setTo(with); final Element finish = packet.addChild("finish", Namespace.JINGLE_MESSAGE); finish.setAttribute("id", sessionId); @@ -242,9 +242,9 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) { - final MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those + public im.conversations.android.xmpp.model.stanza.Message sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) { + final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those packet.setTo(proposal.with); packet.setId(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX + proposal.sessionId); final Element propose = packet.addChild("propose", Namespace.JINGLE_MESSAGE); @@ -257,9 +257,9 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) { - final MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those + public im.conversations.android.xmpp.model.stanza.Message sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) { + final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those packet.setTo(proposal.with); final Element propose = packet.addChild("retract", Namespace.JINGLE_MESSAGE); propose.setAttribute("id", proposal.sessionId); @@ -268,9 +268,9 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket sessionReject(final Jid with, final String sessionId) { - final MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those + public im.conversations.android.xmpp.model.stanza.Message sessionReject(final Jid with, final String sessionId) { + final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message(); + packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those packet.setTo(with); final Element propose = packet.addChild("reject", Namespace.JINGLE_MESSAGE); propose.setAttribute("id", sessionId); diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java index 1485385bcac3f9618f4eec0cbc34aa59c987738c..7bb7341842997906b131073e91a010cad3777b0b 100644 --- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java @@ -9,7 +9,6 @@ import eu.siacs.conversations.entities.Presence; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.PresencePacket; public class PresenceGenerator extends AbstractGenerator { @@ -17,20 +16,20 @@ public class PresenceGenerator extends AbstractGenerator { super(service); } - private PresencePacket subscription(String type, Contact contact) { - PresencePacket packet = new PresencePacket(); + private im.conversations.android.xmpp.model.stanza.Presence subscription(String type, Contact contact) { + im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence(); packet.setAttribute("type", type); packet.setTo(contact.getJid()); packet.setFrom(contact.getAccount().getJid().asBareJid()); return packet; } - public PresencePacket requestPresenceUpdatesFrom(final Contact contact) { + public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom(final Contact contact) { return requestPresenceUpdatesFrom(contact, null); } - public PresencePacket requestPresenceUpdatesFrom(final Contact contact, final String preAuth) { - PresencePacket packet = subscription("subscribe", contact); + public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom(final Contact contact, final String preAuth) { + im.conversations.android.xmpp.model.stanza.Presence packet = subscription("subscribe", contact); String displayName = contact.getAccount().getDisplayName(); if (!TextUtils.isEmpty(displayName)) { packet.addChild("nick", Namespace.NICK).setContent(displayName); @@ -41,24 +40,24 @@ public class PresenceGenerator extends AbstractGenerator { return packet; } - public PresencePacket stopPresenceUpdatesFrom(Contact contact) { + public im.conversations.android.xmpp.model.stanza.Presence stopPresenceUpdatesFrom(Contact contact) { return subscription("unsubscribe", contact); } - public PresencePacket stopPresenceUpdatesTo(Contact contact) { + public im.conversations.android.xmpp.model.stanza.Presence stopPresenceUpdatesTo(Contact contact) { return subscription("unsubscribed", contact); } - public PresencePacket sendPresenceUpdatesTo(Contact contact) { + public im.conversations.android.xmpp.model.stanza.Presence sendPresenceUpdatesTo(Contact contact) { return subscription("subscribed", contact); } - public PresencePacket selfPresence(Account account, Presence.Status status) { + public im.conversations.android.xmpp.model.stanza.Presence selfPresence(Account account, Presence.Status status) { return selfPresence(account, status, true); } - public PresencePacket selfPresence(final Account account, final Presence.Status status, final boolean personal) { - final PresencePacket packet = new PresencePacket(); + public im.conversations.android.xmpp.model.stanza.Presence selfPresence(final Account account, final Presence.Status status, final boolean personal) { + final im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence(); if (personal) { final String sig = account.getPgpSignature(); final String message = account.getPresenceStatusMessage(); @@ -83,16 +82,16 @@ public class PresenceGenerator extends AbstractGenerator { return packet; } - public PresencePacket leave(final MucOptions mucOptions) { - PresencePacket presencePacket = new PresencePacket(); - presencePacket.setTo(mucOptions.getSelf().getFullJid()); - presencePacket.setFrom(mucOptions.getAccount().getJid()); - presencePacket.setAttribute("type", "unavailable"); - return presencePacket; + public im.conversations.android.xmpp.model.stanza.Presence leave(final MucOptions mucOptions) { + im.conversations.android.xmpp.model.stanza.Presence presence = new im.conversations.android.xmpp.model.stanza.Presence(); + presence.setTo(mucOptions.getSelf().getFullJid()); + presence.setFrom(mucOptions.getAccount().getJid()); + presence.setAttribute("type", "unavailable"); + return presence; } - public PresencePacket sendOfflinePresence(Account account) { - PresencePacket packet = new PresencePacket(); + public im.conversations.android.xmpp.model.stanza.Presence sendOfflinePresence(Account account) { + im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence(); packet.setFrom(account.getJid()); packet.setAttribute("type", "unavailable"); return packet; diff --git a/src/main/java/eu/siacs/conversations/http/SlotRequester.java b/src/main/java/eu/siacs/conversations/http/SlotRequester.java index 5a3558855c9f6c2746586e31056fb49699f2ce43..d76a99fda85eabf39864583d3c2fbc95f105da47 100644 --- a/src/main/java/eu/siacs/conversations/http/SlotRequester.java +++ b/src/main/java/eu/siacs/conversations/http/SlotRequester.java @@ -43,7 +43,7 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.IqResponseException; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import okhttp3.Headers; import okhttp3.HttpUrl; @@ -67,9 +67,9 @@ public class SlotRequester { private ListenableFuture requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) { final SettableFuture future = SettableFuture.create(); - final IqPacket request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime); - service.sendIqPacket(account, request, (a, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + final Iq request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime); + service.sendIqPacket(account, request, (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD_LEGACY); if (slotElement != null) { try { @@ -97,9 +97,9 @@ public class SlotRequester { private ListenableFuture requestHttpUpload(Account account, Jid host, DownloadableFile file, String mime) { final SettableFuture future = SettableFuture.create(); - final IqPacket request = service.getIqGenerator().requestHttpUploadSlot(host, file, mime); - service.sendIqPacket(account, request, (a, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + final Iq request = service.getIqGenerator().requestHttpUploadSlot(host, file, mime); + service.sendIqPacket(account, request, (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD); if (slotElement != null) { try { diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index 5de637399fb571bfaff80675cee50a2275cc8d60..2e2cb26843ff7ddc2402b958fe656155cc1fed37 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -16,14 +16,16 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; +import im.conversations.android.xmpp.model.stanza.Stanza; public abstract class AbstractParser { - protected XmppConnectionService mXmppConnectionService; + protected final XmppConnectionService mXmppConnectionService; + protected final Account account; - protected AbstractParser(XmppConnectionService service) { + protected AbstractParser(final XmppConnectionService service, final Account account) { this.mXmppConnectionService = service; + this.account = account; } public static Long parseTimestamp(Element element, Long d) { @@ -34,8 +36,8 @@ public abstract class AbstractParser { long min = Long.MAX_VALUE; boolean returnDefault = true; final Jid to; - if (ignoreCsiAndSm && element instanceof AbstractStanza) { - to = ((AbstractStanza) element).getTo(); + if (ignoreCsiAndSm && element instanceof Stanza stanza) { + to = stanza.getTo(); } else { to = null; } @@ -123,7 +125,7 @@ public abstract class AbstractParser { contact.setLastResource(from.isBareJid() ? "" : from.getResource()); } - protected String avatarData(Element items) { + protected static String avatarData(Element items) { Element item = items.findChild("item"); if (item == null) { return null; diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 0c08c557ebba9d8a052fad0cd33bb7079dcd8758..339c025c9ac160d6e2ee3e689d0f2cf62a582452 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -26,6 +26,7 @@ import java.util.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; @@ -37,18 +38,17 @@ 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.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.forms.Data; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; -public class IqParser extends AbstractParser implements OnIqPacketReceived { +public class IqParser extends AbstractParser implements Consumer { - public IqParser(final XmppConnectionService service) { - super(service); + public IqParser(final XmppConnectionService service, final Account account) { + super(service, account); } - public static List items(IqPacket packet) { + public static List items(final Iq packet) { ArrayList items = new ArrayList<>(); final Element query = packet.findChild("query", Namespace.DISCO_ITEMS); if (query == null) { @@ -65,7 +65,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { return items; } - public static Room parseRoom(IqPacket packet) { + public static Room parseRoom(Iq packet) { final Element query = packet.findChild("query", Namespace.DISCO_INFO); if (query == null) { return null; @@ -143,7 +143,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { mXmppConnectionService.syncRoster(account); } - public String avatarData(final IqPacket packet) { + public static String avatarData(final Iq packet) { final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB); if (pubsub == null) { return null; @@ -152,10 +152,10 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { if (items == null) { return null; } - return super.avatarData(items); + return AbstractParser.avatarData(items); } - public Element getItem(final IqPacket packet) { + public static Element getItem(final Iq packet) { final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB); if (pubsub == null) { return null; @@ -168,7 +168,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } @NonNull - public Set deviceIds(final Element item) { + public static Set deviceIds(final Element item) { Set deviceIds = new HashSet<>(); if (item != null) { final Element list = item.findChild("list"); @@ -189,7 +189,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { return deviceIds; } - private Integer signedPreKeyId(final Element bundle) { + private static Integer signedPreKeyId(final Element bundle) { final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); if (signedPreKeyPublic == null) { return null; @@ -201,7 +201,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } } - private ECPublicKey signedPreKeyPublic(final Element bundle) { + private static ECPublicKey signedPreKeyPublic(final Element bundle) { ECPublicKey publicKey = null; final String signedPreKeyPublic = bundle.findChildContent("signedPreKeyPublic"); if (signedPreKeyPublic == null) { @@ -215,7 +215,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { return publicKey; } - private byte[] signedPreKeySignature(final Element bundle) { + private static byte[] signedPreKeySignature(final Element bundle) { final String signedPreKeySignature = bundle.findChildContent("signedPreKeySignature"); if (signedPreKeySignature == null) { return null; @@ -228,7 +228,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } } - private IdentityKey identityKey(final Element bundle) { + private static IdentityKey identityKey(final Element bundle) { final String identityKey = bundle.findChildContent("identityKey"); if (identityKey == null) { return null; @@ -241,7 +241,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } } - public Map preKeyPublics(final IqPacket packet) { + public static Map preKeyPublics(final Iq packet) { Map preKeyRecords = new HashMap<>(); Element item = getItem(packet); if (item == null) { @@ -284,7 +284,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { return BaseEncoding.base64().decode(CharMatcher.whitespace().removeFrom(input)); } - public Pair verification(final IqPacket packet) { + public static Pair verification(final Iq packet) { Element item = getItem(packet); Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null; Element chain = verification != null ? verification.findChild("chain") : null; @@ -312,7 +312,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } } - public PreKeyBundle bundle(final IqPacket bundle) { + public static PreKeyBundle bundle(final Iq bundle) { final Element bundleItem = getItem(bundle); if (bundleItem == null) { return null; @@ -336,7 +336,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); } - public List preKeys(final IqPacket preKeys) { + public static List preKeys(final Iq preKeys) { List bundles = new ArrayList<>(); Map preKeyPublics = preKeyPublics(preKeys); if (preKeyPublics != null) { @@ -351,15 +351,15 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - final boolean isGet = packet.getType() == IqPacket.TYPE.GET; - if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) { + public void accept(final Iq packet) { + final boolean isGet = packet.getType() == Iq.Type.GET; + if (packet.getType() == Iq.Type.ERROR || packet.getType() == Iq.Type.TIMEOUT) { return; } if (packet.hasChild("query", Namespace.ROSTER) && packet.fromServer(account)) { final Element query = packet.findChild("query"); // If this is in response to a query for the whole roster: - if (packet.getType() == IqPacket.TYPE.RESULT) { + if (packet.getType() == Iq.Type.RESULT) { account.getRoster().markAllAsNotInRoster(); } this.rosterItems(account, query); @@ -373,7 +373,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { (block != null ? block.getChildren() : null); // If this is a response to a blocklist query, clear the block list and replace with the new one. // Otherwise, just update the existing blocklist. - if (packet.getType() == IqPacket.TYPE.RESULT) { + if (packet.getType() == Iq.Type.RESULT) { account.clearBlocklist(); account.getXmppConnection().getFeatures().setBlockListRequested(true); } @@ -389,7 +389,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } } account.getBlocklist().addAll(jids); - if (packet.getType() == IqPacket.TYPE.SET) { + if (packet.getType() == Iq.Type.SET) { boolean removed = false; for (Jid jid : jids) { removed |= mXmppConnectionService.removeBlockedConversations(account, jid); @@ -401,15 +401,15 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } // Update the UI mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); - if (packet.getType() == IqPacket.TYPE.SET) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); + if (packet.getType() == Iq.Type.SET) { + final Iq response = packet.generateResponse(Iq.Type.RESULT); mXmppConnectionService.sendIqPacket(account, response, null); } } else if (packet.hasChild("unblock", Namespace.BLOCKING) && - packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) { + packet.fromServer(account) && packet.getType() == Iq.Type.SET) { Log.d(Config.LOGTAG, "Received unblock update from server"); final Collection items = packet.findChild("unblock", Namespace.BLOCKING).getChildren(); - if (items.size() == 0) { + if (items.isEmpty()) { // No children to unblock == unblock all account.getBlocklist().clear(); } else { @@ -425,7 +425,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { account.getBlocklist().removeAll(jids); } mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); - final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); + final Iq response = packet.generateResponse(Iq.Type.RESULT); mXmppConnectionService.sendIqPacket(account, response, null); } else if (packet.hasChild("open", "http://jabber.org/protocol/ibb") || packet.hasChild("data", "http://jabber.org/protocol/ibb") @@ -433,18 +433,18 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { mXmppConnectionService.getJingleConnectionManager() .deliverIbbPacket(account, packet); } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { - final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet); + final Iq response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet); mXmppConnectionService.sendIqPacket(account, response, null); } else if (packet.hasChild("query", "jabber:iq:version") && isGet) { - final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet); + final Iq response = mXmppConnectionService.getIqGenerator().versionResponse(packet); mXmppConnectionService.sendIqPacket(account, response, null); } else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); + final Iq response = packet.generateResponse(Iq.Type.RESULT); mXmppConnectionService.sendIqPacket(account, response, null); } else if (packet.hasChild("time", "urn:xmpp:time") && isGet) { - final IqPacket response; + final Iq response; if (mXmppConnectionService.useTorToConnect() || account.isOnion()) { - response = packet.generateResponse(IqPacket.TYPE.ERROR); + response = packet.generateResponse(Iq.Type.ERROR); final Element error = response.addChild("error"); error.setAttribute("type", "cancel"); error.addChild("not-allowed", "urn:ietf:params:xml:ns:xmpp-stanzas"); @@ -452,18 +452,18 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet); } mXmppConnectionService.sendIqPacket(account, response, null); - } else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) && packet.getType() == IqPacket.TYPE.SET) { + } else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) && packet.getType() == Iq.Type.SET) { final Jid transport = packet.getFrom(); final Element push = packet.findChild("push", Namespace.UNIFIED_PUSH); final boolean success = push != null && mXmppConnectionService.processUnifiedPushMessage( account, transport, push); - final IqPacket response; + final Iq response; if (success) { - response = packet.generateResponse(IqPacket.TYPE.RESULT); + response = packet.generateResponse(Iq.Type.RESULT); } else { - response = packet.generateResponse(IqPacket.TYPE.ERROR); + response = packet.generateResponse(Iq.Type.ERROR); final Element error = response.addChild("error"); error.setAttribute("type", "cancel"); error.setAttribute("code", "404"); @@ -471,8 +471,8 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } mXmppConnectionService.sendIqPacket(account, response, null); } else { - if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); + if (packet.getType() == Iq.Type.GET || packet.getType() == Iq.Type.SET) { + final Iq response = packet.generateResponse(Iq.Type.ERROR); final Element error = response.addChild("error"); error.setAttribute("type", "cancel"); error.addChild("feature-not-implemented", "urn:ietf:params:xml:ns:xmpp-stanzas"); diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 011942c5037e6f2b1e6cafcca2c9bd70b904f189..c979ae1b6eb78b5d4a13eee3d600694d3865d938 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -15,6 +15,7 @@ import java.util.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; @@ -49,17 +50,20 @@ 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 eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.carbons.Received; +import im.conversations.android.xmpp.model.carbons.Sent; +import im.conversations.android.xmpp.model.forward.Forwarded; -public class MessageParser extends AbstractParser implements OnMessagePacketReceived { +public class MessageParser extends AbstractParser implements Consumer { private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH); private static final List JINGLE_MESSAGE_ELEMENT_NAMES = Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish"); - public MessageParser(XmppConnectionService service) { - super(service); + public MessageParser(final XmppConnectionService service, final Account account) { + super(service, account); } private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) { @@ -98,7 +102,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece return result != null ? result : fallback; } - private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) { + private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final im.conversations.android.xmpp.model.stanza.Message packet) { ChatState state = ChatState.parse(packet); if (state != null && c != null) { final Account account = c.getAccount(); @@ -240,7 +244,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) { Element item = items.findChild("item"); - Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + final Set deviceIds = IqParser.deviceIds(item); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... "); final AxolotlService axolotlService = account.getAxolotlService(); axolotlService.registerDevices(from, deviceIds); @@ -347,10 +351,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece mXmppConnectionService.updateAccountUi(); } - private boolean handleErrorMessage(final Account account, final MessagePacket packet) { - if (packet.getType() == MessagePacket.TYPE_ERROR) { + private boolean handleErrorMessage(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet) { + if (packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.ERROR) { if (packet.fromServer(account)) { - final Pair forwarded = packet.getForwardedMessagePacket("received", Namespace.CARBONS); + final var forwarded = getForwardedMessagePacket(packet,"received", Namespace.CARBONS); if (forwarded != null) { return handleErrorMessage(account, forwarded.first); } @@ -393,11 +397,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } @Override - public void onMessagePacketReceived(Account account, MessagePacket original) { + public void accept(final im.conversations.android.xmpp.model.stanza.Message original) { if (handleErrorMessage(account, original)) { return; } - final MessagePacket packet; + final im.conversations.android.xmpp.model.stanza.Message packet; Long timestamp = null; boolean isCarbon = false; String serverMsgId = null; @@ -411,7 +415,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece final MessageArchiveService.Query query = queryId == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(queryId); final boolean offlineMessagesRetrieved = account.getXmppConnection().isOfflineMessagesRetrieved(); if (query != null && query.validFrom(original.getFrom())) { - final Pair f = original.getForwardedMessagePacket("result", query.version.namespace); + final var f = getForwardedMessagePacket(original,"result", query.version.namespace); if (f == null) { return; } @@ -426,9 +430,9 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result with invalid from (" + original.getFrom() + ") or queryId (" + queryId + ")"); return; } else if (original.fromServer(account)) { - Pair f; - f = original.getForwardedMessagePacket("received", Namespace.CARBONS); - f = f == null ? original.getForwardedMessagePacket("sent", Namespace.CARBONS) : f; + Pair f; + f = getForwardedMessagePacket(original, Received.class); + f = f == null ? getForwardedMessagePacket(original, Sent.class) : f; packet = f != null ? f.first : original; if (handleErrorMessage(account, packet)) { return; @@ -468,7 +472,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece return; } - boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT; + boolean isTypeGroupChat = packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT; if (query != null && !query.muc() && isTypeGroupChat) { Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping"); return; @@ -1106,6 +1110,34 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } } + private static Pair getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, Class clazz) { + final var extension = original.getExtension(clazz); + final var forwarded = extension == null ? null : extension.getExtension(Forwarded.class); + if (forwarded == null) { + return null; + } + final Long timestamp = AbstractParser.parseTimestamp(forwarded, null); + final var forwardedMessage = forwarded.getMessage(); + if (forwardedMessage == null) { + return null; + } + return new Pair<>(forwardedMessage,timestamp); + } + + private static Pair getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, final String name, final String namespace) { + final Element wrapper = original.findChild(name, namespace); + final var forwardedElement = wrapper == null ? null : wrapper.findChild("forwarded",Namespace.FORWARD); + if (forwardedElement instanceof Forwarded forwarded) { + final Long timestamp = AbstractParser.parseTimestamp(forwarded, null); + final var forwardedMessage = forwarded.getMessage(); + if (forwardedMessage == null) { + return null; + } + return new Pair<>(forwardedMessage,timestamp); + } + return null; + } + private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query, final String id) { final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid()); if (conversation != null && (query == null || query.isCatchup())) { @@ -1118,7 +1150,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } } - private void processMessageReceipts(final Account account, final MessagePacket packet, final String remoteMsgId, MessageArchiveService.Query query) { + private void processMessageReceipts(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet, final String remoteMsgId, MessageArchiveService.Query query) { final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0"); final boolean request = packet.hasChild("request", "urn:xmpp:receipts"); if (query == null) { @@ -1130,7 +1162,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece receiptsNamespaces.add("urn:xmpp:receipts"); } if (receiptsNamespaces.size() > 0) { - final MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, + final var receipt = mXmppConnectionService.getMessageGenerator().received(account, packet.getFrom(), remoteMsgId, receiptsNamespaces, diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 584b8e704fe35417d26725e31bf24aeb2ac008a7..fc439ae0830d8c3555c08c60d35c6b4d7a5173cd 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -19,22 +19,21 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.OnPresencePacketReceived; import eu.siacs.conversations.xmpp.pep.Avatar; -import eu.siacs.conversations.xmpp.stanzas.PresencePacket; import org.openintents.openpgp.util.OpenPgpUtils; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; -public class PresenceParser extends AbstractParser implements OnPresencePacketReceived { +public class PresenceParser extends AbstractParser implements Consumer { - public PresenceParser(XmppConnectionService service) { - super(service); + public PresenceParser(final XmppConnectionService service, final Account account) { + super(service, account); } - public void parseConferencePresence(PresencePacket packet, Account account) { + public void parseConferencePresence(final im.conversations.android.xmpp.model.stanza.Presence packet, Account account) { final Conversation conversation = packet.getFrom() == null ? null @@ -58,7 +57,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe } } - private void processConferencePresence(PresencePacket packet, Conversation conversation) { + private void processConferencePresence(final im.conversations.android.xmpp.model.stanza.Presence packet, Conversation conversation) { final Account account = conversation.getAccount(); final MucOptions mucOptions = conversation.getMucOptions(); final Jid jid = conversation.getAccount().getJid(); @@ -297,7 +296,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe return codes; } - private void parseContactPresence(final PresencePacket packet, final Account account) { + private void parseContactPresence(final im.conversations.android.xmpp.model.stanza.Presence packet, final Account account) { final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator(); final Jid from = packet.getFrom(); if (from == null || from.equals(account.getJid())) { @@ -431,7 +430,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe } @Override - public void onPresencePacketReceived(Account account, PresencePacket packet) { + public void accept(final im.conversations.android.xmpp.model.stanza.Presence packet) { if (packet.hasChild("x", Namespace.MUC_USER)) { this.parseConferencePresence(packet, account); } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) { diff --git a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java index 3ca6bfde4cfecd9f76eebb2e6d13f055c7b7f418..b9d43fa51523e3c8fd74aec716a6325b8063a170 100644 --- a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java +++ b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java @@ -20,10 +20,9 @@ import eu.siacs.conversations.http.services.MuclumbusService; import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.utils.TLSSocketFactory; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.XmppConnection; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import okhttp3.OkHttpClient; import okhttp3.ResponseBody; @@ -202,7 +201,7 @@ public class ChannelDiscoveryService { final String query, final OnChannelSearchResultsFound listener) { final Map localMucService = getLocalMucServices(); Log.d(Config.LOGTAG, "checking with " + localMucService.size() + " muc services"); - if (localMucService.size() == 0) { + if (localMucService.isEmpty()) { listener.onChannelSearchResultsFound(Collections.emptyList()); return; } @@ -216,39 +215,36 @@ public class ChannelDiscoveryService { } final AtomicInteger queriesInFlight = new AtomicInteger(); final List rooms = new ArrayList<>(); - for (Map.Entry entry : localMucService.entrySet()) { - IqPacket itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey()); + for (final Map.Entry entry : localMucService.entrySet()) { + Iq itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey()); queriesInFlight.incrementAndGet(); + final var account = entry.getValue(); service.sendIqPacket( - entry.getValue(), + account, itemsRequest, - (account, itemsResponse) -> { - if (itemsResponse.getType() == IqPacket.TYPE.RESULT) { + (itemsResponse) -> { + if (itemsResponse.getType() == Iq.Type.RESULT) { final List items = IqParser.items(itemsResponse); - for (Jid item : items) { - IqPacket infoRequest = + for (final Jid item : items) { + final Iq infoRequest = service.getIqGenerator().queryDiscoInfo(item); queriesInFlight.incrementAndGet(); service.sendIqPacket( account, infoRequest, - new OnIqPacketReceived() { - @Override - public void onIqPacketReceived( - Account account, IqPacket infoResponse) { - if (infoResponse.getType() - == IqPacket.TYPE.RESULT) { - final Room room = - IqParser.parseRoom(infoResponse); - if (room != null) { - rooms.add(room); - } - if (queriesInFlight.decrementAndGet() <= 0) { - finishDiscoSearch(rooms, query, listener); - } - } else { - queriesInFlight.decrementAndGet(); + infoResponse -> { + if (infoResponse.getType() + == Iq.Type.RESULT) { + final Room room = + IqParser.parseRoom(infoResponse); + if (room != null) { + rooms.add(room); } + if (queriesInFlight.decrementAndGet() <= 0) { + finishDiscoSearch(rooms, query, listener); + } + } else { + queriesInFlight.decrementAndGet(); } }); } diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index 126993501271a8ac63ab8e9a6843f0820a04667f..2c342e6728a985b12a1bb7d28b8a41ec63e132cc 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -23,8 +23,8 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.mam.MamReference; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.stanza.Message; public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { @@ -81,7 +81,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { return false; } - public static Element findResult(MessagePacket packet) { + public static Element findResult(Message packet) { for (Version version : values()) { Element result = packet.findChild("result", version.namespace); if (result != null) { @@ -234,17 +234,17 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { throw new IllegalStateException("Attempted to run MAM query for archived conversation"); } Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": running mam query " + query.toString()); - final IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); - this.mXmppConnectionService.sendIqPacket(account, packet, (a, p) -> { + final Iq packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); + this.mXmppConnectionService.sendIqPacket(account, packet, (p) -> { final Element fin = p.findChild("fin", query.version.namespace); - if (p.getType() == IqPacket.TYPE.TIMEOUT) { + if (p.getType() == Iq.Type.TIMEOUT) { synchronized (this.queries) { this.queries.remove(query); if (query.hasCallback()) { query.callback(false); } } - } else if (p.getType() == IqPacket.TYPE.RESULT && fin != null) { + } else if (p.getType() == Iq.Type.RESULT && fin != null) { final boolean running; synchronized (this.queries) { running = this.queries.contains(query); @@ -254,10 +254,10 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring MAM iq result because query had been killed"); } - } else if (p.getType() == IqPacket.TYPE.RESULT && query.isLegacy()) { + } else if (p.getType() == Iq.Type.RESULT && query.isLegacy()) { //do nothing } else { - Log.d(Config.LOGTAG, a.getJid().asBareJid().toString() + ": error executing mam: " + p.toString()); + Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": error executing mam: " + p.toString()); try { finalizeQuery(query, true); } catch (final IllegalStateException e) { @@ -304,7 +304,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } } - boolean inCatchup(Account account) { + public boolean inCatchup(Account account) { synchronized (this.queries) { for (Query query : queries) { if (query.account == account && query.isCatchup() && query.getWith() == null) { diff --git a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java index 4aab05ceeb00ddefc6d366c1dfd3e94ac4e7036b..92ae9d9ec668533d097680e333ac58bb4bc33248 100644 --- a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java +++ b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java @@ -32,8 +32,9 @@ import eu.siacs.conversations.receiver.UnifiedPushDistributor; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.PresencePacket; +import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.stanza.Presence; + import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.List; @@ -82,7 +83,7 @@ public class UnifiedPushBroker { } private void sendDirectedPresence(final Account account, Jid to) { - final PresencePacket presence = new PresencePacket(); + final var presence = new Presence(); presence.setTo(to); service.sendPresencePacket(account, presence); } @@ -146,7 +147,7 @@ public class UnifiedPushBroker { UnifiedPushDistributor.hash(account.getUuid(), renewal.application); final String hashedInstance = UnifiedPushDistributor.hash(account.getUuid(), renewal.instance); - final IqPacket registration = new IqPacket(IqPacket.TYPE.SET); + final Iq registration = new Iq(Iq.Type.SET); registration.setTo(transport.transport); final Element register = registration.addChild("register", Namespace.UNIFIED_PUSH); register.setAttribute("application", hashedApplication); @@ -160,7 +161,7 @@ public class UnifiedPushBroker { this.service.sendIqPacket( account, registration, - (a, response) -> processRegistration(transport, renewal, messenger, response)); + (response) -> processRegistration(transport, renewal, messenger, response)); } } @@ -168,8 +169,8 @@ public class UnifiedPushBroker { final Transport transport, final UnifiedPushDatabase.PushTarget renewal, final Messenger messenger, - final IqPacket response) { - if (response.getType() == IqPacket.TYPE.RESULT) { + final Iq response) { + if (response.getType() == Iq.Type.RESULT) { final Element registered = response.findChild("registered", Namespace.UNIFIED_PUSH); if (registered == null) { return; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 80b4d6ddde600f4e4bc3b2a757921a77567ca0c5..0cf6fbe248756554170a15b4ad7d37facf659ebe 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -52,7 +52,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.RemoteInput; import androidx.core.content.ContextCompat; -import androidx.core.util.Consumer; import com.google.common.base.Objects; import com.google.common.base.Optional; @@ -92,6 +91,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; @@ -123,8 +123,6 @@ import eu.siacs.conversations.generator.PresenceGenerator; import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.parser.AbstractParser; import eu.siacs.conversations.parser.IqParser; -import eu.siacs.conversations.parser.MessageParser; -import eu.siacs.conversations.parser.PresenceParser; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.UnifiedPushDatabase; @@ -141,7 +139,6 @@ import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.ConversationsFileObserver; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.EasyOnboardingInvite; -import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.utils.QuickLoader; @@ -160,11 +157,8 @@ import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnBindListener; import eu.siacs.conversations.xmpp.OnContactStatusChanged; -import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnMessageAcknowledged; -import eu.siacs.conversations.xmpp.OnMessagePacketReceived; -import eu.siacs.conversations.xmpp.OnPresencePacketReceived; import eu.siacs.conversations.xmpp.OnStatusChanged; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.XmppConnection; @@ -178,9 +172,7 @@ import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; import eu.siacs.conversations.xmpp.mam.MamReference; import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.pep.PublishOptions; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; -import eu.siacs.conversations.xmpp.stanzas.PresencePacket; +import im.conversations.android.xmpp.model.stanza.Iq; import me.leolin.shortcutbadger.ShortcutBadger; public class XmppConnectionService extends Service { @@ -225,12 +217,12 @@ public class XmppConnectionService extends Service { private final Set mInProgressAvatarFetches = new HashSet<>(); private final Set mOmittedPepAvatarFetches = new HashSet<>(); private final HashSet mLowPingTimeoutMode = new HashSet<>(); - private final OnIqPacketReceived mDefaultIqHandler = (account, packet) -> { - if (packet.getType() != IqPacket.TYPE.RESULT) { - Element error = packet.findChild("error"); + private final Consumer mDefaultIqHandler = (packet) -> { + if (packet.getType() != Iq.Type.RESULT) { + final var error = packet.getError(); String text = error != null ? error.findChildContent("text") : null; if (text != null) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received iq error - " + text); + Log.d(Config.LOGTAG, "received iq error: " + text); } } }; @@ -247,9 +239,6 @@ public class XmppConnectionService extends Service { private final AtomicBoolean mOngoingVideoTranscoding = new AtomicBoolean(false); private final AtomicBoolean mForceDuringOnCreate = new AtomicBoolean(false); private final AtomicReference ongoingCall = new AtomicReference<>(); - private final OnMessagePacketReceived mMessageParser = new MessageParser(this); - private final OnPresencePacketReceived mPresenceParser = new PresenceParser(this); - private final IqParser mIqParser = new IqParser(this); private final MessageGenerator mMessageGenerator = new MessageGenerator(this); public OnContactStatusChanged onContactStatusChanged = (contact, online) -> { Conversation conversation = find(getConversations(), contact); @@ -330,79 +319,6 @@ public class XmppConnectionService extends Service { public final Set FILENAMES_TO_IGNORE_DELETION = new HashSet<>(); - private final OnBindListener mOnBindListener = new OnBindListener() { - - @Override - public void onBind(final Account account) { - synchronized (mInProgressAvatarFetches) { - for (Iterator iterator = mInProgressAvatarFetches.iterator(); iterator.hasNext(); ) { - final String KEY = iterator.next(); - if (KEY.startsWith(account.getJid().asBareJid() + "_")) { - iterator.remove(); - } - } - } - boolean loggedInSuccessfully = account.setOption(Account.OPTION_LOGGED_IN_SUCCESSFULLY, true); - boolean gainedFeature = account.setOption(Account.OPTION_HTTP_UPLOAD_AVAILABLE, account.getXmppConnection().getFeatures().httpUpload(0)); - if (loggedInSuccessfully || gainedFeature) { - databaseBackend.updateAccount(account); - } - - if (loggedInSuccessfully) { - if (!TextUtils.isEmpty(account.getDisplayName())) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": display name wasn't empty on first log in. publishing"); - publishDisplayName(account); - } - } - - account.getRoster().clearPresences(); - synchronized (account.inProgressConferenceJoins) { - account.inProgressConferenceJoins.clear(); - } - synchronized (account.inProgressConferencePings) { - account.inProgressConferencePings.clear(); - } - mJingleConnectionManager.notifyRebound(account); - mQuickConversationsService.considerSyncBackground(false); - fetchRosterFromServer(account); - - final XmppConnection connection = account.getXmppConnection(); - - if (connection.getFeatures().bookmarks2()) { - fetchBookmarks2(account); - } else if (!account.getXmppConnection().getFeatures().bookmarksConversion()) { - fetchBookmarks(account); - } - - if (connection.getFeatures().mds()) { - fetchMessageDisplayedSynchronization(account); - } else { - Log.d(Config.LOGTAG,account.getJid()+": server has no support for mds"); - } - final boolean flexible = account.getXmppConnection().getFeatures().flexibleOfflineMessageRetrieval(); - final boolean catchup = getMessageArchiveService().inCatchup(account); - final boolean trackOfflineMessageRetrieval; - if (flexible && catchup && account.getXmppConnection().isMamPreferenceAlways()) { - trackOfflineMessageRetrieval = false; - sendIqPacket(account, mIqGenerator.purgeOfflineMessages(), (acc, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG, acc.getJid().asBareJid() + ": successfully purged offline messages"); - } - }); - } else { - trackOfflineMessageRetrieval = true; - } - sendPresence(account); - account.getXmppConnection().trackOfflineMessageRetrieval(trackOfflineMessageRetrieval); - if (mPushManagementService.available(account)) { - mPushManagementService.registerPushTokenOnServer(account); - } - connectMultiModeConversations(account); - syncDirtyContacts(account); - - unifiedPushBroker.renewUnifiedPushEndpointsOnBind(account); - } - }; private final AtomicLong mLastExpiryRun = new AtomicLong(0); private final LruCache, ServiceDiscoveryResult> discoCache = new LruCache<>(20); @@ -1636,12 +1552,8 @@ public class XmppConnectionService extends Service { public XmppConnection createConnection(final Account account) { final XmppConnection connection = new XmppConnection(account, this); - connection.setOnMessagePacketReceivedListener(this.mMessageParser); connection.setOnStatusChangedListener(this.statusListener); - connection.setOnPresencePacketReceivedListener(this.mPresenceParser); - connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser); connection.setOnJinglePacketReceivedListener((mJingleConnectionManager::deliverPacket)); - connection.setOnBindListener(this.mOnBindListener); connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService); connection.addOnAdvancedStreamFeaturesAvailableListener(this.mAvatarService); @@ -1654,7 +1566,7 @@ public class XmppConnectionService extends Service { public void sendChatState(Conversation conversation) { if (sendChatStates()) { - MessagePacket packet = mMessageGenerator.generateChatState(conversation); + final var packet = mMessageGenerator.generateChatState(conversation); sendMessagePacket(conversation.getAccount(), packet); } } @@ -1692,7 +1604,7 @@ public class XmppConnectionService extends Service { } } - MessagePacket packet = null; + im.conversations.android.xmpp.model.stanza.Message packet = null; final boolean addToConversation = !message.edited(); boolean saveInDb = addToConversation; message.setStatus(Message.STATUS_WAITING); @@ -1866,13 +1778,13 @@ public class XmppConnectionService extends Service { callback.inviteRequestFailed(getString(R.string.server_does_not_support_easy_onboarding_invites)); return; } - final IqPacket request = new IqPacket(IqPacket.TYPE.SET); + final Iq request = new Iq(Iq.Type.SET); request.setTo(jid); final Element command = request.addChild("command", Namespace.COMMANDS); command.setAttribute("node", Namespace.EASY_ONBOARDING_INVITE); command.setAttribute("action", "execute"); - sendIqPacket(account, request, (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + sendIqPacket(account, request, (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element resultCommand = response.findChild("command", Namespace.COMMANDS); final Element x = resultCommand == null ? null : resultCommand.findChild("x", Namespace.DATA); if (x != null) { @@ -1887,7 +1799,7 @@ public class XmppConnectionService extends Service { } callback.inviteRequestFailed(getString(R.string.unable_to_parse_invite)); Log.d(Config.LOGTAG, response.toString()); - } else if (response.getType() == IqPacket.TYPE.ERROR) { + } else if (response.getType() == Iq.Type.ERROR) { callback.inviteRequestFailed(IqParser.errorMessage(response)); } else { callback.inviteRequestFailed(getString(R.string.remote_server_timeout)); @@ -1896,54 +1808,42 @@ public class XmppConnectionService extends Service { } - public void fetchRosterFromServer(final Account account) { - final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET); - if (!"".equals(account.getRosterVersion())) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() - + ": fetching roster version " + account.getRosterVersion()); - } else { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching roster"); - } - iqPacket.query(Namespace.ROSTER).setAttribute("ver", account.getRosterVersion()); - sendIqPacket(account, iqPacket, mIqParser); - } - public void fetchBookmarks(final Account account) { - final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET); + final Iq iqPacket = new Iq(Iq.Type.GET); final Element query = iqPacket.query("jabber:iq:private"); query.addChild("storage", Namespace.BOOKMARKS); - final OnIqPacketReceived callback = (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + final Consumer callback = (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element query1 = response.query(); final Element storage = query1.findChild("storage", "storage:bookmarks"); Map bookmarks = Bookmark.parseFromStorage(storage, account); - processBookmarksInitial(a, bookmarks, false); + processBookmarksInitial(account, bookmarks, false); } else { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": could not fetch bookmarks"); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not fetch bookmarks"); } }; sendIqPacket(account, iqPacket, callback); } public void fetchBookmarks2(final Account account) { - final IqPacket retrieve = mIqGenerator.retrieveBookmarks(); - sendIqPacket(account, retrieve, (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + final Iq retrieve = mIqGenerator.retrieveBookmarks(); + sendIqPacket(account, retrieve, (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element pubsub = response.findChild("pubsub", Namespace.PUBSUB); - final Map bookmarks = Bookmark.parseFromPubsub(pubsub, a); - processBookmarksInitial(a, bookmarks, true); + final Map bookmarks = Bookmark.parseFromPubsub(pubsub, account); + processBookmarksInitial(account, bookmarks, true); } }); } - private void fetchMessageDisplayedSynchronization(final Account account) { + public void fetchMessageDisplayedSynchronization(final Account account) { Log.d(Config.LOGTAG, account.getJid() + ": retrieve mds"); final var retrieve = mIqGenerator.retrieveMds(); sendIqPacket( account, retrieve, - (a, response) -> { - if (response.getType() != IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() != Iq.Type.RESULT) { return; } final var pubSub = response.findChild("pubsub", Namespace.PUBSUB); @@ -2096,11 +1996,11 @@ public class XmppConnectionService extends Service { account.removeBookmark(bookmark); final XmppConnection connection = account.getXmppConnection(); if (connection.getFeatures().bookmarks2()) { - final IqPacket request = mIqGenerator.deleteItem(Namespace.BOOKMARKS2, bookmark.getJid().asBareJid().toEscapedString()); + final Iq request = mIqGenerator.deleteItem(Namespace.BOOKMARKS2, bookmark.getJid().asBareJid().toEscapedString()); Log.d(Config.LOGTAG,account.getJid().asBareJid() + ": removing bookmark via Bookmarks 2"); - sendIqPacket(account, request, (a, response) -> { - if (response.getType() == IqPacket.TYPE.ERROR) { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": unable to delete bookmark " + response.getErrorCondition()); + sendIqPacket(account, request, (response) -> { + if (response.getType() == Iq.Type.ERROR) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to delete bookmark " + response.getErrorCondition()); } }); } else if (connection.getFeatures().bookmarksConversion()) { @@ -2112,7 +2012,7 @@ public class XmppConnectionService extends Service { private void pushBookmarksPrivateXml(Account account) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": pushing bookmarks via private xml"); - IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET); + final Iq iqPacket = new Iq(Iq.Type.SET); Element query = iqPacket.query("jabber:iq:private"); Element storage = query.addChild("storage", "storage:bookmarks"); for (final Bookmark bookmark : account.getBookmarks()) { @@ -2137,9 +2037,9 @@ public class XmppConnectionService extends Service { } private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final String id, final Bundle options, final boolean retry) { - final IqPacket packet = mIqGenerator.publishElement(node, element, id, options); - sendIqPacket(account, packet, (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + final Iq packet = mIqGenerator.publishElement(node, element, id, options); + sendIqPacket(account, packet, (response) -> { + if (response.getType() == Iq.Type.RESULT) { return; } if (retry && PublishOptions.preconditionNotMet(response)) { @@ -2610,6 +2510,10 @@ public class XmppConnectionService extends Service { return this.unifiedPushBroker.renewUnifiedPushEndpoints(null); } + public UnifiedPushBroker getUnifiedPushBroker() { + return this.unifiedPushBroker; + } + private void provisionAccount(final String address, final String password) { final Jid jid = Jid.ofEscaped(address); final Account account = new Account(jid, password); @@ -2708,12 +2612,12 @@ public class XmppConnectionService extends Service { } public void updateAccountPasswordOnServer(final Account account, final String newPassword, final OnAccountPasswordChanged callback) { - final IqPacket iq = getIqGenerator().generateSetPassword(account, newPassword); - sendIqPacket(account, iq, (a, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { - a.setPassword(newPassword); - a.setOption(Account.OPTION_MAGIC_CREATE, false); - databaseBackend.updateAccount(a); + final Iq iq = getIqGenerator().generateSetPassword(account, newPassword); + sendIqPacket(account, iq, (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { + account.setPassword(newPassword); + account.setOption(Account.OPTION_MAGIC_CREATE, false); + databaseBackend.updateAccount(account); callback.onPasswordChangeSucceeded(); } else { callback.onPasswordChangeFailed(); @@ -2722,12 +2626,12 @@ public class XmppConnectionService extends Service { } public void unregisterAccount(final Account account, final Consumer callback) { - final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET); + final Iq iqPacket = new Iq(Iq.Type.SET); final Element query = iqPacket.addChild("query",Namespace.REGISTER); query.addChild("remove"); - sendIqPacket(account, iqPacket, (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { - deleteAccount(a); + sendIqPacket(account, iqPacket, (response) -> { + if (response.getType() == Iq.Type.RESULT) { + deleteAccount(account); callback.accept(true); } else { callback.accept(false); @@ -3055,7 +2959,7 @@ public class XmppConnectionService extends Service { Log.d(Config.LOGTAG, "app switched into background"); } - private void connectMultiModeConversations(Account account) { + public void connectMultiModeConversations(Account account) { List conversations = getConversations(); for (Conversation conversation : conversations) { if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getAccount() == account) { @@ -3079,20 +2983,20 @@ public class XmppConnectionService extends Service { } } final Jid self = conversation.getMucOptions().getSelf().getFullJid(); - final IqPacket ping = new IqPacket(IqPacket.TYPE.GET); + final Iq ping = new Iq(Iq.Type.GET); ping.setTo(self); ping.addChild("ping", Namespace.PING); - sendIqPacket(conversation.getAccount(), ping, (a, response) -> { - if (response.getType() == IqPacket.TYPE.ERROR) { - Element error = response.findChild("error"); + sendIqPacket(conversation.getAccount(), ping, (response) -> { + if (response.getType() == Iq.Type.ERROR) { + final var error = response.getError(); if (error == null || error.hasChild("service-unavailable") || error.hasChild("feature-not-implemented") || error.hasChild("item-not-found")) { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": ping to " + self + " came back as ignorable error"); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ping to " + self + " came back as ignorable error"); } else { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": ping to " + self + " failed. attempting rejoin"); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ping to " + self + " failed. attempting rejoin"); joinMuc(conversation); } - } else if (response.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": ping to " + self + " came back fine"); + } else if (response.getType() == Iq.Type.RESULT) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ping to " + self + " came back fine"); } synchronized (account.inProgressConferencePings) { account.inProgressConferencePings.remove(conversation); @@ -3151,7 +3055,7 @@ public class XmppConnectionService extends Service { final Jid joinJid = mucOptions.getSelf().getFullJid(); Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": joining conversation " + joinJid.toString()); - PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous() || onConferenceJoined != null); + final var packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous() || onConferenceJoined != null); packet.setTo(joinJid); Element x = packet.addChild("x", "http://jabber.org/protocol/muc"); if (conversation.getMucOptions().getPassword() != null) { @@ -3240,16 +3144,16 @@ public class XmppConnectionService extends Service { final Account account = conversation.getAccount(); final AxolotlService axolotlService = account.getAxolotlService(); final String[] affiliations = {"member", "admin", "owner"}; - OnIqPacketReceived callback = new OnIqPacketReceived() { + final Consumer callback = new Consumer() { private int i = 0; private boolean success = true; @Override - public void onIqPacketReceived(Account account, IqPacket packet) { + public void accept(Iq response) { final boolean omemoEnabled = conversation.getNextEncryption() == Message.ENCRYPTION_AXOLOTL; - Element query = packet.query("http://jabber.org/protocol/muc#admin"); - if (packet.getType() == IqPacket.TYPE.RESULT && query != null) { + Element query = response.query("http://jabber.org/protocol/muc#admin"); + if (response.getType() == Iq.Type.RESULT && query != null) { for (Element child : query.getChildren()) { if ("item".equals(child.getName())) { MucOptions.User user = AbstractParser.parseItem(conversation, child); @@ -3335,29 +3239,29 @@ public class XmppConnectionService extends Service { } private void deletePepNode(final Account account, final String node, final Runnable runnable) { - final IqPacket request = mIqGenerator.deleteNode(node); - sendIqPacket(account, request, (a, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG,a.getJid().asBareJid()+": successfully deleted pep node "+node); + final Iq request = mIqGenerator.deleteNode(node); + sendIqPacket(account, request, (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": successfully deleted pep node "+node); if (runnable != null) { runnable.run(); } } else { - Log.d(Config.LOGTAG,a.getJid().asBareJid()+": failed to delete "+ packet); + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": failed to delete "+ packet); } }); } private void deleteVcardAvatar(final Account account, @NonNull final Runnable runnable) { - final IqPacket retrieveVcard = mIqGenerator.retrieveVcardAvatar(account.getJid().asBareJid()); - sendIqPacket(account, retrieveVcard, (a, response) -> { - if (response.getType() != IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG,a.getJid().asBareJid()+": no vCard set. nothing to do"); + final Iq retrieveVcard = mIqGenerator.retrieveVcardAvatar(account.getJid().asBareJid()); + sendIqPacket(account, retrieveVcard, (response) -> { + if (response.getType() != Iq.Type.RESULT) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": no vCard set. nothing to do"); return; } final Element vcard = response.findChild("vCard", "vcard-temp"); if (vcard == null) { - Log.d(Config.LOGTAG,a.getJid().asBareJid()+": no vCard set. nothing to do"); + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": no vCard set. nothing to do"); return; } Element photo = vcard.findChild("PHOTO"); @@ -3365,12 +3269,12 @@ public class XmppConnectionService extends Service { photo = vcard.addChild("PHOTO"); } photo.clearChildren(); - IqPacket publication = new IqPacket(IqPacket.TYPE.SET); - publication.setTo(a.getJid().asBareJid()); + final Iq publication = new Iq(Iq.Type.SET); + publication.setTo(account.getJid().asBareJid()); publication.addChild(vcard); - sendIqPacket(account, publication, (a1, publicationResponse) -> { - if (publicationResponse.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG,a1.getJid().asBareJid()+": successfully deleted vcard avatar"); + sendIqPacket(account, publication, (publicationResponse) -> { + if (publicationResponse.getType() == Iq.Type.RESULT) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": successfully deleted vcard avatar"); runnable.run(); } else { Log.d(Config.LOGTAG, "failed to publish vcard " + publicationResponse.getErrorCondition()); @@ -3450,7 +3354,7 @@ public class XmppConnectionService extends Service { } }); - final PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, options.nonanonymous()); + final var packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, options.nonanonymous()); packet.setTo(joinJid); sendPresencePacket(account, packet); } else { @@ -3610,39 +3514,37 @@ public class XmppConnectionService extends Service { } public void fetchConferenceConfiguration(final Conversation conversation, final OnConferenceConfigurationFetched callback) { - IqPacket request = mIqGenerator.queryDiscoInfo(conversation.getJid().asBareJid()); - sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - final MucOptions mucOptions = conversation.getMucOptions(); - final Bookmark bookmark = conversation.getBookmark(); - final boolean sameBefore = StringUtils.equals(bookmark == null ? null : bookmark.getBookmarkName(), mucOptions.getName()); + final Iq request = mIqGenerator.queryDiscoInfo(conversation.getJid().asBareJid()); + final var account = conversation.getAccount(); + sendIqPacket(account, request, response -> { + if (response.getType() == Iq.Type.RESULT) { + final MucOptions mucOptions = conversation.getMucOptions(); + final Bookmark bookmark = conversation.getBookmark(); + final boolean sameBefore = StringUtils.equals(bookmark == null ? null : bookmark.getBookmarkName(), mucOptions.getName()); - if (mucOptions.updateConfiguration(new ServiceDiscoveryResult(packet))) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": muc configuration changed for " + conversation.getJid().asBareJid()); - updateConversation(conversation); - } + if (mucOptions.updateConfiguration(new ServiceDiscoveryResult(response))) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": muc configuration changed for " + conversation.getJid().asBareJid()); + updateConversation(conversation); + } - if (bookmark != null && (sameBefore || bookmark.getBookmarkName() == null)) { - if (bookmark.setBookmarkName(StringUtils.nullOnEmpty(mucOptions.getName()))) { - createBookmark(account, bookmark); - } + if (bookmark != null && (sameBefore || bookmark.getBookmarkName() == null)) { + if (bookmark.setBookmarkName(StringUtils.nullOnEmpty(mucOptions.getName()))) { + createBookmark(account, bookmark); } + } - if (callback != null) { - callback.onConferenceConfigurationFetched(conversation); - } + if (callback != null) { + callback.onConferenceConfigurationFetched(conversation); + } - updateConversationUi(); - } else if (packet.getType() == IqPacket.TYPE.TIMEOUT) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received timeout waiting for conference configuration fetch"); - } else { - if (callback != null) { - callback.onFetchFailed(conversation, packet.getErrorCondition()); - } + updateConversationUi(); + } else if (response.getType() == Iq.Type.TIMEOUT) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received timeout waiting for conference configuration fetch"); + } else { + if (callback != null) { + callback.onFetchFailed(conversation, response.getErrorCondition()); } } }); @@ -3654,33 +3556,27 @@ public class XmppConnectionService extends Service { public void pushNodeConfiguration(Account account, final Jid jid, final String node, final Bundle options, final OnConfigurationPushed callback) { Log.d(Config.LOGTAG, "pushing node configuration"); - sendIqPacket(account, mIqGenerator.requestPubsubConfiguration(jid, node), new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub#owner"); - Element configuration = pubsub == null ? null : pubsub.findChild("configure"); - Element x = configuration == null ? null : configuration.findChild("x", Namespace.DATA); - if (x != null) { - final Data data = Data.parse(x); - data.submit(options); - sendIqPacket(account, mIqGenerator.publishPubsubConfiguration(jid, node, data), new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT && callback != null) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully changed node configuration for node " + node); - callback.onPushSucceeded(); - } else if (packet.getType() == IqPacket.TYPE.ERROR && callback != null) { - callback.onPushFailed(); - } - } - }); - } else if (callback != null) { - callback.onPushFailed(); - } - } else if (packet.getType() == IqPacket.TYPE.ERROR && callback != null) { + sendIqPacket(account, mIqGenerator.requestPubsubConfiguration(jid, node), responseToRequest -> { + if (responseToRequest.getType() == Iq.Type.RESULT) { + Element pubsub = responseToRequest.findChild("pubsub", "http://jabber.org/protocol/pubsub#owner"); + Element configuration = pubsub == null ? null : pubsub.findChild("configure"); + Element x = configuration == null ? null : configuration.findChild("x", Namespace.DATA); + if (x != null) { + final Data data = Data.parse(x); + data.submit(options); + sendIqPacket(account, mIqGenerator.publishPubsubConfiguration(jid, node, data), responseToPublish -> { + if (responseToPublish.getType() == Iq.Type.RESULT && callback != null) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully changed node configuration for node " + node); + callback.onPushSucceeded(); + } else if (responseToPublish.getType() == Iq.Type.ERROR && callback != null) { + callback.onPushFailed(); + } + }); + } else if (callback != null) { callback.onPushFailed(); } + } else if (responseToRequest.getType() == Iq.Type.ERROR && callback != null) { + callback.onPushFailed(); } }); } @@ -3700,50 +3596,45 @@ public class XmppConnectionService extends Service { options.putString("allow_private_messages", allow ? "1" : "0"); options.putString("allow_private_messages_from_visitors", allow ? "anyone" : "nobody"); } - final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final var account = conversation.getAccount(); + final Iq request = new Iq(Iq.Type.GET); request.setTo(conversation.getJid().asBareJid()); request.query("http://jabber.org/protocol/muc#owner"); - sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - final Data data = Data.parse(packet.query().findChild("x", Namespace.DATA)); - data.submit(options); - final IqPacket set = new IqPacket(IqPacket.TYPE.SET); - set.setTo(conversation.getJid().asBareJid()); - set.query("http://jabber.org/protocol/muc#owner").addChild(data); - sendIqPacket(account, set, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (callback != null) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - callback.onPushSucceeded(); - } else { - Log.d(Config.LOGTAG,"failed: "+packet.toString()); - callback.onPushFailed(); - } - } - } - }); - } else { + sendIqPacket(account, request, response -> { + if (response.getType() == Iq.Type.RESULT) { + final Data data = Data.parse(response.query().findChild("x", Namespace.DATA)); + data.submit(options); + final Iq set = new Iq(Iq.Type.SET); + set.setTo(conversation.getJid().asBareJid()); + set.query("http://jabber.org/protocol/muc#owner").addChild(data); + sendIqPacket(account, set, packet -> { if (callback != null) { - callback.onPushFailed(); + if (packet.getType() == Iq.Type.RESULT) { + callback.onPushSucceeded(); + } else { + Log.d(Config.LOGTAG,"failed: "+packet.toString()); + callback.onPushFailed(); + } } + }); + } else { + if (callback != null) { + callback.onPushFailed(); } } }); } public void pushSubjectToConference(final Conversation conference, final String subject) { - MessagePacket packet = this.getMessageGenerator().conferenceSubject(conference, StringUtils.nullOnEmpty(subject)); + final var packet = this.getMessageGenerator().conferenceSubject(conference, StringUtils.nullOnEmpty(subject)); this.sendMessagePacket(conference.getAccount(), packet); } public void changeAffiliationInConference(final Conversation conference, Jid user, final MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) { final Jid jid = user.asBareJid(); - final IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString()); - sendIqPacket(conference.getAccount(), request, (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + final Iq request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString()); + sendIqPacket(conference.getAccount(), request, (response) -> { + if (response.getType() == Iq.Type.RESULT) { conference.getMucOptions().changeAffiliation(jid, affiliation); getAvatarService().clear(conference); if (callback != null) { @@ -3760,29 +3651,27 @@ public class XmppConnectionService extends Service { } public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role) { - IqPacket request = this.mIqGenerator.changeRole(conference, nick, role.toString()); - sendIqPacket(conference.getAccount(), request, (account, packet) -> { - if (packet.getType() != IqPacket.TYPE.RESULT) { + final var account =conference.getAccount(); + final Iq request = this.mIqGenerator.changeRole(conference, nick, role.toString()); + sendIqPacket(account, request, (packet) -> { + if (packet.getType() != Iq.Type.RESULT) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + " unable to change role of " + nick); } }); } public void destroyRoom(final Conversation conversation, final OnRoomDestroy callback) { - IqPacket request = new IqPacket(IqPacket.TYPE.SET); + final Iq request = new Iq(Iq.Type.SET); request.setTo(conversation.getJid().asBareJid()); request.query("http://jabber.org/protocol/muc#owner").addChild("destroy"); - sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - if (callback != null) { - callback.onRoomDestroySucceeded(); - } - } else if (packet.getType() == IqPacket.TYPE.ERROR) { - if (callback != null) { - callback.onRoomDestroyFailed(); - } + sendIqPacket(conversation.getAccount(), request, response -> { + if (response.getType() == Iq.Type.RESULT) { + if (callback != null) { + callback.onRoomDestroySucceeded(); + } + } else if (response.getType() == Iq.Type.ERROR) { + if (callback != null) { + callback.onRoomDestroyFailed(); } } }); @@ -3832,7 +3721,7 @@ public class XmppConnectionService extends Service { updateConversationUi(); } - protected void syncDirtyContacts(Account account) { + public void syncDirtyContacts(Account account) { for (Contact contact : account.getRoster().getContacts()) { if (contact.getOption(Contact.Options.DIRTY_PUSH)) { pushContactToServer(contact); @@ -3868,7 +3757,7 @@ public class XmppConnectionService extends Service { final boolean sendUpdates = contact .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST) && contact.getOption(Contact.Options.PREEMPTIVE_GRANT); - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + final Iq iq = new Iq(Iq.Type.SET); iq.query(Namespace.ROSTER).addChild(contact.asElement()); account.getXmppConnection().sendIqPacket(iq, mDefaultIqHandler); if (sendUpdates) { @@ -3920,10 +3809,11 @@ public class XmppConnectionService extends Service { } private void publishMucAvatar(Conversation conversation, Avatar avatar, OnAvatarPublication callback) { - final IqPacket retrieve = mIqGenerator.retrieveVcardAvatar(avatar); - sendIqPacket(conversation.getAccount(), retrieve, (account, response) -> { - boolean itemNotFound = response.getType() == IqPacket.TYPE.ERROR && response.hasChild("error") && response.findChild("error").hasChild("item-not-found"); - if (response.getType() == IqPacket.TYPE.RESULT || itemNotFound) { + final var account = conversation.getAccount(); + final Iq retrieve = mIqGenerator.retrieveVcardAvatar(avatar); + sendIqPacket(account, retrieve, (response) -> { + boolean itemNotFound = response.getType() == Iq.Type.ERROR && response.hasChild("error") && response.findChild("error").hasChild("item-not-found"); + if (response.getType() == Iq.Type.RESULT || itemNotFound) { Element vcard = response.findChild("vCard", "vcard-temp"); if (vcard == null) { vcard = new Element("vCard", "vcard-temp"); @@ -3935,11 +3825,11 @@ public class XmppConnectionService extends Service { photo.clearChildren(); photo.addChild("TYPE").setContent(avatar.type); photo.addChild("BINVAL").setContent(avatar.image); - IqPacket publication = new IqPacket(IqPacket.TYPE.SET); + final Iq publication = new Iq(Iq.Type.SET); publication.setTo(conversation.getJid().asBareJid()); publication.addChild(vcard); - sendIqPacket(account, publication, (a1, publicationResponse) -> { - if (publicationResponse.getType() == IqPacket.TYPE.RESULT) { + sendIqPacket(account, publication, (publicationResponse) -> { + if (publicationResponse.getType() == Iq.Type.RESULT) { callback.onAvatarPublicationSucceeded(); } else { Log.d(Config.LOGTAG, "failed to publish vcard " + publicationResponse.getErrorCondition()); @@ -3965,71 +3855,64 @@ public class XmppConnectionService extends Service { public void publishAvatar(Account account, final Avatar avatar, final Bundle options, final boolean retry, final OnAvatarPublication callback) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": publishing avatar. options=" + options); - IqPacket packet = this.mIqGenerator.publishAvatar(avatar, options); - this.sendIqPacket(account, packet, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, IqPacket result) { - if (result.getType() == IqPacket.TYPE.RESULT) { - publishAvatarMetadata(account, avatar, options, true, callback); - } else if (retry && PublishOptions.preconditionNotMet(result)) { - pushNodeConfiguration(account, Namespace.AVATAR_DATA, options, new OnConfigurationPushed() { - @Override - public void onPushSucceeded() { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": changed node configuration for avatar node"); - publishAvatar(account, avatar, options, false, callback); - } + final Iq packet = this.mIqGenerator.publishAvatar(avatar, options); + this.sendIqPacket(account, packet, result -> { + if (result.getType() == Iq.Type.RESULT) { + publishAvatarMetadata(account, avatar, options, true, callback); + } else if (retry && PublishOptions.preconditionNotMet(result)) { + pushNodeConfiguration(account, Namespace.AVATAR_DATA, options, new OnConfigurationPushed() { + @Override + public void onPushSucceeded() { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": changed node configuration for avatar node"); + publishAvatar(account, avatar, options, false, callback); + } - @Override - public void onPushFailed() { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to change node configuration for avatar node"); - publishAvatar(account, avatar, null, false, callback); - } - }); - } else { - Element error = result.findChild("error"); - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server rejected avatar " + (avatar.size / 1024) + "KiB " + (error != null ? error.toString() : "")); - if (callback != null) { - callback.onAvatarPublicationFailed(R.string.error_publish_avatar_server_reject); + @Override + public void onPushFailed() { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to change node configuration for avatar node"); + publishAvatar(account, avatar, null, false, callback); } + }); + } else { + Element error = result.findChild("error"); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server rejected avatar " + (avatar.size / 1024) + "KiB " + (error != null ? error.toString() : "")); + if (callback != null) { + callback.onAvatarPublicationFailed(R.string.error_publish_avatar_server_reject); } } }); } public void publishAvatarMetadata(Account account, final Avatar avatar, final Bundle options, final boolean retry, final OnAvatarPublication callback) { - final IqPacket packet = XmppConnectionService.this.mIqGenerator.publishAvatarMetadata(avatar, options); - sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket result) { - if (result.getType() == IqPacket.TYPE.RESULT) { - if (account.setAvatar(avatar.getFilename())) { - getAvatarService().clear(account); - databaseBackend.updateAccount(account); - notifyAccountAvatarHasChanged(account); - } - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": published avatar " + (avatar.size / 1024) + "KiB"); - if (callback != null) { - callback.onAvatarPublicationSucceeded(); + final Iq packet = XmppConnectionService.this.mIqGenerator.publishAvatarMetadata(avatar, options); + sendIqPacket(account, packet, result -> { + if (result.getType() == Iq.Type.RESULT) { + if (account.setAvatar(avatar.getFilename())) { + getAvatarService().clear(account); + databaseBackend.updateAccount(account); + notifyAccountAvatarHasChanged(account); + } + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": published avatar " + (avatar.size / 1024) + "KiB"); + if (callback != null) { + callback.onAvatarPublicationSucceeded(); + } + } else if (retry && PublishOptions.preconditionNotMet(result)) { + pushNodeConfiguration(account, Namespace.AVATAR_METADATA, options, new OnConfigurationPushed() { + @Override + public void onPushSucceeded() { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": changed node configuration for avatar meta data node"); + publishAvatarMetadata(account, avatar, options, false, callback); } - } else if (retry && PublishOptions.preconditionNotMet(result)) { - pushNodeConfiguration(account, Namespace.AVATAR_METADATA, options, new OnConfigurationPushed() { - @Override - public void onPushSucceeded() { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": changed node configuration for avatar meta data node"); - publishAvatarMetadata(account, avatar, options, false, callback); - } - @Override - public void onPushFailed() { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to change node configuration for avatar meta data node"); - publishAvatarMetadata(account, avatar, null, false, callback); - } - }); - } else { - if (callback != null) { - callback.onAvatarPublicationFailed(R.string.error_publish_avatar_server_reject); + @Override + public void onPushFailed() { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to change node configuration for avatar meta data node"); + publishAvatarMetadata(account, avatar, null, false, callback); } + }); + } else { + if (callback != null) { + callback.onAvatarPublicationFailed(R.string.error_publish_avatar_server_reject); } } }); @@ -4040,10 +3923,10 @@ public class XmppConnectionService extends Service { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping republication of avatar because pep is broken"); return; } - IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); - this.sendIqPacket(account, packet, new OnIqPacketReceived() { + final Iq packet = this.mIqGenerator.retrieveAvatarMetaData(null); + this.sendIqPacket(account, packet, new Consumer() { - private Avatar parseAvatar(IqPacket packet) { + private Avatar parseAvatar(Iq packet) { Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub"); if (pubsub != null) { Element items = pubsub.findChild("items"); @@ -4054,16 +3937,16 @@ public class XmppConnectionService extends Service { return null; } - private boolean errorIsItemNotFound(IqPacket packet) { + private boolean errorIsItemNotFound(Iq packet) { Element error = packet.findChild("error"); - return packet.getType() == IqPacket.TYPE.ERROR + return packet.getType() == Iq.Type.ERROR && error != null && error.hasChild("item-not-found"); } @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT || errorIsItemNotFound(packet)) { + public void accept(final Iq packet) { + if (packet.getType() == Iq.Type.RESULT || errorIsItemNotFound(packet)) { Avatar serverAvatar = parseAvatar(packet); if (serverAvatar == null && account.getAvatar() != null) { Avatar avatar = fileBackend.getStoredPepAvatar(account.getAvatar()); @@ -4079,6 +3962,17 @@ public class XmppConnectionService extends Service { }); } + public void cancelAvatarFetches(final Account account) { + synchronized (mInProgressAvatarFetches) { + for (final Iterator iterator = mInProgressAvatarFetches.iterator(); iterator.hasNext(); ) { + final String KEY = iterator.next(); + if (KEY.startsWith(account.getJid().asBareJid() + "_")) { + iterator.remove(); + } + } + } + } + public void fetchAvatar(Account account, Avatar avatar) { fetchAvatar(account, avatar, null); } @@ -4105,26 +3999,26 @@ public class XmppConnectionService extends Service { } } - private void fetchAvatarPep(Account account, final Avatar avatar, final UiCallback callback) { - IqPacket packet = this.mIqGenerator.retrievePepAvatar(avatar); - sendIqPacket(account, packet, (a, result) -> { + private void fetchAvatarPep(final Account account, final Avatar avatar, final UiCallback callback) { + final Iq packet = this.mIqGenerator.retrievePepAvatar(avatar); + sendIqPacket(account, packet, (result) -> { synchronized (mInProgressAvatarFetches) { - mInProgressAvatarFetches.remove(generateFetchKey(a, avatar)); + mInProgressAvatarFetches.remove(generateFetchKey(account, avatar)); } - final String ERROR = a.getJid().asBareJid() + ": fetching avatar for " + avatar.owner + " failed "; - if (result.getType() == IqPacket.TYPE.RESULT) { - avatar.image = mIqParser.avatarData(result); + final String ERROR = account.getJid().asBareJid() + ": fetching avatar for " + avatar.owner + " failed "; + if (result.getType() == Iq.Type.RESULT) { + avatar.image = IqParser.avatarData(result); if (avatar.image != null) { if (getFileBackend().save(avatar)) { - if (a.getJid().asBareJid().equals(avatar.owner)) { - if (a.setAvatar(avatar.getFilename())) { - databaseBackend.updateAccount(a); + if (account.getJid().asBareJid().equals(avatar.owner)) { + if (account.setAvatar(avatar.getFilename())) { + databaseBackend.updateAccount(account); } - getAvatarService().clear(a); + getAvatarService().clear(account); updateConversationUi(); updateAccountUi(); } else { - final Contact contact = a.getRoster().getContact(avatar.owner); + final Contact contact = account.getRoster().getContact(avatar.owner); contact.setAvatar(avatar); syncRoster(account); getAvatarService().clear(contact); @@ -4134,7 +4028,7 @@ public class XmppConnectionService extends Service { if (callback != null) { callback.success(avatar); } - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully fetched pep avatar for " + avatar.owner); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully fetched pep avatar for " + avatar.owner); return; } } else { @@ -4157,57 +4051,54 @@ public class XmppConnectionService extends Service { } private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback callback) { - IqPacket packet = this.mIqGenerator.retrieveVcardAvatar(avatar); - this.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - final boolean previouslyOmittedPepFetch; - synchronized (mInProgressAvatarFetches) { - final String KEY = generateFetchKey(account, avatar); - mInProgressAvatarFetches.remove(KEY); - previouslyOmittedPepFetch = mOmittedPepAvatarFetches.remove(KEY); - } - if (packet.getType() == IqPacket.TYPE.RESULT) { - Element vCard = packet.findChild("vCard", "vcard-temp"); - Element photo = vCard != null ? vCard.findChild("PHOTO") : null; - String image = photo != null ? photo.findChildContent("BINVAL") : null; - if (image != null) { - avatar.image = image; - if (getFileBackend().save(avatar)) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() - + ": successfully fetched vCard avatar for " + avatar.owner + " omittedPep=" + previouslyOmittedPepFetch); - if (avatar.owner.isBareJid()) { - if (account.getJid().asBareJid().equals(avatar.owner) && account.getAvatar() == null) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": had no avatar. replacing with vcard"); - account.setAvatar(avatar.getFilename()); - databaseBackend.updateAccount(account); - getAvatarService().clear(account); - updateAccountUi(); - } else { - final Contact contact = account.getRoster().getContact(avatar.owner); - contact.setAvatar(avatar, previouslyOmittedPepFetch); - syncRoster(account); - getAvatarService().clear(contact); - updateRosterUi(); - } - updateConversationUi(); + final Iq packet = this.mIqGenerator.retrieveVcardAvatar(avatar); + this.sendIqPacket(account, packet, response -> { + final boolean previouslyOmittedPepFetch; + synchronized (mInProgressAvatarFetches) { + final String KEY = generateFetchKey(account, avatar); + mInProgressAvatarFetches.remove(KEY); + previouslyOmittedPepFetch = mOmittedPepAvatarFetches.remove(KEY); + } + if (response.getType() == Iq.Type.RESULT) { + Element vCard = response.findChild("vCard", "vcard-temp"); + Element photo = vCard != null ? vCard.findChild("PHOTO") : null; + String image = photo != null ? photo.findChildContent("BINVAL") : null; + if (image != null) { + avatar.image = image; + if (getFileBackend().save(avatar)) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + + ": successfully fetched vCard avatar for " + avatar.owner + " omittedPep=" + previouslyOmittedPepFetch); + if (avatar.owner.isBareJid()) { + if (account.getJid().asBareJid().equals(avatar.owner) && account.getAvatar() == null) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": had no avatar. replacing with vcard"); + account.setAvatar(avatar.getFilename()); + databaseBackend.updateAccount(account); + getAvatarService().clear(account); + updateAccountUi(); } else { - Conversation conversation = find(account, avatar.owner.asBareJid()); - if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { - MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner); - if (user != null) { - if (user.setAvatar(avatar)) { - getAvatarService().clear(user); - updateConversationUi(); - updateMucRosterUi(); - } - if (user.getRealJid() != null) { - Contact contact = account.getRoster().getContact(user.getRealJid()); - contact.setAvatar(avatar); - syncRoster(account); - getAvatarService().clear(contact); - updateRosterUi(); - } + final Contact contact = account.getRoster().getContact(avatar.owner); + contact.setAvatar(avatar, previouslyOmittedPepFetch); + syncRoster(account); + getAvatarService().clear(contact); + updateRosterUi(); + } + updateConversationUi(); + } else { + Conversation conversation = find(account, avatar.owner.asBareJid()); + if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { + MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner); + if (user != null) { + if (user.setAvatar(avatar)) { + getAvatarService().clear(user); + updateConversationUi(); + updateMucRosterUi(); + } + if (user.getRealJid() != null) { + Contact contact = account.getRoster().getContact(user.getRealJid()); + contact.setAvatar(avatar); + syncRoster(account); + getAvatarService().clear(contact); + updateRosterUi(); } } } @@ -4218,36 +4109,32 @@ public class XmppConnectionService extends Service { }); } - public void checkForAvatar(Account account, final UiCallback callback) { - IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); - this.sendIqPacket(account, packet, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub"); - if (pubsub != null) { - Element items = pubsub.findChild("items"); - if (items != null) { - Avatar avatar = Avatar.parseMetadata(items); - if (avatar != null) { - avatar.owner = account.getJid().asBareJid(); - if (fileBackend.isAvatarCached(avatar)) { - if (account.setAvatar(avatar.getFilename())) { - databaseBackend.updateAccount(account); - } - getAvatarService().clear(account); - callback.success(avatar); - } else { - fetchAvatarPep(account, avatar, callback); + public void checkForAvatar(final Account account, final UiCallback callback) { + final Iq packet = this.mIqGenerator.retrieveAvatarMetaData(null); + this.sendIqPacket(account, packet, response -> { + if (response.getType() == Iq.Type.RESULT) { + Element pubsub = response.findChild("pubsub", "http://jabber.org/protocol/pubsub"); + if (pubsub != null) { + Element items = pubsub.findChild("items"); + if (items != null) { + Avatar avatar = Avatar.parseMetadata(items); + if (avatar != null) { + avatar.owner = account.getJid().asBareJid(); + if (fileBackend.isAvatarCached(avatar)) { + if (account.setAvatar(avatar.getFilename())) { + databaseBackend.updateAccount(account); } - return; + getAvatarService().clear(account); + callback.success(avatar); + } else { + fetchAvatarPep(account, avatar, callback); } + return; } } } - callback.error(0, null); } + callback.error(0, null); }); } @@ -4259,7 +4146,7 @@ public class XmppConnectionService extends Service { if (conversation.getAccount() == account && conversation.getMode() == Conversational.MODE_MULTI) { final MucOptions mucOptions = conversation.getMucOptions(); if (mucOptions.online()) { - PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous()); + final var packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous()); packet.setTo(mucOptions.getSelf().getFullJid()); connection.sendPresencePacket(packet); } @@ -4274,7 +4161,7 @@ public class XmppConnectionService extends Service { contact.setOption(Contact.Options.DIRTY_DELETE); Account account = contact.getAccount(); if (account.getStatus() == Account.State.ONLINE) { - IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + final Iq iq = new Iq(Iq.Type.SET); Element item = iq.query(Namespace.ROSTER).addChild("item"); item.setAttribute("jid", contact.getJid()); item.setAttribute("subscription", "remove"); @@ -4334,12 +4221,12 @@ public class XmppConnectionService extends Service { if (user == null || user.getAffiliation() == MucOptions.Affiliation.OUTCAST) { changeAffiliationInConference(conversation, contact, MucOptions.Affiliation.NONE, null); } - final MessagePacket packet = mMessageGenerator.invite(conversation, contact); + final var packet = mMessageGenerator.invite(conversation, contact); sendMessagePacket(conversation.getAccount(), packet); } public void directInvite(Conversation conversation, Jid jid) { - MessagePacket packet = mMessageGenerator.directInvite(conversation, jid); + final var packet = mMessageGenerator.directInvite(conversation, jid); sendMessagePacket(conversation.getAccount(), packet); } @@ -4676,7 +4563,7 @@ public class XmppConnectionService extends Service { if (sendDisplayedMarker && serverAssist) { final var mdsDisplayed = mIqGenerator.mdsDisplayed(stanzaId, conversation); - final MessagePacket packet = mMessageGenerator.confirm(last); + final var packet = mMessageGenerator.confirm(last); packet.addChild(mdsDisplayed); if (!last.isPrivateMessage()) { packet.setTo(packet.getTo().asBareJid()); @@ -4692,7 +4579,7 @@ public class XmppConnectionService extends Service { conversation.getAccount().getJid().asBareJid() + ": sending displayed marker to " + last.getCounterpart().toString()); - final MessagePacket packet = mMessageGenerator.confirm(last); + final var packet = mMessageGenerator.confirm(last); this.sendMessagePacket(account, packet); } } @@ -4798,15 +4685,15 @@ public class XmppConnectionService extends Service { return mucServers; } - public void sendMessagePacket(Account account, MessagePacket packet) { + public void sendMessagePacket(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet) { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.sendMessagePacket(packet); } } - public void sendPresencePacket(Account account, PresencePacket packet) { - XmppConnection connection = account.getXmppConnection(); + public void sendPresencePacket(final Account account, final im.conversations.android.xmpp.model.stanza.Presence packet) { + final XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.sendPresencePacket(packet); } @@ -4814,18 +4701,18 @@ public class XmppConnectionService extends Service { public void sendCreateAccountWithCaptchaPacket(Account account, String id, Data data) { final XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - IqPacket request = mIqGenerator.generateCreateAccountWithCaptcha(account, id, data); - connection.sendUnmodifiedIqPacket(request, connection.registrationResponseListener, true); + if (connection == null) { + return; } + connection.sendCreateAccountWithCaptchaPacket(id, data); } - public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) { + public void sendIqPacket(final Account account, final Iq packet, final Consumer callback) { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.sendIqPacket(packet, callback); } else if (callback != null) { - callback.onIqPacketReceived(account, new IqPacket(IqPacket.TYPE.TIMEOUT)); + callback.accept(Iq.TIMEOUT); } } @@ -4840,7 +4727,7 @@ public class XmppConnectionService extends Service { } else { status = getTargetPresence(); } - final PresencePacket packet = mPresenceGenerator.selfPresence(account, status); + final var packet = mPresenceGenerator.selfPresence(account, status); if (mLastActivity > 0 && includeIdleTimestamp) { long since = Math.min(mLastActivity, System.currentTimeMillis()); //don't send future dates packet.addChild("idle", Namespace.IDLE).setAttribute("since", AbstractGenerator.getTimestamp(since)); @@ -4890,10 +4777,6 @@ public class XmppConnectionService extends Service { return this.mIqGenerator; } - public IqParser getIqParser() { - return this.mIqParser; - } - public JingleConnectionManager getJingleConnectionManager() { return this.mJingleConnectionManager; } @@ -4986,10 +4869,11 @@ public class XmppConnectionService extends Service { public boolean sendBlockRequest(final Blockable blockable, final boolean reportSpam, final String serverMsgId) { if (blockable != null && blockable.getBlockedJid() != null) { + final var account = blockable.getAccount(); final Jid jid = blockable.getBlockedJid(); - this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid, reportSpam, serverMsgId), (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { - a.getBlocklist().add(jid); + this.sendIqPacket(account, getIqGenerator().generateSetBlockRequest(jid, reportSpam, serverMsgId), (response) -> { + if (response.getType() == Iq.Type.RESULT) { + account.getBlocklist().add(jid); updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); } }); @@ -5030,31 +4914,29 @@ public class XmppConnectionService extends Service { public void sendUnblockRequest(final Blockable blockable) { if (blockable != null && blockable.getJid() != null) { + final var account = blockable.getAccount(); final Jid jid = blockable.getBlockedJid(); - this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetUnblockRequest(jid), new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.getBlocklist().remove(jid); - updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); - } + this.sendIqPacket(account, getIqGenerator().generateSetUnblockRequest(jid), response -> { + if (response.getType() == Iq.Type.RESULT) { + account.getBlocklist().remove(jid); + updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); } }); } } - public void publishDisplayName(Account account) { + public void publishDisplayName(final Account account) { String displayName = account.getDisplayName(); - final IqPacket request; + final Iq request; if (TextUtils.isEmpty(displayName)) { request = mIqGenerator.deleteNode(Namespace.NICK); } else { request = mIqGenerator.publishNick(displayName); } mAvatarService.clear(account); - sendIqPacket(account, request, (account1, packet) -> { - if (packet.getType() == IqPacket.TYPE.ERROR) { - Log.d(Config.LOGTAG, account1.getJid().asBareJid() + ": unable to modify nick name " + packet); + sendIqPacket(account, request, (packet) -> { + if (packet.getType() == Iq.Type.ERROR) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to modify nick name " + packet); } }); } @@ -5072,7 +4954,7 @@ public class XmppConnectionService extends Service { } } - public void fetchCaps(Account account, final Jid jid, final Presence presence) { + public void fetchCaps(final Account account, final Jid jid, final Presence presence) { final Pair key = new Pair<>(presence.getHash(), presence.getVer()); final ServiceDiscoveryResult disco = getCachedServiceDiscoveryResult(key); if (disco != null) { @@ -5082,7 +4964,7 @@ public class XmppConnectionService extends Service { syncRoster(account); } } else { - final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.setTo(jid); final String node = presence.getNode(); final String ver = presence.getVer(); @@ -5091,14 +4973,14 @@ public class XmppConnectionService extends Service { query.setAttribute("node", node + "#" + ver); } Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": making disco request for " + key.second + " to " + jid); - sendIqPacket(account, request, (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + sendIqPacket(account, request, (response) -> { + if (response.getType() == Iq.Type.RESULT) { final ServiceDiscoveryResult discoveryResult = new ServiceDiscoveryResult(response); if (presence.getVer().equals(discoveryResult.getVer())) { databaseBackend.insertDiscoveryResult(discoveryResult); - injectServiceDiscoveryResult(a.getRoster(), presence.getHash(), presence.getVer(), discoveryResult); + injectServiceDiscoveryResult(account.getRoster(), presence.getHash(), presence.getVer(), discoveryResult); } else { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + discoveryResult.getVer()); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + discoveryResult.getVer()); } } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to fetch caps from " + jid); @@ -5126,13 +5008,13 @@ public class XmppConnectionService extends Service { } } - public void fetchMamPreferences(Account account, final OnMamPreferencesFetched callback) { + public void fetchMamPreferences(final Account account, final OnMamPreferencesFetched callback) { final MessageArchiveService.Version version = MessageArchiveService.Version.get(account); - IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.addChild("prefs", version.namespace); - sendIqPacket(account, request, (account1, packet) -> { - Element prefs = packet.findChild("prefs", version.namespace); - if (packet.getType() == IqPacket.TYPE.RESULT && prefs != null) { + sendIqPacket(account, request, (packet) -> { + final Element prefs = packet.findChild("prefs", version.namespace); + if (packet.getType() == Iq.Type.RESULT && prefs != null) { callback.onPreferencesFetched(prefs); } else { callback.onPreferencesFetchFailed(); @@ -5231,7 +5113,7 @@ public class XmppConnectionService extends Service { } public void pushMamPreferences(Account account, Element prefs) { - IqPacket set = new IqPacket(IqPacket.TYPE.SET); + final Iq set = new Iq(Iq.Type.SET); set.addChild(prefs); sendIqPacket(account, set, null); } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 2ce3eb52aca08ae900176314482634833ae3ea02..45c2259f4fd8f38848858a2b4faab90ad3e8c088 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -494,14 +494,9 @@ public abstract class XmppActivity extends ActionBarActivity { } protected boolean isOptimizingBattery() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); - return pm != null - && !pm.isIgnoringBatteryOptimizations(getPackageName()); - } else { - return false; - } - } + final PowerManager pm = getSystemService(PowerManager.class); + return !pm.isIgnoringBatteryOptimizations(getPackageName()); +} protected boolean isAffectedByDataSaver() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index 98a2bfb46aececf2acea02d9a46fd573c9df56b5..20db7f8782e9ea96d8583b68996a1542b00629b0 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -3,7 +3,9 @@ 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; @@ -12,7 +14,7 @@ import java.util.List; import eu.siacs.conversations.utils.XmlHelper; import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import im.conversations.android.xmpp.model.stanza.Message; public class Element { private final String name; @@ -136,6 +138,10 @@ public class Element { return this; } + public void setAttribute(final String name, final boolean value) { + this.setAttribute(name, value ? "1" : "0"); + } + public void removeAttribute(final String name) { this.attributes.remove(name); } @@ -153,6 +159,11 @@ public class Element { } } + public long getLongAttribute(final String name) { + final var value = Longs.tryParse(Strings.nullToEmpty(this.attributes.get(name))); + return value == null ? 0 : value; + } + public Optional getOptionalIntAttribute(final String name) { final String value = getAttribute(name); if (value == null) { @@ -167,7 +178,7 @@ public class Element { try { return Jid.ofEscaped(jid); } catch (final IllegalArgumentException e) { - return InvalidJid.of(jid, this instanceof MessagePacket); + return InvalidJid.of(jid, this instanceof Message); } } return null; @@ -180,7 +191,7 @@ public class Element { @NonNull public String toString() { final StringBuilder elementOutput = new StringBuilder(); - if ((content == null) && (children.size() == 0)) { + if (content == null && children.isEmpty()) { final Tag emptyTag = Tag.empty(name); emptyTag.setAttributes(this.attributes); elementOutput.append(emptyTag); diff --git a/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java b/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java index 635afd1452d096738017c042456db7e8af7d36bd..d730bd34f7b4a4bef5c5fdd08bd63598d00c1e88 100644 --- a/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java +++ b/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java @@ -37,7 +37,7 @@ public class LocalizedContent { } } } - if (contents.size() == 0) { + if (contents.isEmpty()) { return null; } final String userLanguage = Locale.getDefault().getLanguage(); diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 2e5eb1c5a69bbaa288d334bb6321fe76c8cfe934..cc08154f9b4fa0a510765b13619b8d1c16dce493 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -1,8 +1,29 @@ package eu.siacs.conversations.xml; public final class Namespace { + public static final String ADDRESSING = "http://jabber.org/protocol/address"; + public static final String AXOLOTL = "eu.siacs.conversations.axolotl"; + public static final String PGP_SIGNED = "jabber:x:signed"; + public static final String PGP_ENCRYPTED = "jabber:x:encrypted"; + public static final String AXOLOTL_BUNDLES = AXOLOTL + ".bundles"; + public static final String AXOLOTL_DEVICE_LIST = AXOLOTL + ".devicelist"; + public static final String HINTS = "urn:xmpp:hints"; + public static final String MESSAGE_ARCHIVE_MANAGEMENT = "urn:xmpp:mam:2"; + public static final String VERSION = "jabber:iq:version"; + public static final String LAST_MESSAGE_CORRECTION = "urn:xmpp:message-correct:0"; + public static final String RESULT_SET_MANAGEMENT = "http://jabber.org/protocol/rsm"; + public static final String CHAT_MARKERS = "urn:xmpp:chat-markers:0"; + public static final String CHAT_STATES = "http://jabber.org/protocol/chatstates"; + public static final String DELIVERY_RECEIPTS = "urn:xmpp:receipts"; + public static final String REACTIONS = "urn:xmpp:reactions:0"; + public static final String VCARD_TEMP = "vcard-temp"; + public static final String VCARD_TEMP_UPDATE = "vcard-temp:x:update"; + public static final String DELAY = "urn:xmpp:delay"; + public static final String OCCUPANT_ID = "urn:xmpp:occupant-id:0"; public static final String STREAMS = "http://etherx.jabber.org/streams"; + public static final String STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas"; public static final String JABBER_CLIENT = "jabber:client"; + public static final String FORWARD = "urn:xmpp:forward:0"; public static final String DISCO_ITEMS = "http://jabber.org/protocol/disco#items"; public static final String DISCO_INFO = "http://jabber.org/protocol/disco#info"; public static final String EXTERNAL_SERVICE_DISCOVERY = "urn:xmpp:extdisco:2"; @@ -23,12 +44,15 @@ public final class Namespace { public static final String FAST = "urn:xmpp:fast:0"; public static final String TLS = "urn:ietf:params:xml:ns:xmpp-tls"; public static final String PUBSUB = "http://jabber.org/protocol/pubsub"; + public static final String PUBSUB_EVENT = PUBSUB + "#event"; + public static final String MUC = "http://jabber.org/protocol/muc"; public static final String PUBSUB_PUBLISH_OPTIONS = PUBSUB + "#publish-options"; public static final String PUBSUB_CONFIG_NODE_MAX = PUBSUB + "#config-node-max"; public static final String PUBSUB_ERROR = PUBSUB + "#errors"; public static final String PUBSUB_OWNER = PUBSUB + "#owner"; public static final String NICK = "http://jabber.org/protocol/nick"; - public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = "http://jabber.org/protocol/offline"; + public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = + "http://jabber.org/protocol/offline"; public static final String BIND = "urn:ietf:params:xml:ns:xmpp-bind"; public static final String BIND2 = "urn:xmpp:bind:0"; public static final String STREAM_MANAGEMENT = "urn:xmpp:sm:3"; @@ -38,7 +62,7 @@ public final class Namespace { public static final String BOOKMARKS = "storage:bookmarks"; public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0"; public static final String AVATAR_DATA = "urn:xmpp:avatar:data"; - public static final String AVATAR_METADATA = "urn:xmpp:avatar:metadata"; + public static final String AVATAR_METADATA = "urn:xmpp:avatar:metadata"; public static final String AVATAR_CONVERSION = "urn:xmpp:pep-vcard-conversion:0"; public static final String JINGLE = "urn:xmpp:jingle:1"; public static final String JINGLE_ERRORS = "urn:xmpp:jingle:errors:1"; @@ -48,7 +72,8 @@ public final class Namespace { public static final String JINGLE_TRANSPORTS_S5B = "urn:xmpp:jingle:transports:s5b:1"; public static final String JINGLE_TRANSPORTS_IBB = "urn:xmpp:jingle:transports:ibb:1"; public static final String JINGLE_TRANSPORT_ICE_UDP = "urn:xmpp:jingle:transports:ice-udp:1"; - public static final String JINGLE_TRANSPORT_WEBRTC_DATA_CHANNEL = "urn:xmpp:jingle:transports:webrtc-datachannel:1"; + public static final String JINGLE_TRANSPORT_WEBRTC_DATA_CHANNEL = + "urn:xmpp:jingle:transports:webrtc-datachannel:1"; public static final String JINGLE_TRANSPORT = "urn:xmpp:jingle:transports:dtls-sctp:1"; public static final String JINGLE_APPS_RTP = "urn:xmpp:jingle:apps:rtp:1"; @@ -57,9 +82,12 @@ public final class Namespace { public static final String JINGLE_APPS_GROUPING = "urn:xmpp:jingle:apps:grouping:0"; public static final String JINGLE_FEATURE_AUDIO = "urn:xmpp:jingle:apps:rtp:audio"; public static final String JINGLE_FEATURE_VIDEO = "urn:xmpp:jingle:apps:rtp:video"; - public static final String JINGLE_RTP_HEADER_EXTENSIONS = "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"; - public static final String JINGLE_RTP_FEEDBACK_NEGOTIATION = "urn:xmpp:jingle:apps:rtp:rtcp-fb:0"; - public static final String JINGLE_RTP_SOURCE_SPECIFIC_MEDIA_ATTRIBUTES = "urn:xmpp:jingle:apps:rtp:ssma:0"; + public static final String JINGLE_RTP_HEADER_EXTENSIONS = + "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"; + public static final String JINGLE_RTP_FEEDBACK_NEGOTIATION = + "urn:xmpp:jingle:apps:rtp:rtcp-fb:0"; + public static final String JINGLE_RTP_SOURCE_SPECIFIC_MEDIA_ATTRIBUTES = + "urn:xmpp:jingle:apps:rtp:ssma:0"; public static final String IBB = "http://jabber.org/protocol/ibb"; public static final String PING = "urn:xmpp:ping"; public static final String PUSH = "urn:xmpp:push:0"; @@ -70,8 +98,10 @@ public final class Namespace { public static final String INVITE = "urn:xmpp:invite"; public static final String PARS = "urn:xmpp:pars:0"; public static final String EASY_ONBOARDING_INVITE = "urn:xmpp:invite#invite"; - public static final String OMEMO_DTLS_SRTP_VERIFICATION = "http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification"; - public static final String JINGLE_TRANSPORT_ICE_OPTION = "http://gultsch.de/xmpp/drafts/jingle/transports/ice-udp/option"; + public static final String OMEMO_DTLS_SRTP_VERIFICATION = + "http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification"; + public static final String JINGLE_TRANSPORT_ICE_OPTION = + "http://gultsch.de/xmpp/drafts/jingle/transports/ice-udp/option"; public static final String UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push"; public static final String REPORTING = "urn:xmpp:reporting:1"; public static final String REPORTING_REASON_SPAM = "urn:xmpp:reporting:spam"; @@ -79,4 +109,7 @@ public final class Namespace { public static final String HASHES = "urn:xmpp:hashes:2"; public static final String MDS_DISPLAYED = "urn:xmpp:mds:displayed:0"; public static final String MDS_SERVER_ASSIST = "urn:xmpp:mds:server-assist:0"; + + public static final String ENTITY_CAPABILITIES = "http://jabber.org/protocol/caps"; + public static final String ENTITY_CAPABILITIES_2 = "urn:xmpp:caps"; } diff --git a/src/main/java/eu/siacs/conversations/xml/TagWriter.java b/src/main/java/eu/siacs/conversations/xml/TagWriter.java index 5a9f3317c4bfae527ca498f915466bcd947fad97..2401c612ca8c7a57882425148b0c0c06e3bdea31 100644 --- a/src/main/java/eu/siacs/conversations/xml/TagWriter.java +++ b/src/main/java/eu/siacs/conversations/xml/TagWriter.java @@ -10,13 +10,14 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import eu.siacs.conversations.Config; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; +import im.conversations.android.xmpp.model.StreamElement; public class TagWriter { private OutputStreamWriter outputStream; private boolean finished = false; - private final LinkedBlockingQueue writeQueue = new LinkedBlockingQueue(); + + private final LinkedBlockingQueue writeQueue = new LinkedBlockingQueue<>(); private CountDownLatch stanzaWriterCountDownLatch = null; private final Thread asyncStanzaWriter = new Thread() { @@ -25,13 +26,13 @@ public class TagWriter { public void run() { stanzaWriterCountDownLatch = new CountDownLatch(1); while (!isInterrupted()) { - if (finished && writeQueue.size() == 0) { + if (finished && writeQueue.isEmpty()) { break; } try { - AbstractStanza output = writeQueue.take(); + final var output = writeQueue.take(); outputStream.write(output.toString()); - if (writeQueue.size() == 0) { + if (writeQueue.isEmpty()) { outputStream.flush(); } } catch (Exception e) { @@ -74,7 +75,7 @@ public class TagWriter { } } - public synchronized void writeElement(Element element) throws IOException { + public synchronized void writeElement(final StreamElement element) throws IOException { if (outputStream == null) { throw new IOException("output stream was null"); } @@ -82,7 +83,7 @@ public class TagWriter { outputStream.flush(); } - public void writeStanzaAsync(AbstractStanza stanza) { + public void writeStanzaAsync(StreamElement stanza) { if (finished) { Log.d(Config.LOGTAG, "attempting to write stanza to finished TagWriter"); } else { diff --git a/src/main/java/eu/siacs/conversations/xml/XmlReader.java b/src/main/java/eu/siacs/conversations/xml/XmlReader.java index 240b92b7ae9ae11ab7cbbdfbca51403f8231d356..090a6e47dfcdfaf64ab9dd14309611bebfa8b7c8 100644 --- a/src/main/java/eu/siacs/conversations/xml/XmlReader.java +++ b/src/main/java/eu/siacs/conversations/xml/XmlReader.java @@ -3,6 +3,12 @@ package eu.siacs.conversations.xml; import android.util.Log; import android.util.Xml; +import eu.siacs.conversations.Config; + +import im.conversations.android.xmpp.ExtensionFactory; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.StreamElement; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -11,8 +17,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import eu.siacs.conversations.Config; - public class XmlReader implements Closeable { private final XmlPullParser parser; private InputStream is; @@ -87,8 +91,21 @@ public class XmlReader implements Closeable { return null; } - public Element readElement(Tag currentTag) throws IOException { - Element element = new Element(currentTag.getName()); + public T readElement(final Tag current, final Class clazz) + throws IOException { + final Element element = readElement(current); + if (clazz.isInstance(element)) { + return clazz.cast(element); + } + throw new IOException( + String.format("Read unexpected {%s}%s", element.getNamespace(), element.getName())); + } + + public Element readElement(final Tag currentTag) throws IOException { + final var attributes = currentTag.getAttributes(); + final var namespace = attributes.get("xmlns"); + final var name = currentTag.getName(); + final Element element = ExtensionFactory.create(name, namespace); element.setAttributes(currentTag.getAttributes()); Tag nextTag = this.readTag(); if (nextTag == null) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java b/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java index f3a21c36f54bc055ccc73dd87e48a5ee66a47adf..4e3092821d4c241a004e5ce77f7e8e2940dea0ea 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java @@ -31,7 +31,7 @@ package eu.siacs.conversations.xmpp; import androidx.annotation.NonNull; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; +import im.conversations.android.xmpp.model.stanza.Stanza; public class InvalidJid implements Jid { @@ -137,10 +137,10 @@ public class InvalidJid implements Jid { } public static boolean isValid(Jid jid) { - return !(jid != null && jid instanceof InvalidJid); + return !(jid instanceof InvalidJid); } - public static boolean hasValidFrom(AbstractStanza stanza) { + public static boolean hasValidFrom(Stanza stanza) { final String from = stanza.getAttribute("from"); if (from == null) { return false; diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnIqPacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/OnIqPacketReceived.java deleted file mode 100644 index 6db24ef94845d95d363869b7de3225a24ee04013..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/OnIqPacketReceived.java +++ /dev/null @@ -1,8 +0,0 @@ -package eu.siacs.conversations.xmpp; - -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; - -public interface OnIqPacketReceived extends PacketReceived { - void onIqPacketReceived(Account account, IqPacket packet); -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnMessagePacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/OnMessagePacketReceived.java index 24acf16e27a59950c61357e1f5d9aa02686d9119..6ff26884bee375e74ddef4c5919f23ef05e4a35b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/OnMessagePacketReceived.java +++ b/src/main/java/eu/siacs/conversations/xmpp/OnMessagePacketReceived.java @@ -1,8 +1,7 @@ package eu.siacs.conversations.xmpp; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import im.conversations.android.xmpp.model.stanza.Message; -public interface OnMessagePacketReceived extends PacketReceived { - void onMessagePacketReceived(Account account, MessagePacket packet); +public interface OnMessagePacketReceived { + void onMessagePacketReceived(Message packet); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnPresencePacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/OnPresencePacketReceived.java deleted file mode 100644 index e1bf839f43e26809091dc5bdf8c22626ce98e0f5..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/OnPresencePacketReceived.java +++ /dev/null @@ -1,8 +0,0 @@ -package eu.siacs.conversations.xmpp; - -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.stanzas.PresencePacket; - -public interface OnPresencePacketReceived extends PacketReceived { - void onPresencePacketReceived(Account account, PresencePacket packet); -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/PacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/PacketReceived.java deleted file mode 100644 index 05ddc392f822b1c55d0457551b823d5b590db129..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/PacketReceived.java +++ /dev/null @@ -1,5 +0,0 @@ -package eu.siacs.conversations.xmpp; - -public interface PacketReceived { - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 31e150ac7447734b48112377aad6bf50aa927a09..a3826626678d9a2061dfe33ce7acad9d686a9274 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -36,6 +36,9 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.http.HttpConnectionManager; +import eu.siacs.conversations.parser.IqParser; +import eu.siacs.conversations.parser.MessageParser; +import eu.siacs.conversations.parser.PresenceParser; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.MemorizingTrustManager; import eu.siacs.conversations.services.MessageArchiveService; @@ -58,18 +61,37 @@ import eu.siacs.conversations.xml.XmlReader; import eu.siacs.conversations.xmpp.bind.Bind2; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; -import eu.siacs.conversations.xmpp.stanzas.AbstractAcknowledgeableStanza; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; -import eu.siacs.conversations.xmpp.stanzas.PresencePacket; -import eu.siacs.conversations.xmpp.stanzas.csi.ActivePacket; -import eu.siacs.conversations.xmpp.stanzas.csi.InactivePacket; -import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket; -import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket; -import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket; -import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket; + +import im.conversations.android.xmpp.model.AuthenticationStreamFeature; +import im.conversations.android.xmpp.model.StreamElement; +import im.conversations.android.xmpp.model.bind2.Bind; +import im.conversations.android.xmpp.model.bind2.Bound; +import im.conversations.android.xmpp.model.csi.Active; +import im.conversations.android.xmpp.model.csi.Inactive; +import im.conversations.android.xmpp.model.error.Condition; +import im.conversations.android.xmpp.model.fast.Fast; +import im.conversations.android.xmpp.model.fast.RequestToken; +import im.conversations.android.xmpp.model.jingle.Jingle; +import im.conversations.android.xmpp.model.sasl.Auth; +import im.conversations.android.xmpp.model.sasl.Mechanisms; +import im.conversations.android.xmpp.model.sasl.Response; +import im.conversations.android.xmpp.model.sasl.Success; +import im.conversations.android.xmpp.model.sasl2.Authenticate; +import im.conversations.android.xmpp.model.sasl2.Authentication; +import im.conversations.android.xmpp.model.sm.Ack; +import im.conversations.android.xmpp.model.sm.Enable; +import im.conversations.android.xmpp.model.sm.Enabled; +import im.conversations.android.xmpp.model.sm.Failed; +import im.conversations.android.xmpp.model.sm.Request; +import im.conversations.android.xmpp.model.sm.Resume; +import im.conversations.android.xmpp.model.sm.Resumed; +import im.conversations.android.xmpp.model.sm.StreamManagement; +import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.stanza.Presence; +import im.conversations.android.xmpp.model.stanza.Stanza; +import im.conversations.android.xmpp.model.tls.Proceed; +import im.conversations.android.xmpp.model.tls.StartTls; +import im.conversations.android.xmpp.processor.BindProcessor; import okhttp3.HttpUrl; @@ -104,6 +126,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.regex.Matcher; import javax.net.ssl.KeyManager; @@ -116,46 +139,12 @@ import javax.net.ssl.X509TrustManager; public class XmppConnection implements Runnable { - private static final int PACKET_IQ = 0; - private static final int PACKET_MESSAGE = 1; - private static final int PACKET_PRESENCE = 2; - public final OnIqPacketReceived registrationResponseListener = - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.setOption(Account.OPTION_REGISTER, false); - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": successfully registered new account on server"); - throw new StateChangingError(Account.State.REGISTRATION_SUCCESSFUL); - } else { - final List PASSWORD_TOO_WEAK_MSGS = - Arrays.asList( - "The password is too weak", "Please use a longer password."); - Element error = packet.findChild("error"); - Account.State state = Account.State.REGISTRATION_FAILED; - if (error != null) { - if (error.hasChild("conflict")) { - state = Account.State.REGISTRATION_CONFLICT; - } else if (error.hasChild("resource-constraint") - && "wait".equals(error.getAttribute("type"))) { - state = Account.State.REGISTRATION_PLEASE_WAIT; - } else if (error.hasChild("not-acceptable") - && PASSWORD_TOO_WEAK_MSGS.contains( - error.findChildContent("text"))) { - state = Account.State.REGISTRATION_PASSWORD_TOO_WEAK; - } - } - throw new StateChangingError(state); - } - }; protected final Account account; private final Features features = new Features(this); private final HashMap disco = new HashMap<>(); private final HashMap commands = new HashMap<>(); - private final SparseArray mStanzaQueue = new SparseArray<>(); - private final Hashtable> packetCallbacks = - new Hashtable<>(); + private final SparseArray mStanzaQueue = new SparseArray<>(); + private final Hashtable>> packetCallbacks = new Hashtable<>(); private final Set advancedStreamFeaturesLoadedListeners = new HashSet<>(); private final AppSettings appSettings; @@ -168,8 +157,8 @@ public class XmppConnection implements Runnable { private boolean quickStartInProgress = false; private boolean isBound = false; private boolean offlineMessagesRetrieved = false; - private Element streamFeatures; - private Element boundStreamFeatures; + private im.conversations.android.xmpp.model.streams.Features streamFeatures; + private im.conversations.android.xmpp.model.streams.Features boundStreamFeatures; private StreamId streamId = null; private int stanzasReceived = 0; private int stanzasSent = 0; @@ -186,12 +175,13 @@ public class XmppConnection implements Runnable { private final AtomicInteger mSmCatchupMessageCounter = new AtomicInteger(0); private boolean mInteractive = false; private int attempt = 0; - private OnPresencePacketReceived presenceListener = null; private OnJinglePacketReceived jingleListener = null; - private OnIqPacketReceived unregisteredIqListener = null; - private OnMessagePacketReceived messageListener = null; + + private final Consumer presenceListener; + private final Consumer unregisteredIqListener; + private final Consumer messageListener; private OnStatusChanged statusListener = null; - private OnBindListener bindListener = null; + private final Runnable bindListener; private OnMessageAcknowledged acknowledgedListener = null; private LoginInfo loginInfo; private HashedToken.Mechanism hashTokenRequest; @@ -206,6 +196,10 @@ public class XmppConnection implements Runnable { this.account = account; this.mXmppConnectionService = service; this.appSettings = new AppSettings(mXmppConnectionService.getApplicationContext()); + this.presenceListener = new PresenceParser(service, account); + this.unregisteredIqListener = new IqParser(service, account); + this.messageListener = new MessageParser(service, account); + this.bindListener = new BindProcessor(service, account); } private static void fixResource(final Context context, final Account account) { @@ -606,7 +600,7 @@ public class XmppConnection implements Runnable { } else if (nextTag.isStart("features", Namespace.STREAMS)) { processStreamFeatures(nextTag); } else if (nextTag.isStart("proceed", Namespace.TLS)) { - switchOverToTls(); + switchOverToTls(nextTag); } else if (nextTag.isStart("failure", Namespace.TLS)) { throw new StateChangingException(Account.State.TLS_ERROR); } else if (account.isOptionSet(Account.OPTION_REGISTER) @@ -632,10 +626,10 @@ public class XmppConnection implements Runnable { throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } else if (this.streamId != null && nextTag.isStart("resumed", Namespace.STREAM_MANAGEMENT)) { - final Element resumed = tagReader.readElement(nextTag); + final Resumed resumed = tagReader.readElement(nextTag, Resumed.class); processResumed(resumed); } else if (nextTag.isStart("failed", Namespace.STREAM_MANAGEMENT)) { - final Element failed = tagReader.readElement(nextTag); + final Failed failed = tagReader.readElement(nextTag, Failed.class); processFailed(failed, true); } else if (nextTag.isStart("iq", Namespace.JABBER_CLIENT)) { processIq(nextTag); @@ -651,7 +645,7 @@ public class XmppConnection implements Runnable { } else if (nextTag.isStart("presence", Namespace.JABBER_CLIENT)) { processPresence(nextTag); } else if (nextTag.isStart("enabled", Namespace.STREAM_MANAGEMENT)) { - final Element enabled = tagReader.readElement(nextTag); + final var enabled = tagReader.readElement(nextTag, Enabled.class); processEnabled(enabled); } else if (nextTag.isStart("r", Namespace.STREAM_MANAGEMENT)) { tagReader.readElement(nextTag); @@ -662,7 +656,7 @@ public class XmppConnection implements Runnable { + ": acknowledging stanza #" + this.stanzasReceived); } - final AckPacket ack = new AckPacket(this.stanzasReceived); + final Ack ack = new Ack(this.stanzasReceived); tagWriter.writeStanzaAsync(ack); } else if (nextTag.isStart("a", Namespace.STREAM_MANAGEMENT)) { boolean accountUiNeedsRefresh = false; @@ -689,11 +683,11 @@ public class XmppConnection implements Runnable { if (accountUiNeedsRefresh) { mXmppConnectionService.updateAccountUi(); } - final Element ack = tagReader.readElement(nextTag); + final var ack = tagReader.readElement(nextTag, Ack.class); lastPacketReceived = SystemClock.elapsedRealtime(); final boolean acknowledgedMessages; synchronized (this.mStanzaQueue) { - final Optional serverSequence = ack.getOptionalIntAttribute("h"); + final Optional serverSequence = ack.getHandled(); if (serverSequence.isPresent()) { acknowledgedMessages = acknowledgeStanzaUpTo(serverSequence.get()); } else { @@ -729,11 +723,11 @@ public class XmppConnection implements Runnable { } catch (final IllegalArgumentException e) { throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } - final Element response; + final StreamElement response; if (version == SaslMechanism.Version.SASL) { - response = new Element("response", Namespace.SASL); + response = new Response(); } else if (version == SaslMechanism.Version.SASL_2) { - response = new Element("response", Namespace.SASL_2); + response = new im.conversations.android.xmpp.model.sasl2.Response(); } else { throw new AssertionError("Missing implementation for " + version); } @@ -753,26 +747,23 @@ public class XmppConnection implements Runnable { tagWriter.writeElement(response); } - private boolean processSuccess(final Element success) + private boolean processSuccess(final Element element) throws IOException, XmlPullParserException { - final SaslMechanism.Version version; - try { - version = SaslMechanism.Version.of(success); - } catch (final IllegalArgumentException e) { - throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); - } final LoginInfo currentLoginInfo = this.loginInfo; final SaslMechanism currentSaslMechanism = LoginInfo.mechanism(currentLoginInfo); if (currentLoginInfo == null || currentSaslMechanism == null) { throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } + final SaslMechanism.Version version; final String challenge; - if (version == SaslMechanism.Version.SASL) { + if (element instanceof Success success) { challenge = success.getContent(); - } else if (version == SaslMechanism.Version.SASL_2) { + version = SaslMechanism.Version.SASL; + } else if (element instanceof im.conversations.android.xmpp.model.sasl2.Success success) { challenge = success.findChildContent("additional-data"); + version = SaslMechanism.Version.SASL_2; } else { - throw new AssertionError("Missing implementation for " + version); + throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } try { currentLoginInfo.success(challenge, sslSocketOrNull(socket)); @@ -786,47 +777,24 @@ public class XmppConnection implements Runnable { if (SaslMechanism.pin(currentSaslMechanism)) { account.setPinnedMechanism(currentSaslMechanism); } - if (version == SaslMechanism.Version.SASL_2) { - final String authorizationIdentifier = - success.findChildContent("authorization-identifier"); - final Jid authorizationJid; - try { - authorizationJid = - Strings.isNullOrEmpty(authorizationIdentifier) - ? null - : Jid.ofEscaped(authorizationIdentifier); - } catch (final IllegalArgumentException e) { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": SASL 2.0 authorization identifier was not a valid jid"); - throw new StateChangingException(Account.State.BIND_FAILURE); - } - if (authorizationJid == null) { - throw new StateChangingException(Account.State.BIND_FAILURE); - } + if (element instanceof im.conversations.android.xmpp.model.sasl2.Success success) { + final var authorizationJid = success.getAuthorizationIdentifier(); + checkAssignedDomainOrThrow(authorizationJid); Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": SASL 2.0 authorization identifier was " + authorizationJid); - if (!account.getJid().getDomain().equals(authorizationJid.getDomain())) { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": server tried to re-assign domain to " - + authorizationJid.getDomain()); - throw new StateChangingError(Account.State.BIND_FAILURE); - } + // TODO this should only happen when we used Bind 2 if (authorizationJid.isFullJid() && account.setJid(authorizationJid)) { Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": jid changed during SASL 2.0. updating database"); } - final Element bound = success.findChild("bound", Namespace.BIND2); - final Element resumed = success.findChild("resumed", Namespace.STREAM_MANAGEMENT); - final Element failed = success.findChild("failed", Namespace.STREAM_MANAGEMENT); + final Bound bound = success.getExtension(Bound.class); + final Resumed resumed = success.getExtension(Resumed.class); + final Failed failed = success.getExtension(Failed.class); final Element tokenWrapper = success.findChild("token", Namespace.FAST); final String token = tokenWrapper == null ? null : tokenWrapper.getAttribute("token"); if (bound != null && resumed != null) { @@ -853,8 +821,7 @@ public class XmppConnection implements Runnable { this.isBound = true; processNopStreamFeatures(); this.boundStreamFeatures = this.streamFeatures; - final Element streamManagementEnabled = - bound.findChild("enabled", Namespace.STREAM_MANAGEMENT); + final Enabled streamManagementEnabled = bound.getExtension(Enabled.class); final Element carbonsEnabled = bound.findChild("enabled", Namespace.CARBONS); final boolean waitForDisco; if (streamManagementEnabled != null) { @@ -939,7 +906,7 @@ public class XmppConnection implements Runnable { private void resetOutboundStanzaQueue() { synchronized (this.mStanzaQueue) { - final ImmutableList.Builder intermediateStanzasBuilder = + final ImmutableList.Builder intermediateStanzasBuilder = new ImmutableList.Builder<>(); if (Config.EXTENDED_SM_LOGGING) { Log.d( @@ -949,7 +916,7 @@ public class XmppConnection implements Runnable { + this.stanzasSentBeforeAuthentication); } for (int i = this.stanzasSentBeforeAuthentication + 1; i <= this.stanzasSent; ++i) { - final AbstractAcknowledgeableStanza stanza = this.mStanzaQueue.get(i); + final Stanza stanza = this.mStanzaQueue.get(i); if (stanza != null) { intermediateStanzasBuilder.add(stanza); } @@ -973,7 +940,9 @@ public class XmppConnection implements Runnable { private void processNopStreamFeatures() throws IOException { final Tag tag = tagReader.readTag(); if (tag != null && tag.isStart("features", Namespace.STREAMS)) { - this.streamFeatures = tagReader.readElement(tag); + this.streamFeatures = + tagReader.readElement( + tag, im.conversations.android.xmpp.model.streams.Features.class); Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -1043,22 +1012,8 @@ public class XmppConnection implements Runnable { } } - private void processEnabled(final Element enabled) { - final String id; - if (enabled.getAttributeAsBoolean("resume")) { - id = enabled.getAttribute("id"); - } else { - id = null; - } - final String locationAttribute = enabled.getAttribute("location"); - final Resolver.Result currentResolverResult = this.currentResolverResult; - final Resolver.Result location; - if (Strings.isNullOrEmpty(locationAttribute) || currentResolverResult == null) { - location = null; - } else { - location = currentResolverResult.seeOtherHost(locationAttribute); - } - final StreamId streamId = id == null ? null : new StreamId(id, location); + private void processEnabled(final Enabled enabled) { + final StreamId streamId = getStreamId(enabled); if (streamId == null) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": stream management enabled"); } else { @@ -1071,16 +1026,30 @@ public class XmppConnection implements Runnable { this.streamId = streamId; this.stanzasReceived = 0; this.inSmacksSession = true; - final RequestPacket r = new RequestPacket(); + final var r = new Request(); tagWriter.writeStanzaAsync(r); } - private void processResumed(final Element resumed) throws StateChangingException { + @Nullable + private StreamId getStreamId(final Enabled enabled) { + final Optional id = enabled.getResumeId(); + final String locationAttribute = enabled.getLocation(); + final Resolver.Result currentResolverResult = this.currentResolverResult; + final Resolver.Result location; + if (Strings.isNullOrEmpty(locationAttribute) || currentResolverResult == null) { + location = null; + } else { + location = currentResolverResult.seeOtherHost(locationAttribute); + } + return id.isPresent() ? new StreamId(id.get(), location) : null; + } + + private void processResumed(final Resumed resumed) throws StateChangingException { this.inSmacksSession = true; this.isBound = true; - this.tagWriter.writeStanzaAsync(new RequestPacket()); + this.tagWriter.writeStanzaAsync(new Request()); lastPacketReceived = SystemClock.elapsedRealtime(); - final Optional h = resumed.getOptionalIntAttribute("h"); + final Optional h = resumed.getHandled(); final int serverCount; if (h.isPresent()) { serverCount = h.get(); @@ -1088,7 +1057,7 @@ public class XmppConnection implements Runnable { resetStreamId(); throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } - final ArrayList failedStanzas = new ArrayList<>(); + final ArrayList failedStanzas = new ArrayList<>(); final boolean acknowledgedMessages; synchronized (this.mStanzaQueue) { if (serverCount < stanzasSent) { @@ -1111,8 +1080,8 @@ public class XmppConnection implements Runnable { Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": resending " + failedStanzas.size() + " stanzas"); - for (final AbstractAcknowledgeableStanza packet : failedStanzas) { - if (packet instanceof MessagePacket message) { + for (final Stanza packet : failedStanzas) { + if (packet instanceof im.conversations.android.xmpp.model.stanza.Message message) { mXmppConnectionService.markMessage( account, message.getTo().asBareJid(), @@ -1131,8 +1100,8 @@ public class XmppConnection implements Runnable { changeStatus(Account.State.ONLINE); } - private void processFailed(final Element failed, final boolean sendBindRequest) { - final Optional serverCount = failed.getOptionalIntAttribute("h"); + private void processFailed(final Failed failed, final boolean sendBindRequest) { + final Optional serverCount = failed.getHandled(); if (serverCount.isPresent()) { Log.d( Config.LOGTAG, @@ -1179,8 +1148,9 @@ public class XmppConnection implements Runnable { + ": server acknowledged stanza #" + mStanzaQueue.keyAt(i)); } - final AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i); - if (stanza instanceof MessagePacket packet && acknowledgedListener != null) { + final Stanza stanza = mStanzaQueue.valueAt(i); + if (stanza instanceof im.conversations.android.xmpp.model.stanza.Message packet + && acknowledgedListener != null) { final String id = packet.getId(); final Jid to = packet.getTo(); if (id != null && to != null) { @@ -1195,29 +1165,9 @@ public class XmppConnection implements Runnable { return acknowledgedMessages; } - private @NonNull Element processPacket(final Tag currentTag, final int packetType) + private @NonNull S processPacket(final Tag currentTag, final Class clazz) throws IOException { - final Element element = - switch (packetType) { - case PACKET_IQ -> new IqPacket(); - case PACKET_MESSAGE -> new MessagePacket(); - case PACKET_PRESENCE -> new PresencePacket(); - default -> throw new AssertionError("Should never encounter invalid type"); - }; - element.setAttributes(currentTag.getAttributes()); - Tag nextTag = tagReader.readTag(); - if (nextTag == null) { - throw new IOException("interrupted mid tag"); - } - while (!nextTag.isEnd(element.getName())) { - if (!nextTag.isNo()) { - element.addChild(tagReader.readElement(nextTag)); - } - nextTag = tagReader.readTag(); - if (nextTag == null) { - throw new IOException("interrupted mid tag"); - } - } + final S stanza = tagReader.readElement(currentTag, clazz); if (stanzasReceived == Integer.MAX_VALUE) { resetStreamId(); throw new IOException("time to restart the session. cant handle >2 billion pcks"); @@ -1229,25 +1179,19 @@ public class XmppConnection implements Runnable { Config.LOGTAG, account.getJid().asBareJid() + ": not counting stanza(" - + element.getClass().getSimpleName() + + stanza.getClass().getSimpleName() + "). Not in smacks session."); } lastPacketReceived = SystemClock.elapsedRealtime(); if (Config.BACKGROUND_STANZA_LOGGING && mXmppConnectionService.checkListeners()) { - Log.d(Config.LOGTAG, "[background stanza] " + element); - } - if (element instanceof IqPacket - && (((IqPacket) element).getType() == IqPacket.TYPE.SET) - && element.hasChild("jingle", Namespace.JINGLE)) { - return JinglePacket.upgrade((IqPacket) element); - } else { - return element; + Log.d(Config.LOGTAG, "[background stanza] " + stanza); } + return stanza; } private void processIq(final Tag currentTag) throws IOException { - final IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ); - if (!packet.valid()) { + final Iq packet = processPacket(currentTag, Iq.class); + if (packet.isInvalid()) { Log.e( Config.LOGTAG, "encountered invalid iq from='" @@ -1263,12 +1207,12 @@ public class XmppConnection implements Runnable { account.getJid().asBareJid() + "Not processing iq. Thread was interrupted"); return; } - if (packet instanceof JinglePacket jinglePacket && isBound) { + if (packet.hasExtension(Jingle.class) && packet.getType() == Iq.Type.SET && isBound) { if (this.jingleListener != null) { - this.jingleListener.onJinglePacketReceived(account, jinglePacket); + this.jingleListener.onJinglePacketReceived(account, packet); } } else { - final OnIqPacketReceived callback = getIqPacketReceivedCallback(packet); + final var callback = getIqPacketReceivedCallback(packet); if (callback == null) { Log.d( Config.LOGTAG, @@ -1278,17 +1222,17 @@ public class XmppConnection implements Runnable { return; } try { - callback.onIqPacketReceived(account, packet); + callback.accept(packet); } catch (final StateChangingError error) { throw new StateChangingException(error.state); } } } - private OnIqPacketReceived getIqPacketReceivedCallback(final IqPacket stanza) + private Consumer getIqPacketReceivedCallback(final Iq stanza) throws StateChangingException { final boolean isRequest = - stanza.getType() == IqPacket.TYPE.GET || stanza.getType() == IqPacket.TYPE.SET; + stanza.getType() == Iq.Type.GET || stanza.getType() == Iq.Type.SET; if (isRequest) { if (isBound) { return this.unregisteredIqListener; @@ -1328,8 +1272,9 @@ public class XmppConnection implements Runnable { } private void processMessage(final Tag currentTag) throws IOException { - final MessagePacket packet = (MessagePacket) processPacket(currentTag, PACKET_MESSAGE); - if (!packet.valid()) { + final var packet = + processPacket(currentTag, im.conversations.android.xmpp.model.stanza.Message.class); + if (packet.isInvalid()) { Log.e( Config.LOGTAG, "encountered invalid message from='" @@ -1346,12 +1291,12 @@ public class XmppConnection implements Runnable { + "Not processing message. Thread was interrupted"); return; } - this.messageListener.onMessagePacketReceived(account, packet); + this.messageListener.accept(packet); } private void processPresence(final Tag currentTag) throws IOException { - final PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE); - if (!packet.valid()) { + final var packet = processPacket(currentTag, Presence.class); + if (packet.isInvalid()) { Log.e( Config.LOGTAG, "encountered invalid presence from='" @@ -1368,17 +1313,15 @@ public class XmppConnection implements Runnable { + "Not processing presence. Thread was interrupted"); return; } - this.presenceListener.onPresencePacketReceived(account, packet); + this.presenceListener.accept(packet); } private void sendStartTLS() throws IOException { - final Tag startTLS = Tag.empty("starttls"); - startTLS.setAttribute("xmlns", Namespace.TLS); - tagWriter.writeTag(startTLS); + tagWriter.writeElement(new StartTls()); } - private void switchOverToTls() throws XmlPullParserException, IOException { - tagReader.readTag(); + private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException { + tagReader.readElement(currentTag, Proceed.class); final Socket socket = this.socket; final SSLSocket sslSocket = upgradeSocketToTls(socket); this.socket = sslSocket; @@ -1439,11 +1382,13 @@ public class XmppConnection implements Runnable { } private void processStreamFeatures(final Tag currentTag) throws IOException { - this.streamFeatures = tagReader.readElement(currentTag); + this.streamFeatures = + tagReader.readElement( + currentTag, im.conversations.android.xmpp.model.streams.Features.class); final boolean isSecure = isSecure(); final boolean needsBinding = !isBound && !account.isOptionSet(Account.OPTION_REGISTER); if (this.quickStartInProgress) { - if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2)) { + if (this.streamFeatures.hasStreamFeature(Authentication.class)) { Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -1452,8 +1397,7 @@ public class XmppConnection implements Runnable { if (SaslMechanism.hashedToken(LoginInfo.mechanism(this.loginInfo))) { return; } - if (isFastTokenAvailable( - this.streamFeatures.findChild("authentication", Namespace.SASL_2))) { + if (isFastTokenAvailable(this.streamFeatures.getExtension(Authentication.class))) { Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -1471,8 +1415,7 @@ public class XmppConnection implements Runnable { mXmppConnectionService.databaseBackend.updateAccount(account); throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } - if (this.streamFeatures.hasChild("starttls", Namespace.TLS) - && !features.encryptionEnabled) { + if (this.streamFeatures.hasExtension(StartTls.class) && !features.encryptionEnabled) { sendStartTLS(); } else if (this.streamFeatures.hasChild("register", Namespace.REGISTER_STREAM_FEATURE) && account.isOptionSet(Account.OPTION_REGISTER)) { @@ -1489,15 +1432,15 @@ public class XmppConnection implements Runnable { } else if (!this.streamFeatures.hasChild("register", Namespace.REGISTER_STREAM_FEATURE) && account.isOptionSet(Account.OPTION_REGISTER)) { throw new StateChangingException(Account.State.REGISTRATION_NOT_SUPPORTED); - } else if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2) + } else if (this.streamFeatures.hasStreamFeature(Authentication.class) && shouldAuthenticate && isSecure) { authenticate(SaslMechanism.Version.SASL_2); - } else if (this.streamFeatures.hasChild("mechanisms", Namespace.SASL) + } else if (this.streamFeatures.hasStreamFeature(Mechanisms.class) && shouldAuthenticate && isSecure) { authenticate(SaslMechanism.Version.SASL); - } else if (this.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT) + } else if (this.streamFeatures.streamManagement() && isSecure && LoginInfo.isSuccess(loginInfo) && streamId != null @@ -1509,7 +1452,7 @@ public class XmppConnection implements Runnable { + ": resuming after stanza #" + stanzasReceived); } - final ResumePacket resume = new ResumePacket(this.streamId.id, stanzasReceived); + final var resume = new Resume(this.streamId.id, stanzasReceived); this.mSmCatchupMessageCounter.set(0); this.mWaitingForSmCatchup.set(true); this.tagWriter.writeStanzaAsync(resume); @@ -1537,9 +1480,9 @@ public class XmppConnection implements Runnable { private void authenticate() throws IOException { final boolean isSecure = isSecure(); - if (isSecure && this.streamFeatures.hasChild("authentication", Namespace.SASL_2)) { + if (isSecure && this.streamFeatures.hasStreamFeature(Authentication.class)) { authenticate(SaslMechanism.Version.SASL_2); - } else if (isSecure && this.streamFeatures.hasChild("mechanisms", Namespace.SASL)) { + } else if (isSecure && this.streamFeatures.hasStreamFeature(Mechanisms.class)) { authenticate(SaslMechanism.Version.SASL); } else { throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); @@ -1551,13 +1494,13 @@ public class XmppConnection implements Runnable { } private void authenticate(final SaslMechanism.Version version) throws IOException { - final Element authElement; + final AuthenticationStreamFeature authElement; if (version == SaslMechanism.Version.SASL) { - authElement = this.streamFeatures.findChild("mechanisms", Namespace.SASL); + authElement = this.streamFeatures.getExtension(Mechanisms.class); } else { - authElement = this.streamFeatures.findChild("authentication", Namespace.SASL_2); + authElement = this.streamFeatures.getExtension(Authentication.class); } - final Collection mechanisms = SaslMechanism.mechanisms(authElement); + final Collection mechanisms = authElement.getMechanismNames(); final Element cbElement = this.streamFeatures.findChild("sasl-channel-binding", Namespace.CHANNEL_BINDING); final Collection channelBindings = ChannelBinding.of(cbElement); @@ -1569,26 +1512,27 @@ public class XmppConnection implements Runnable { final String firstMessage = saslMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)); final boolean usingFast = SaslMechanism.hashedToken(saslMechanism); - final Element authenticate; + final StreamElement authenticate; if (version == SaslMechanism.Version.SASL) { - authenticate = new Element("auth", Namespace.SASL); + authenticate = new Auth(); if (!Strings.isNullOrEmpty(firstMessage)) { authenticate.setContent(firstMessage); } quickStartAvailable = false; this.loginInfo = new LoginInfo(saslMechanism, version, Collections.emptyList()); } else if (version == SaslMechanism.Version.SASL_2) { - final Element inline = authElement.findChild("inline", Namespace.SASL_2); - final boolean sm = inline != null && inline.hasChild("sm", Namespace.STREAM_MANAGEMENT); + final Authentication authentication = (Authentication) authElement; + final var inline = authentication.getInline(); + final boolean sm = inline != null && inline.hasExtension(StreamManagement.class); final HashedToken.Mechanism hashTokenRequest; if (usingFast) { hashTokenRequest = null; - } else { - final Element fast = - inline == null ? null : inline.findChild("fast", Namespace.FAST); - final Collection fastMechanisms = SaslMechanism.mechanisms(fast); + } else if (inline != null) { hashTokenRequest = - HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket)); + HashedToken.Mechanism.best( + inline.getFastMechanisms(), SSLSockets.version(this.socket)); + } else { + hashTokenRequest = null; } final Collection bindFeatures = Bind2.features(inline); quickStartAvailable = @@ -1633,9 +1577,9 @@ public class XmppConnection implements Runnable { } } - private static boolean isFastTokenAvailable(final Element authentication) { - final Element inline = authentication == null ? null : authentication.findChild("inline"); - return inline != null && inline.hasChild("fast", Namespace.FAST); + private static boolean isFastTokenAvailable(final Authentication authentication) { + final var inline = authentication == null ? null : authentication.getInline(); + return inline != null && inline.hasExtension(Fast.class); } private void validate( @@ -1649,7 +1593,7 @@ public class XmppConnection implements Runnable { + mechanisms); throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } - validateRequireChannelBinding(saslMechanism); + checkRequireChannelBinding(saslMechanism); if (SaslMechanism.hashedToken(saslMechanism)) { return; } @@ -1668,7 +1612,7 @@ public class XmppConnection implements Runnable { } } - private void validateRequireChannelBinding(@NonNull final SaslMechanism mechanism) + private void checkRequireChannelBinding(@NonNull final SaslMechanism mechanism) throws StateChangingException { if (appSettings.isRequireChannelBinding()) { if (mechanism instanceof ChannelBindingMechanism) { @@ -1679,19 +1623,44 @@ public class XmppConnection implements Runnable { } } - private Element generateAuthenticationRequest( + private void checkAssignedDomainOrThrow(final Jid jid) throws StateChangingException { + if (jid == null) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": bind response is missing jid"); + throw new StateChangingException(Account.State.BIND_FAILURE); + } + final var current = this.account.getJid().getDomain(); + if (jid.getDomain().equals(current)) { + return; + } + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": server tried to re-assign domain to " + + jid.getDomain()); + throw new StateChangingException(Account.State.BIND_FAILURE); + } + + private void checkAssignedDomain(final Jid jid) { + try { + checkAssignedDomainOrThrow(jid); + } catch (final StateChangingException e) { + throw new StateChangingError(e.state); + } + } + + private Authenticate generateAuthenticationRequest( final String firstMessage, final boolean usingFast) { return generateAuthenticationRequest( firstMessage, usingFast, null, Bind2.QUICKSTART_FEATURES, true); } - private Element generateAuthenticationRequest( + private Authenticate generateAuthenticationRequest( final String firstMessage, final boolean usingFast, final HashedToken.Mechanism hashedTokenRequest, final Collection bind, final boolean inlineStreamManagement) { - final Element authenticate = new Element("authenticate", Namespace.SASL_2); + final var authenticate = new Authenticate(); if (!Strings.isNullOrEmpty(firstMessage)) { authenticate.addChild("initial-response").setContent(firstMessage); } @@ -1712,31 +1681,29 @@ public class XmppConnection implements Runnable { authenticate.addChild(generateBindRequest(bind)); } if (inlineStreamManagement && streamId != null) { - final ResumePacket resume = new ResumePacket(this.streamId.id, stanzasReceived); + final var resume = new Resume(this.streamId.id, stanzasReceived); this.mSmCatchupMessageCounter.set(0); this.mWaitingForSmCatchup.set(true); - authenticate.addChild(resume); + authenticate.addExtension(resume); } if (hashedTokenRequest != null) { - authenticate - .addChild("request-token", Namespace.FAST) - .setAttribute("mechanism", hashedTokenRequest.name()); + authenticate.addExtension(new RequestToken(hashedTokenRequest)); } if (usingFast) { - authenticate.addChild("fast", Namespace.FAST); + authenticate.addExtension(new Fast()); } return authenticate; } - private Element generateBindRequest(final Collection bindFeatures) { + private Bind generateBindRequest(final Collection bindFeatures) { Log.d(Config.LOGTAG, "inline bind features: " + bindFeatures); - final Element bind = new Element("bind", Namespace.BIND2); + final var bind = new Bind(); bind.addChild("tag").setContent(mXmppConnectionService.getString(R.string.app_name)); if (bindFeatures.contains(Namespace.CARBONS)) { - bind.addChild("enable", Namespace.CARBONS); + bind.addExtension(new im.conversations.android.xmpp.model.carbons.Enable()); } if (bindFeatures.contains(Namespace.STREAM_MANAGEMENT)) { - bind.addChild(new EnablePacket()); + bind.addExtension(new Enable()); } return bind; } @@ -1744,12 +1711,12 @@ public class XmppConnection implements Runnable { private void register() { final String preAuth = account.getKey(Account.KEY_PRE_AUTH_REGISTRATION_TOKEN); if (preAuth != null && features.invite()) { - final IqPacket preAuthRequest = new IqPacket(IqPacket.TYPE.SET); + final Iq preAuthRequest = new Iq(Iq.Type.SET); preAuthRequest.addChild("preauth", Namespace.PARS).setAttribute("token", preAuth); sendUnmodifiedIqPacket( preAuthRequest, - (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { sendRegistryRequest(); } else { final String error = response.getErrorCondition(); @@ -1768,21 +1735,21 @@ public class XmppConnection implements Runnable { } private void sendRegistryRequest() { - final IqPacket register = new IqPacket(IqPacket.TYPE.GET); + final Iq register = new Iq(Iq.Type.GET); register.query(Namespace.REGISTER); register.setTo(account.getDomain()); sendUnmodifiedIqPacket( register, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + (packet) -> { + if (packet.getType() == Iq.Type.TIMEOUT) { return; } - if (packet.getType() == IqPacket.TYPE.ERROR) { + if (packet.getType() == Iq.Type.ERROR) { throw new StateChangingError(Account.State.REGISTRATION_FAILED); } final Element query = packet.query(Namespace.REGISTER); if (query.hasChild("username") && (query.hasChild("password"))) { - final IqPacket register1 = new IqPacket(IqPacket.TYPE.SET); + final Iq register1 = new Iq(Iq.Type.SET); final Element username = new Element("username").setContent(account.getUsername()); final Element password = @@ -1790,7 +1757,7 @@ public class XmppConnection implements Runnable { register1.query(Namespace.REGISTER).addChild(username); register1.query().addChild(password); register1.setFrom(account.getJid().asBareJid()); - sendUnmodifiedIqPacket(register1, registrationResponseListener, true); + sendUnmodifiedIqPacket(register1, this::processRegistrationResponse, true); } else if (query.hasChild("x", Namespace.DATA)) { final Data data = Data.parse(query.findChild("x", Namespace.DATA)); final Element blob = query.findChild("data", "urn:xmpp:bob"); @@ -1858,6 +1825,45 @@ public class XmppConnection implements Runnable { true); } + public void sendCreateAccountWithCaptchaPacket(final String id, final Data data) { + final Iq request = IqGenerator.generateCreateAccountWithCaptcha(account, id, data); + this.sendUnmodifiedIqPacket(request, this::processRegistrationResponse, true); + } + + private void processRegistrationResponse(final Iq response) { + if (response.getType() == Iq.Type.RESULT) { + account.setOption(Account.OPTION_REGISTER, false); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": successfully registered new account on server"); + throw new StateChangingError(Account.State.REGISTRATION_SUCCESSFUL); + } else { + final Account.State state = getRegistrationFailedState(response); + throw new StateChangingError(state); + } + } + + @NonNull + private static Account.State getRegistrationFailedState(final Iq response) { + final List PASSWORD_TOO_WEAK_MESSAGES = + Arrays.asList("The password is too weak", "Please use a longer password."); + final var error = response.getError(); + final var condition = error == null ? null : error.getCondition(); + final Account.State state; + if (condition instanceof Condition.Conflict) { + state = Account.State.REGISTRATION_CONFLICT; + } else if (condition instanceof Condition.ResourceConstraint) { + state = Account.State.REGISTRATION_PLEASE_WAIT; + } else if (condition instanceof Condition.NotAcceptable + && PASSWORD_TOO_WEAK_MESSAGES.contains(error.getTextAsString())) { + state = Account.State.REGISTRATION_PASSWORD_TOO_WEAK; + } else { + state = Account.State.REGISTRATION_FAILED; + } + return state; + } + private void setAccountCreationFailed(final String url) { final HttpUrl httpUrl = url == null ? null : HttpUrl.parse(url); if (httpUrl != null && httpUrl.isHttps()) { @@ -1905,61 +1911,36 @@ public class XmppConnection implements Runnable { } else { fixResource(mXmppConnectionService, account); } - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + final Iq iq = new Iq(Iq.Type.SET); final String resource = Config.USE_RANDOM_RESOURCE_ON_EVERY_BIND ? nextRandomId() : account.getResource(); - iq.addChild("bind", Namespace.BIND).addChild("resource").setContent(resource); + iq.addExtension(new im.conversations.android.xmpp.model.bind.Bind()).setResource(resource); this.sendUnmodifiedIqPacket( iq, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + (packet) -> { + if (packet.getType() == Iq.Type.TIMEOUT) { return; } - final Element bind = packet.findChild("bind"); - if (bind != null && packet.getType() == IqPacket.TYPE.RESULT) { + final var bind = + packet.getExtension( + im.conversations.android.xmpp.model.bind.Bind.class); + if (bind != null && packet.getType() == Iq.Type.RESULT) { isBound = true; - final Element jid = bind.findChild("jid"); - if (jid != null && jid.getContent() != null) { - try { - Jid assignedJid = Jid.ofEscaped(jid.getContent()); - if (!account.getJid().getDomain().equals(assignedJid.getDomain())) { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": server tried to re-assign domain to " - + assignedJid.getDomain()); - throw new StateChangingError(Account.State.BIND_FAILURE); - } - if (account.setJid(assignedJid)) { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": jid changed during bind. updating database"); - mXmppConnectionService.databaseBackend.updateAccount(account); - } - if (streamFeatures.hasChild("session") - && !streamFeatures - .findChild("session") - .hasChild("optional")) { - sendStartSession(); - } else { - final boolean waitForDisco = enableStreamManagement(); - sendPostBindInitialization(waitForDisco, false); - } - return; - } catch (final IllegalArgumentException e) { - Log.d( - Config.LOGTAG, - account.getJid().asBareJid() - + ": server reported invalid jid (" - + jid.getContent() - + ") on bind"); - } - } else { + final Jid assignedJid = bind.getJid(); + checkAssignedDomain(assignedJid); + if (account.setJid(assignedJid)) { Log.d( Config.LOGTAG, - account.getJid() - + ": disconnecting because of bind failure. (no jid)"); + account.getJid().asBareJid() + + ": jid changed during bind. updating database"); + mXmppConnectionService.databaseBackend.updateAccount(account); + } + if (streamFeatures.hasChild("session") + && !streamFeatures.findChild("session").hasChild("optional")) { + sendStartSession(); + } else { + final boolean waitForDisco = enableStreamManagement(); + sendPostBindInitialization(waitForDisco, false); } } else { Log.d( @@ -1967,23 +1948,24 @@ public class XmppConnection implements Runnable { account.getJid() + ": disconnecting because of bind failure (" + packet); + final var error = packet.getError(); + // TODO error.is(Condition) + if (packet.getType() == Iq.Type.ERROR + && error != null + && error.hasChild("conflict")) { + account.setResource(createNewResource()); + } + throw new StateChangingError(Account.State.BIND_FAILURE); } - final Element error = packet.findChild("error"); - if (packet.getType() == IqPacket.TYPE.ERROR - && error != null - && error.hasChild("conflict")) { - account.setResource(createNewResource()); - } - throw new StateChangingError(Account.State.BIND_FAILURE); }, true); } private void clearIqCallbacks() { - final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.TIMEOUT); - final ArrayList callbacks = new ArrayList<>(); + final Iq failurePacket = new Iq(Iq.Type.TIMEOUT); + final ArrayList> callbacks = new ArrayList<>(); synchronized (this.packetCallbacks) { - if (this.packetCallbacks.size() == 0) { + if (this.packetCallbacks.isEmpty()) { return; } Log.d( @@ -1992,17 +1974,16 @@ public class XmppConnection implements Runnable { + ": clearing " + this.packetCallbacks.size() + " iq callbacks"); - final Iterator> iterator = - this.packetCallbacks.values().iterator(); + final var iterator = this.packetCallbacks.values().iterator(); while (iterator.hasNext()) { - Pair entry = iterator.next(); + final var entry = iterator.next(); callbacks.add(entry.second); iterator.remove(); } } - for (OnIqPacketReceived callback : callbacks) { + for (final var callback : callbacks) { try { - callback.onIqPacketReceived(account, failurePacket); + callback.accept(failurePacket); } catch (StateChangingError error) { Log.d( Config.LOGTAG, @@ -2034,15 +2015,15 @@ public class XmppConnection implements Runnable { Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": sending legacy session to outdated server"); - final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET); + final Iq startSession = new Iq(Iq.Type.SET); startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session"); this.sendUnmodifiedIqPacket( startSession, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { final boolean waitForDisco = enableStreamManagement(); sendPostBindInitialization(waitForDisco, false); - } else if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + } else if (packet.getType() != Iq.Type.TIMEOUT) { throw new StateChangingError(Account.State.SESSION_FAILURE); } }, @@ -2050,11 +2031,10 @@ public class XmppConnection implements Runnable { } private boolean enableStreamManagement() { - final boolean streamManagement = - this.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT); + final boolean streamManagement = this.streamFeatures.streamManagement(); if (streamManagement) { synchronized (this.mStanzaQueue) { - final EnablePacket enable = new EnablePacket(); + final var enable = new Enable(); tagWriter.writeStanzaAsync(enable); stanzasSent = 0; mStanzaQueue.clear(); @@ -2111,13 +2091,13 @@ public class XmppConnection implements Runnable { private void sendServiceDiscoveryInfo(final Jid jid) { mPendingServiceDiscoveries.incrementAndGet(); - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + final Iq iq = new Iq(Iq.Type.GET); iq.setTo(jid); iq.query("http://jabber.org/protocol/disco#info"); this.sendIqPacket( iq, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { boolean advancedStreamFeaturesLoaded; synchronized (XmppConnection.this.disco) { ServiceDiscoveryResult result = new ServiceDiscoveryResult(packet); @@ -2135,7 +2115,7 @@ public class XmppConnection implements Runnable { || jid.equals(account.getJid().asBareJid()))) { enableAdvancedStreamFeatures(); } - } else if (packet.getType() == IqPacket.TYPE.ERROR) { + } else if (packet.getType() == Iq.Type.ERROR) { Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -2159,7 +2139,7 @@ public class XmppConnection implements Runnable { enableAdvancedStreamFeatures(); } } - if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + if (packet.getType() != Iq.Type.TIMEOUT) { if (mPendingServiceDiscoveries.decrementAndGet() == 0 && mWaitForDisco.compareAndSet(true, false)) { finalizeBind(); @@ -2169,12 +2149,12 @@ public class XmppConnection implements Runnable { } private void discoverMamPreferences() { - IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.addChild("prefs", MessageArchiveService.Version.MAM_2.namespace); sendIqPacket( request, - (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { Element prefs = response.findChild( "prefs", MessageArchiveService.Version.MAM_2.namespace); @@ -2189,13 +2169,13 @@ public class XmppConnection implements Runnable { } private void discoverCommands() { - final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.setTo(account.getDomain()); request.addChild("query", Namespace.DISCO_ITEMS).setAttribute("node", Namespace.COMMANDS); sendIqPacket( request, - (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element query = response.findChild("query", Namespace.DISCO_ITEMS); if (query == null) { return; @@ -2224,17 +2204,14 @@ public class XmppConnection implements Runnable { private void finalizeBind() { this.offlineMessagesRetrieved = false; - if (bindListener != null) { - bindListener.onBind(account); - } - changeStatusToOnline(); + this.bindListener.run(); + this.changeStatusToOnline(); } private void enableAdvancedStreamFeatures() { if (getFeatures().blocking() && !features.blockListRequested) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": Requesting block list"); - this.sendIqPacket( - getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser()); + this.sendIqPacket(getIqGenerator().generateGetBlockList(), unregisteredIqListener); } for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) { @@ -2250,13 +2227,13 @@ public class XmppConnection implements Runnable { private void sendServiceDiscoveryItems(final Jid server) { mPendingServiceDiscoveries.incrementAndGet(); - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + final Iq iq = new Iq(Iq.Type.GET); iq.setTo(server.getDomain()); iq.query("http://jabber.org/protocol/disco#items"); this.sendIqPacket( iq, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { final HashSet items = new HashSet<>(); final List elements = packet.query().getChildren(); for (final Element element : elements) { @@ -2279,7 +2256,7 @@ public class XmppConnection implements Runnable { + ": could not query disco items of " + server); } - if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + if (packet.getType() != Iq.Type.TIMEOUT) { if (mPendingServiceDiscoveries.decrementAndGet() == 0 && mWaitForDisco.compareAndSet(true, false)) { finalizeBind(); @@ -2289,12 +2266,12 @@ public class XmppConnection implements Runnable { } private void sendEnableCarbons() { - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + final Iq iq = new Iq(Iq.Type.SET); iq.addChild("enable", Namespace.CARBONS); this.sendIqPacket( iq, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": successfully enabled carbons"); @@ -2363,8 +2340,8 @@ public class XmppConnection implements Runnable { private void failPendingMessages(final String error) { synchronized (this.mStanzaQueue) { for (int i = 0; i < mStanzaQueue.size(); ++i) { - final AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i); - if (stanza instanceof MessagePacket packet) { + final Stanza stanza = mStanzaQueue.valueAt(i); + if (stanza instanceof im.conversations.android.xmpp.model.stanza.Message packet) { final String id = packet.getId(); final Jid to = packet.getTo(); mXmppConnectionService.markMessage( @@ -2398,7 +2375,7 @@ public class XmppConnection implements Runnable { SaslMechanism.Version.SASL_2, Bind2.QUICKSTART_FEATURES); final boolean usingFast = quickStartMechanism instanceof HashedToken; - final Element authenticate = + final StreamElement authenticate = generateAuthenticationRequest( quickStartMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)), usingFast); @@ -2445,13 +2422,14 @@ public class XmppConnection implements Runnable { return CryptoHelper.random(s ? 3 : 9); } - public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { + public String sendIqPacket(final Iq packet, final Consumer callback) { packet.setFrom(account.getJid()); return this.sendUnmodifiedIqPacket(packet, callback, false); } public synchronized String sendUnmodifiedIqPacket( - final IqPacket packet, final OnIqPacketReceived callback, boolean force) { + final Iq packet, final Consumer callback, boolean force) { + // TODO if callback != null verify that type is get or set if (packet.getId() == null) { packet.setAttribute("id", nextRandomId()); } @@ -2464,19 +2442,19 @@ public class XmppConnection implements Runnable { return packet.getId(); } - public void sendMessagePacket(final MessagePacket packet) { + public void sendMessagePacket(final im.conversations.android.xmpp.model.stanza.Message packet) { this.sendPacket(packet); } - public void sendPresencePacket(final PresencePacket packet) { + public void sendPresencePacket(final Presence packet) { this.sendPacket(packet); } - private synchronized void sendPacket(final AbstractStanza packet) { + private synchronized void sendPacket(final StreamElement packet) { sendPacket(packet, false); } - private synchronized void sendPacket(final AbstractStanza packet, final boolean force) { + private synchronized void sendPacket(final StreamElement packet, final boolean force) { if (stanzasSent == Integer.MAX_VALUE) { resetStreamId(); disconnect(true); @@ -2492,7 +2470,7 @@ public class XmppConnection implements Runnable { + " do not write stanza to unbound stream " + packet.toString()); } - if (packet instanceof AbstractAcknowledgeableStanza stanza) { + if (packet instanceof Stanza stanza) { if (this.mStanzaQueue.size() != 0) { int currentHighestKey = this.mStanzaQueue.keyAt(this.mStanzaQueue.size() - 1); if (currentHighestKey != stanzasSent) { @@ -2511,7 +2489,9 @@ public class XmppConnection implements Runnable { + stanzasSent); } this.mStanzaQueue.append(stanzasSent, stanza); - if (stanza instanceof MessagePacket && stanza.getId() != null && inSmacksSession) { + if (stanza instanceof im.conversations.android.xmpp.model.stanza.Message + && stanza.getId() != null + && inSmacksSession) { if (Config.EXTENDED_SM_LOGGING) { Log.d( Config.LOGTAG, @@ -2519,7 +2499,7 @@ public class XmppConnection implements Runnable { + ": requesting ack for message stanza #" + stanzasSent); } - tagWriter.writeStanzaAsync(new RequestPacket()); + tagWriter.writeStanzaAsync(new Request()); } } } @@ -2527,7 +2507,7 @@ public class XmppConnection implements Runnable { public void sendPing() { if (!r()) { - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + final Iq iq = new Iq(Iq.Type.GET); iq.setFrom(account.getJid()); iq.addChild("ping", Namespace.PING); this.sendIqPacket(iq, null); @@ -2535,18 +2515,6 @@ public class XmppConnection implements Runnable { this.lastPingSent = SystemClock.elapsedRealtime(); } - public void setOnMessagePacketReceivedListener(final OnMessagePacketReceived listener) { - this.messageListener = listener; - } - - public void setOnUnregisteredIqPacketReceivedListener(final OnIqPacketReceived listener) { - this.unregisteredIqListener = listener; - } - - public void setOnPresencePacketReceivedListener(final OnPresencePacketReceived listener) { - this.presenceListener = listener; - } - public void setOnJinglePacketReceivedListener(final OnJinglePacketReceived listener) { this.jingleListener = listener; } @@ -2555,10 +2523,6 @@ public class XmppConnection implements Runnable { this.statusListener = listener; } - public void setOnBindListener(final OnBindListener listener) { - this.bindListener = listener; - } - public void setOnMessageAcknowledgeListener(final OnMessageAcknowledged listener) { this.acknowledgedListener = listener; } @@ -2654,7 +2618,7 @@ public class XmppConnection implements Runnable { public boolean r() { if (getFeatures().sm()) { - this.tagWriter.writeStanzaAsync(new RequestPacket()); + this.tagWriter.writeStanzaAsync(new Request()); return true; } else { return false; @@ -2732,11 +2696,11 @@ public class XmppConnection implements Runnable { } public void sendActive() { - this.sendPacket(new ActivePacket()); + this.sendPacket(new Active()); } public void sendInactive() { - this.sendPacket(new InactivePacket()); + this.sendPacket(new Inactive()); } public void resetAttemptCount(boolean resetConnectTime) { @@ -2756,11 +2720,11 @@ public class XmppConnection implements Runnable { public void trackOfflineMessageRetrieval(boolean trackOfflineMessageRetrieval) { if (trackOfflineMessageRetrieval) { - final IqPacket iqPing = new IqPacket(IqPacket.TYPE.GET); + final Iq iqPing = new Iq(Iq.Type.GET); iqPing.addChild("ping", Namespace.PING); this.sendIqPacket( iqPing, - (a, response) -> { + (response) -> { Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -2776,6 +2740,20 @@ public class XmppConnection implements Runnable { return this.offlineMessagesRetrieved; } + public void fetchRoster() { + final Iq iqPacket = new Iq(Iq.Type.GET); + final var version = account.getRosterVersion(); + if (Strings.isNullOrEmpty(account.getRosterVersion())) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching roster"); + } else { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + ": fetching roster version " + version); + } + iqPacket.query(Namespace.ROSTER).setAttribute("ver", version); + sendIqPacket(iqPacket, unregisteredIqListener); + } + private class MyKeyManager implements X509KeyManager { @Override public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { @@ -2957,13 +2935,12 @@ public class XmppConnection implements Runnable { public boolean sm() { return streamId != null || (connection.streamFeatures != null - && connection.streamFeatures.hasChild( - "sm", Namespace.STREAM_MANAGEMENT)); + && connection.streamFeatures.streamManagement()); } public boolean csi() { return connection.streamFeatures != null - && connection.streamFeatures.hasChild("csi", Namespace.CSI); + && connection.streamFeatures.clientStateIndication(); } public boolean pep() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractContentMap.java index 847678a05753255f1bd600ef928403d0e289038e..2c3c3f762695e712f4f0a3011018ffb4949e1e3e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractContentMap.java @@ -8,7 +8,8 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.Content; import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; +import im.conversations.android.xmpp.model.jingle.Jingle; +import im.conversations.android.xmpp.model.stanza.Iq; import java.util.List; import java.util.Map; @@ -47,8 +48,9 @@ public abstract class AbstractContentMap< return ImmutableList.copyOf(contents.keySet()); } - JinglePacket toJinglePacket(final JinglePacket.Action action, final String sessionId) { - final JinglePacket jinglePacket = new JinglePacket(action, sessionId); + Iq toJinglePacket(final Jingle.Action action, final String sessionId) { + final Iq iq = new Iq(Iq.Type.SET); + final var jinglePacket = iq.addExtension(new Jingle(action, sessionId)); for (final Map.Entry> entry : this.contents.entrySet()) { final DescriptionTransport descriptionTransport = entry.getValue(); final Content content = @@ -65,7 +67,7 @@ public abstract class AbstractContentMap< if (this.group != null) { jinglePacket.addGroup(this.group); } - return jinglePacket; + return iq; } void requireContentDescriptions() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java index 6aeb348c11b126d35ab803809594a385dac027c5..7716792885eb154a8cb0d8a75b23ccad04f51f98 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java @@ -19,9 +19,9 @@ import eu.siacs.conversations.entities.Presence; import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.jingle.Jingle; +import im.conversations.android.xmpp.model.stanza.Iq; import java.util.Arrays; import java.util.Collection; @@ -184,10 +184,10 @@ public abstract class AbstractJingleConnection { return TERMINATED.contains(this.state); } - abstract void deliverPacket(JinglePacket jinglePacket); + abstract void deliverPacket(Iq jinglePacket); protected void receiveOutOfOrderAction( - final JinglePacket jinglePacket, final JinglePacket.Action action) { + final Iq jinglePacket, final Jingle.Action action) { Log.d( Config.LOGTAG, String.format( @@ -205,7 +205,7 @@ public abstract class AbstractJingleConnection { } } - protected void terminateWithOutOfOrder(final JinglePacket jinglePacket) { + protected void terminateWithOutOfOrder(final Iq jinglePacket) { Log.d( Config.LOGTAG, id.account.getJid().asBareJid() + ": terminating session with out-of-order"); @@ -235,37 +235,38 @@ public abstract class AbstractJingleConnection { if (previous != State.NULL && trigger != null) { trigger.accept(target); } - final JinglePacket jinglePacket = - new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId); + final var iq = new Iq(Iq.Type.SET); + final var jinglePacket = + iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId)); jinglePacket.setReason(reason, text); - send(jinglePacket); + send(iq); finish(); } - protected void send(final JinglePacket jinglePacket) { + protected void send(final Iq jinglePacket) { jinglePacket.setTo(id.with); xmppConnectionService.sendIqPacket(id.account, jinglePacket, this::handleIqResponse); } - protected void respondOk(final JinglePacket jinglePacket) { + protected void respondOk(final Iq jinglePacket) { xmppConnectionService.sendIqPacket( - id.account, jinglePacket.generateResponse(IqPacket.TYPE.RESULT), null); + id.account, jinglePacket.generateResponse(Iq.Type.RESULT), null); } - protected void respondWithTieBreak(final JinglePacket jinglePacket) { + protected void respondWithTieBreak(final Iq jinglePacket) { respondWithJingleError(jinglePacket, "tie-break", "conflict", "cancel"); } - protected void respondWithOutOfOrder(final JinglePacket jinglePacket) { + protected void respondWithOutOfOrder(final Iq jinglePacket) { respondWithJingleError(jinglePacket, "out-of-order", "unexpected-request", "wait"); } - protected void respondWithItemNotFound(final JinglePacket jinglePacket) { + protected void respondWithItemNotFound(final Iq jinglePacket) { respondWithJingleError(jinglePacket, null, "item-not-found", "cancel"); } private void respondWithJingleError( - final IqPacket original, + final Iq original, String jingleCondition, String condition, String conditionType) { @@ -273,18 +274,18 @@ public abstract class AbstractJingleConnection { id.account, original, jingleCondition, condition, conditionType); } - private synchronized void handleIqResponse(final Account account, final IqPacket response) { - if (response.getType() == IqPacket.TYPE.ERROR) { + private synchronized void handleIqResponse(final Iq response) { + if (response.getType() == Iq.Type.ERROR) { handleIqErrorResponse(response); return; } - if (response.getType() == IqPacket.TYPE.TIMEOUT) { + if (response.getType() == Iq.Type.TIMEOUT) { handleIqTimeoutResponse(response); } } - protected void handleIqErrorResponse(final IqPacket response) { - Preconditions.checkArgument(response.getType() == IqPacket.TYPE.ERROR); + protected void handleIqErrorResponse(final Iq response) { + Preconditions.checkArgument(response.getType() == Iq.Type.ERROR); final String errorCondition = response.getErrorCondition(); Log.d( Config.LOGTAG, @@ -316,8 +317,8 @@ public abstract class AbstractJingleConnection { this.finish(); } - protected void handleIqTimeoutResponse(final IqPacket response) { - Preconditions.checkArgument(response.getType() == IqPacket.TYPE.TIMEOUT); + protected void handleIqTimeoutResponse(final Iq response) { + Preconditions.checkArgument(response.getType() == Iq.Type.TIMEOUT); Log.d( Config.LOGTAG, id.account.getJid().asBareJid() @@ -361,8 +362,8 @@ public abstract class AbstractJingleConnection { this.sessionId = sessionId; } - public static Id of(Account account, JinglePacket jinglePacket) { - return new Id(account, jinglePacket.getFrom(), jinglePacket.getSessionId()); + public static Id of(Account account, Iq iq, final Jingle jingle) { + return new Id(account, iq.getFrom(), jingle.getSessionId()); } public static Id of(Account account, Jid with, final String sessionId) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/FileTransferContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/FileTransferContentMap.java index c678c91cb8b1de6fba4994a69ec3408620202954..d0f39747eae75b72c815bb5c00c9d99d41296418 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/FileTransferContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/FileTransferContentMap.java @@ -13,11 +13,10 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.IbbTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; -import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.WebRTCDataChannelTransportInfo; import eu.siacs.conversations.xmpp.jingle.transports.Transport; +import im.conversations.android.xmpp.model.jingle.Jingle; import java.util.Arrays; import java.util.Collections; @@ -39,7 +38,7 @@ public class FileTransferContentMap super(group, contents); } - public static FileTransferContentMap of(final JinglePacket jinglePacket) { + public static FileTransferContentMap of(final Jingle jinglePacket) { final Map> contents = of(jinglePacket.getJingleContents()); return new FileTransferContentMap(jinglePacket.getGroup(), contents); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/IceServers.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/IceServers.java index 7b2f884578f55ed00e28db715b23db1ac534ba7a..5149470f09bca5fe0ef6eb9c494a407fac5a30cc 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/IceServers.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/IceServers.java @@ -10,7 +10,7 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.IP; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import org.webrtc.PeerConnection; @@ -20,9 +20,9 @@ import java.util.List; public final class IceServers { - public static List parse(final IqPacket response) { + public static List parse(final Iq response) { ImmutableList.Builder listBuilder = new ImmutableList.Builder<>(); - if (response.getType() == IqPacket.TYPE.RESULT) { + if (response.getType() == Iq.Type.RESULT) { final Element services = response.findChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY); final List children = diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index c3080d692a4d336df858f502d9c2bb6be5aa59c7..95047919ca69c19380681eeffe59c9d304e40493 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -34,14 +34,13 @@ import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jingle.stanzas.Content; import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.Propose; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.jingle.transports.InbandBytestreamsTransport; import eu.siacs.conversations.xmpp.jingle.transports.Transport; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import im.conversations.android.xmpp.model.jingle.Jingle; +import im.conversations.android.xmpp.model.stanza.Iq; import java.lang.ref.WeakReference; import java.security.SecureRandom; @@ -77,9 +76,11 @@ public class JingleConnectionManager extends AbstractConnectionManager { return Base64.encodeToString(id, Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE); } - public void deliverPacket(final Account account, final JinglePacket packet) { - final String sessionId = packet.getSessionId(); - final JinglePacket.Action action = packet.getAction(); + public void deliverPacket(final Account account, final Iq packet) { + final var jingle = packet.getExtension(Jingle.class); + Preconditions.checkNotNull(jingle,"Passed iq packet w/o jingle extension to Connection Manager"); + final String sessionId = jingle.getSessionId(); + final Jingle.Action action = jingle.getAction(); if (sessionId == null) { respondWithJingleError(account, packet, "unknown-session", "item-not-found", "cancel"); return; @@ -88,13 +89,13 @@ public class JingleConnectionManager extends AbstractConnectionManager { respondWithJingleError(account, packet, null, "bad-request", "cancel"); return; } - final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, packet); + final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, packet, jingle); final AbstractJingleConnection existingJingleConnection = connections.get(id); if (existingJingleConnection != null) { existingJingleConnection.deliverPacket(packet); - } else if (action == JinglePacket.Action.SESSION_INITIATE) { + } else if (action == Jingle.Action.SESSION_INITIATE) { final Jid from = packet.getFrom(); - final Content content = packet.getJingleContent(); + final Content content = jingle.getJingleContent(); final String descriptionNamespace = content == null ? null : content.getDescriptionNamespace(); final AbstractJingleConnection connection; @@ -162,14 +163,14 @@ public class JingleConnectionManager extends AbstractConnectionManager { } private void sendSessionTerminate( - final Account account, final IqPacket request, final AbstractJingleConnection.Id id) { + final Account account, final Iq request, final AbstractJingleConnection.Id id) { mXmppConnectionService.sendIqPacket( - account, request.generateResponse(IqPacket.TYPE.RESULT), null); - final JinglePacket sessionTermination = - new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId); - sessionTermination.setTo(id.with); + account, request.generateResponse(Iq.Type.RESULT), null); + final var iq = new Iq(Iq.Type.SET); + iq.setTo(id.with); + final var sessionTermination = iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId)); sessionTermination.setReason(Reason.BUSY, null); - mXmppConnectionService.sendIqPacket(account, sessionTermination, null); + mXmppConnectionService.sendIqPacket(account, iq, null); } private boolean isUsingClearNet(final Account account) { @@ -263,11 +264,11 @@ public class JingleConnectionManager extends AbstractConnectionManager { void respondWithJingleError( final Account account, - final IqPacket original, + final Iq original, final String jingleCondition, final String condition, final String conditionType) { - final IqPacket response = original.generateResponse(IqPacket.TYPE.ERROR); + final Iq response = original.generateResponse(Iq.Type.ERROR); final Element error = response.addChild("error"); error.setAttribute("type", conditionType); error.addChild(condition, "urn:ietf:params:xml:ns:xmpp-stanzas"); @@ -438,7 +439,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { final int activeDevices = account.activeDevicesWithRtpCapability(); Log.d(Config.LOGTAG, "active devices with rtp capability: " + activeDevices); if (activeDevices == 0) { - final MessagePacket reject = + final var reject = mXmppConnectionService .getMessageGenerator() .sessionReject(from, sessionId); @@ -492,10 +493,11 @@ public class JingleConnectionManager extends AbstractConnectionManager { if (remoteMsgId == null) { return; } - final MessagePacket errorMessage = new MessagePacket(); + final var errorMessage = + new im.conversations.android.xmpp.model.stanza.Message(); errorMessage.setTo(from); errorMessage.setId(remoteMsgId); - errorMessage.setType(MessagePacket.TYPE_ERROR); + errorMessage.setType(im.conversations.android.xmpp.model.stanza.Message.Type.ERROR); final Element error = errorMessage.addChild("error"); error.setAttribute("code", "404"); error.setAttribute("type", "cancel"); @@ -720,7 +722,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { rtpSessionProposal.sessionId, RtpEndUserState.RETRACTED); } - final MessagePacket messagePacket = + final var messagePacket = mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal); writeLogMissedOutgoing( account, @@ -790,7 +792,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING); mXmppConnectionService.notifyJingleRtpConnectionUpdate( account, proposal.with, proposal.sessionId, RtpEndUserState.FINDING_DEVICE); - final MessagePacket messagePacket = + final var messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal); mXmppConnectionService.sendMessagePacket(account, messagePacket); return proposal; @@ -800,7 +802,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { public void sendJingleMessageFinish( final Contact contact, final String sessionId, final Reason reason) { final var account = contact.getAccount(); - final MessagePacket messagePacket = + final var messagePacket = mXmppConnectionService .getMessageGenerator() .sessionFinish(contact.getJid(), sessionId, reason); @@ -842,7 +844,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { return false; } - public void deliverIbbPacket(final Account account, final IqPacket packet) { + public void deliverIbbPacket(final Account account, final Iq packet) { final String sid; final Element payload; final InbandBytestreamsTransport.PacketType packetType; @@ -868,7 +870,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { Config.LOGTAG, account.getJid().asBareJid() + ": unable to deliver ibb packet. missing sid"); account.getXmppConnection() - .sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null); + .sendIqPacket(packet.generateResponse(Iq.Type.ERROR), null); return; } for (final AbstractJingleConnection connection : this.connections.values()) { @@ -879,11 +881,11 @@ public class JingleConnectionManager extends AbstractConnectionManager { if (inBandTransport.deliverPacket(packetType, packet.getFrom(), payload)) { account.getXmppConnection() .sendIqPacket( - packet.generateResponse(IqPacket.TYPE.RESULT), null); + packet.generateResponse(Iq.Type.RESULT), null); } else { account.getXmppConnection() .sendIqPacket( - packet.generateResponse(IqPacket.TYPE.ERROR), null); + packet.generateResponse(Iq.Type.ERROR), null); } return; } @@ -894,7 +896,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { Config.LOGTAG, account.getJid().asBareJid() + ": unable to deliver ibb packet with sid=" + sid); account.getXmppConnection() - .sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null); + .sendIqPacket(packet.generateResponse(Iq.Type.ERROR), null); } public void notifyRebound(final Account account) { @@ -945,7 +947,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { account.getJid().asBareJid() + ": resending session proposal to " + proposal.with); - final MessagePacket messagePacket = + final var messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal); mXmppConnectionService.sendMessagePacket(account, messagePacket); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java index 68564ec3c4e08569a6abec58c9fa31c867200c94..cb8e1d48da2eea7e4184252f101e8026156ba1a0 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java @@ -31,7 +31,6 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.IbbTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.WebRTCDataChannelTransportInfo; @@ -39,7 +38,9 @@ import eu.siacs.conversations.xmpp.jingle.transports.InbandBytestreamsTransport; import eu.siacs.conversations.xmpp.jingle.transports.SocksByteStreamsTransport; import eu.siacs.conversations.xmpp.jingle.transports.Transport; import eu.siacs.conversations.xmpp.jingle.transports.WebRTCDataChannelTransport; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; + +import im.conversations.android.xmpp.model.jingle.Jingle; +import im.conversations.android.xmpp.model.stanza.Iq; import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.io.CipherInputStream; @@ -112,22 +113,23 @@ public class JingleFileTransferConnection extends AbstractJingleConnection } @Override - void deliverPacket(final JinglePacket jinglePacket) { - switch (jinglePacket.getAction()) { - case SESSION_ACCEPT -> receiveSessionAccept(jinglePacket); - case SESSION_INITIATE -> receiveSessionInitiate(jinglePacket); - case SESSION_INFO -> receiveSessionInfo(jinglePacket); - case SESSION_TERMINATE -> receiveSessionTerminate(jinglePacket); - case TRANSPORT_ACCEPT -> receiveTransportAccept(jinglePacket); - case TRANSPORT_INFO -> receiveTransportInfo(jinglePacket); - case TRANSPORT_REPLACE -> receiveTransportReplace(jinglePacket); + void deliverPacket(final Iq iq) { + final var jingle = iq.getExtension(Jingle.class); + switch (jingle.getAction()) { + case SESSION_ACCEPT -> receiveSessionAccept(iq, jingle); + case SESSION_INITIATE -> receiveSessionInitiate(iq, jingle); + case SESSION_INFO -> receiveSessionInfo(iq, jingle); + case SESSION_TERMINATE -> receiveSessionTerminate(iq, jingle); + case TRANSPORT_ACCEPT -> receiveTransportAccept(iq, jingle); + case TRANSPORT_INFO -> receiveTransportInfo(iq, jingle); + case TRANSPORT_REPLACE -> receiveTransportReplace(iq, jingle); default -> { - respondOk(jinglePacket); + respondOk(iq); Log.d( Config.LOGTAG, String.format( "%s: received unhandled jingle action %s", - id.account.getJid().asBareJid(), jinglePacket.getAction())); + id.account.getJid().asBareJid(), jingle.getAction())); } } } @@ -203,33 +205,34 @@ public class JingleFileTransferConnection extends AbstractJingleConnection if (transition( State.SESSION_INITIALIZED, () -> this.initiatorFileTransferContentMap = contentMap)) { - final var jinglePacket = - contentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); + final var iq = + contentMap.toJinglePacket(Jingle.Action.SESSION_INITIATE, id.sessionId); + final var jingle = iq.getExtension(Jingle.class); if (xmppAxolotlMessage != null) { this.transportSecurity = new TransportSecurity( xmppAxolotlMessage.getInnerKey(), xmppAxolotlMessage.getIV()); - final var contents = jinglePacket.getJingleContents(); + final var contents = jingle.getJingleContents(); final var rawContent = contents.get(Iterables.getOnlyElement(contentMap.contents.keySet())); if (rawContent != null) { rawContent.setSecurity(xmppAxolotlMessage); } } - jinglePacket.setTo(id.with); + iq.setTo(id.with); xmppConnectionService.sendIqPacket( id.account, - jinglePacket, - (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + iq, + (response) -> { + if (response.getType() == Iq.Type.RESULT) { xmppConnectionService.markMessage(message, Message.STATUS_OFFERED); return; } - if (response.getType() == IqPacket.TYPE.ERROR) { + if (response.getType() == Iq.Type.ERROR) { handleIqErrorResponse(response); return; } - if (response.getType() == IqPacket.TYPE.TIMEOUT) { + if (response.getType() == Iq.Type.TIMEOUT) { handleIqTimeoutResponse(response); } }); @@ -237,15 +240,15 @@ public class JingleFileTransferConnection extends AbstractJingleConnection } } - private void receiveSessionAccept(final JinglePacket jinglePacket) { + private void receiveSessionAccept(final Iq jinglePacket, final Jingle jingle) { Log.d(Config.LOGTAG, "receive file transfer session accept"); if (isResponder()) { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_ACCEPT); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_ACCEPT); return; } final FileTransferContentMap contentMap; try { - contentMap = FileTransferContentMap.of(jinglePacket); + contentMap = FileTransferContentMap.of(jingle); contentMap.requireOnlyFileTransferDescription(); } catch (final RuntimeException e) { Log.d( @@ -261,7 +264,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection } private void receiveSessionAccept( - final JinglePacket jinglePacket, final FileTransferContentMap contentMap) { + final Iq jinglePacket, final FileTransferContentMap contentMap) { if (transition(State.SESSION_ACCEPTED, () -> setRemoteContentMap(contentMap))) { respondOk(jinglePacket); final var transport = this.transport; @@ -280,7 +283,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.account.getJid().asBareJid() + ": receive out of order session-accept"); - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_ACCEPT); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_ACCEPT); } } @@ -309,16 +312,16 @@ public class JingleFileTransferConnection extends AbstractJingleConnection } } - private void receiveSessionInitiate(final JinglePacket jinglePacket) { + private void receiveSessionInitiate(final Iq jinglePacket, final Jingle jingle) { if (isInitiator()) { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_INITIATE); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_INITIATE); return; } Log.d(Config.LOGTAG, "receive session initiate " + jinglePacket); final FileTransferContentMap contentMap; final FileTransferDescription.File file; try { - contentMap = FileTransferContentMap.of(jinglePacket); + contentMap = FileTransferContentMap.of(jingle); contentMap.requireContentDescriptions(); file = contentMap.requireOnlyFile(); // TODO check is offer @@ -332,7 +335,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection return; } final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage; - final var contents = jinglePacket.getJingleContents(); + final var contents = jingle.getJingleContents(); final var rawContent = contents.get(Iterables.getOnlyElement(contentMap.contents.keySet())); final var security = rawContent == null ? null : rawContent.getSecurity(jinglePacket.getFrom()); @@ -349,7 +352,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection } private void receiveSessionInitiate( - final JinglePacket jinglePacket, + final Iq jinglePacket, final FileTransferContentMap contentMap, final FileTransferDescription.File file, final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage) { @@ -396,7 +399,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection Log.d( Config.LOGTAG, id.account.getJid().asBareJid() + ": receive out of order session-initiate"); - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_INITIATE); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_INITIATE); } } @@ -453,9 +456,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection private void sendSessionAccept(final FileTransferContentMap contentMap) { setLocalContentMap(contentMap); - final var jinglePacket = - contentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId); - send(jinglePacket); + final var iq = + contentMap.toJinglePacket(Jingle.Action.SESSION_ACCEPT, id.sessionId); + send(iq); // this needs to come after session-accept or else our candidate-error might arrive first this.transport.connect(); this.transport.readyToSentAdditionalCandidates(); @@ -541,9 +544,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage()); } - private void receiveSessionInfo(final JinglePacket jinglePacket) { + private void receiveSessionInfo(final Iq jinglePacket, final Jingle jingle) { respondOk(jinglePacket); - final var sessionInfo = FileTransferDescription.getSessionInfo(jinglePacket); + final var sessionInfo = FileTransferDescription.getSessionInfo(jingle); if (sessionInfo instanceof FileTransferDescription.Checksum checksum) { receiveSessionInfoChecksum(checksum); } else if (sessionInfo instanceof FileTransferDescription.Received received) { @@ -559,9 +562,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection Log.d(Config.LOGTAG, "peer confirmed received " + received); } - private void receiveSessionTerminate(final JinglePacket jinglePacket) { + private void receiveSessionTerminate(final Iq jinglePacket, final Jingle jingle) { respondOk(jinglePacket); - final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason(); + final Jingle.ReasonWrapper wrapper = jingle.getReason(); final State previous = this.state; Log.d( Config.LOGTAG, @@ -590,15 +593,15 @@ public class JingleFileTransferConnection extends AbstractJingleConnection finish(); } - private void receiveTransportAccept(final JinglePacket jinglePacket) { + private void receiveTransportAccept(final Iq jinglePacket, final Jingle jingle) { if (isResponder()) { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.TRANSPORT_ACCEPT); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.TRANSPORT_ACCEPT); return; } Log.d(Config.LOGTAG, "receive transport accept " + jinglePacket); final GenericTransportInfo transportInfo; try { - transportInfo = FileTransferContentMap.of(jinglePacket).requireOnlyTransportInfo(); + transportInfo = FileTransferContentMap.of(jingle).requireOnlyTransportInfo(); } catch (final RuntimeException e) { Log.d( Config.LOGTAG, @@ -610,15 +613,15 @@ public class JingleFileTransferConnection extends AbstractJingleConnection return; } if (isInState(State.SESSION_ACCEPTED)) { - final var group = jinglePacket.getGroup(); + final var group = jingle.getGroup(); receiveTransportAccept(jinglePacket, new Transport.TransportInfo(transportInfo, group)); } else { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.TRANSPORT_ACCEPT); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.TRANSPORT_ACCEPT); } } private void receiveTransportAccept( - final JinglePacket jinglePacket, final Transport.TransportInfo transportInfo) { + final Iq jinglePacket, final Transport.TransportInfo transportInfo) { final FileTransferContentMap remoteContentMap = getRemoteContentMap().withTransport(transportInfo); setRemoteContentMap(remoteContentMap); @@ -637,11 +640,11 @@ public class JingleFileTransferConnection extends AbstractJingleConnection } } - private void receiveTransportInfo(final JinglePacket jinglePacket) { + private void receiveTransportInfo(final Iq jinglePacket, final Jingle jingle) { final FileTransferContentMap contentMap; final GenericTransportInfo transportInfo; try { - contentMap = FileTransferContentMap.of(jinglePacket); + contentMap = FileTransferContentMap.of(jingle); transportInfo = contentMap.requireOnlyTransportInfo(); } catch (final RuntimeException e) { Log.d( @@ -725,14 +728,14 @@ public class JingleFileTransferConnection extends AbstractJingleConnection } } - private void receiveTransportReplace(final JinglePacket jinglePacket) { + private void receiveTransportReplace(final Iq jinglePacket, final Jingle jingle) { if (isInitiator()) { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.TRANSPORT_REPLACE); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.TRANSPORT_REPLACE); return; } final GenericTransportInfo transportInfo; try { - transportInfo = FileTransferContentMap.of(jinglePacket).requireOnlyTransportInfo(); + transportInfo = FileTransferContentMap.of(jingle).requireOnlyTransportInfo(); } catch (final RuntimeException e) { Log.d( Config.LOGTAG, @@ -746,12 +749,12 @@ public class JingleFileTransferConnection extends AbstractJingleConnection if (isInState(State.SESSION_ACCEPTED)) { receiveTransportReplace(jinglePacket, transportInfo); } else { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.TRANSPORT_REPLACE); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.TRANSPORT_REPLACE); } } private void receiveTransportReplace( - final JinglePacket jinglePacket, final GenericTransportInfo transportInfo) { + final Iq jinglePacket, final GenericTransportInfo transportInfo) { respondOk(jinglePacket); final Transport currentTransport = this.transport; if (currentTransport != null) { @@ -796,11 +799,11 @@ public class JingleFileTransferConnection extends AbstractJingleConnection private void sendTransportAccept(final FileTransferContentMap contentMap) { setLocalContentMap(contentMap); - final var jinglePacket = + final var iq = contentMap .transportInfo() - .toJinglePacket(JinglePacket.Action.TRANSPORT_ACCEPT, id.sessionId); - send(jinglePacket); + .toJinglePacket(Jingle.Action.TRANSPORT_ACCEPT, id.sessionId); + send(iq); transport.connect(); } @@ -982,11 +985,10 @@ public class JingleFileTransferConnection extends AbstractJingleConnection } private void sendSessionInfo(final FileTransferDescription.SessionInfo sessionInfo) { - final var jinglePacket = - new JinglePacket(JinglePacket.Action.SESSION_INFO, this.id.sessionId); - jinglePacket.addJingleChild(sessionInfo.asElement()); - jinglePacket.setTo(this.id.with); - send(jinglePacket); + final var iq = new Iq(Iq.Type.SET); + final var jinglePacket = iq.addExtension(new Jingle(Jingle.Action.SESSION_INFO, this.id.sessionId)); + jinglePacket.addChild(sessionInfo.asElement()); + send(iq); } @Override @@ -1039,11 +1041,11 @@ public class JingleFileTransferConnection extends AbstractJingleConnection private void sendTransportReplace(final FileTransferContentMap contentMap) { setLocalContentMap(contentMap); - final var jinglePacket = + final var iq = contentMap .transportInfo() - .toJinglePacket(JinglePacket.Action.TRANSPORT_REPLACE, id.sessionId); - send(jinglePacket); + .toJinglePacket(Jingle.Action.TRANSPORT_REPLACE, id.sessionId); + send(iq); } @Override @@ -1068,9 +1070,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection + contentName); return; } - final JinglePacket jinglePacket = - transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); - send(jinglePacket); + final Iq iq = + transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId); + send(iq); } @Override @@ -1081,12 +1083,12 @@ public class JingleFileTransferConnection extends AbstractJingleConnection Log.e(Config.LOGTAG, "local content map is null on candidate used"); return; } - final var jinglePacket = + final var iq = contentMap .candidateUsed(streamId, candidate.cid) - .toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); - Log.d(Config.LOGTAG, "sending candidate used " + jinglePacket); - send(jinglePacket); + .toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId); + Log.d(Config.LOGTAG, "sending candidate used " + iq); + send(iq); } @Override @@ -1096,12 +1098,12 @@ public class JingleFileTransferConnection extends AbstractJingleConnection Log.e(Config.LOGTAG, "local content map is null on candidate used"); return; } - final var jinglePacket = + final var iq = contentMap .candidateError(streamId) - .toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); - Log.d(Config.LOGTAG, "sending candidate error " + jinglePacket); - send(jinglePacket); + .toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId); + Log.d(Config.LOGTAG, "sending candidate error " + iq); + send(iq); } @Override @@ -1111,11 +1113,11 @@ public class JingleFileTransferConnection extends AbstractJingleConnection Log.e(Config.LOGTAG, "local content map is null on candidate used"); return; } - final var jinglePacket = + final var iq = contentMap .proxyActivated(streamId, candidate.cid) - .toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); - send(jinglePacket); + .toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId); + send(iq); } @Override @@ -1251,10 +1253,10 @@ public class JingleFileTransferConnection extends AbstractJingleConnection message, Message.STATUS_SEND_FAILED, Message.ERROR_MESSAGE_CANCELLED); } terminateTransport(); - final JinglePacket jinglePacket = - new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId); - jinglePacket.setReason(reason, "User requested to stop file transfer"); - send(jinglePacket); + final Iq iq = new Iq(Iq.Type.SET); + final var jingle = iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId)); + jingle.setReason(reason, "User requested to stop file transfer"); + send(iq); finish(); return true; } else { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 26e62373c4f3df402363b8b35e91a4dbdc656331..4471cfc62bf15e430b978384d9593139f3147eff 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -43,13 +43,13 @@ import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.jingle.stanzas.Content; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.Proceed; import eu.siacs.conversations.xmpp.jingle.stanzas.Propose; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; + +import im.conversations.android.xmpp.model.jingle.Jingle; +import im.conversations.android.xmpp.model.stanza.Iq; import org.webrtc.EglBase; import org.webrtc.IceCandidate; @@ -139,24 +139,25 @@ public class JingleRtpConnection extends AbstractJingleConnection } @Override - synchronized void deliverPacket(final JinglePacket jinglePacket) { - switch (jinglePacket.getAction()) { - case SESSION_INITIATE -> receiveSessionInitiate(jinglePacket); - case TRANSPORT_INFO -> receiveTransportInfo(jinglePacket); - case SESSION_ACCEPT -> receiveSessionAccept(jinglePacket); - case SESSION_TERMINATE -> receiveSessionTerminate(jinglePacket); - case CONTENT_ADD -> receiveContentAdd(jinglePacket); - case CONTENT_ACCEPT -> receiveContentAccept(jinglePacket); - case CONTENT_REJECT -> receiveContentReject(jinglePacket); - case CONTENT_REMOVE -> receiveContentRemove(jinglePacket); - case CONTENT_MODIFY -> receiveContentModify(jinglePacket); + synchronized void deliverPacket(final Iq iq) { + final var jingle = iq.getExtension(Jingle.class); + switch (jingle.getAction()) { + case SESSION_INITIATE -> receiveSessionInitiate(iq, jingle); + case TRANSPORT_INFO -> receiveTransportInfo(iq, jingle); + case SESSION_ACCEPT -> receiveSessionAccept(iq, jingle); + case SESSION_TERMINATE -> receiveSessionTerminate(iq); + case CONTENT_ADD -> receiveContentAdd(iq, jingle); + case CONTENT_ACCEPT -> receiveContentAccept(iq); + case CONTENT_REJECT -> receiveContentReject(iq, jingle); + case CONTENT_REMOVE -> receiveContentRemove(iq, jingle); + case CONTENT_MODIFY -> receiveContentModify(iq, jingle); default -> { - respondOk(jinglePacket); + respondOk(iq); Log.d( Config.LOGTAG, String.format( "%s: received unhandled jingle action %s", - id.account.getJid().asBareJid(), jinglePacket.getAction())); + id.account.getJid().asBareJid(), jingle.getAction())); } } } @@ -183,9 +184,10 @@ public class JingleRtpConnection extends AbstractJingleConnection } } - private void receiveSessionTerminate(final JinglePacket jinglePacket) { + private void receiveSessionTerminate(final Iq jinglePacket) { respondOk(jinglePacket); - final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason(); + final var jingle = jinglePacket.getExtension(Jingle.class); + final Jingle.ReasonWrapper wrapper = jingle.getReason(); final State previous = this.state; Log.d( Config.LOGTAG, @@ -214,7 +216,7 @@ public class JingleRtpConnection extends AbstractJingleConnection finish(); } - private void receiveTransportInfo(final JinglePacket jinglePacket) { + private void receiveTransportInfo(final Iq jinglePacket, final Jingle jingle) { // Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to // INITIALIZED only after transport-info has been received if (isInState( @@ -225,7 +227,7 @@ public class JingleRtpConnection extends AbstractJingleConnection State.SESSION_ACCEPTED)) { final RtpContentMap contentMap; try { - contentMap = RtpContentMap.of(jinglePacket); + contentMap = RtpContentMap.of(jingle); } catch (final IllegalArgumentException | NullPointerException e) { Log.d( Config.LOGTAG, @@ -255,7 +257,7 @@ public class JingleRtpConnection extends AbstractJingleConnection } private void receiveTransportInfo( - final JinglePacket jinglePacket, final RtpContentMap contentMap) { + final Iq jinglePacket, final RtpContentMap contentMap) { final Set>> candidates = contentMap.contents.entrySet(); final RtpContentMap remote = getRemoteContentMap(); @@ -294,17 +296,17 @@ public class JingleRtpConnection extends AbstractJingleConnection } } - private void receiveContentAdd(final JinglePacket jinglePacket) { + private void receiveContentAdd(final Iq iq, final Jingle jingle) { final RtpContentMap modification; try { - modification = RtpContentMap.of(jinglePacket); + modification = RtpContentMap.of(jingle); modification.requireContentDescriptions(); } catch (final RuntimeException e) { Log.d( Config.LOGTAG, id.getAccount().getJid().asBareJid() + ": improperly formatted contents", Throwables.getRootCause(e)); - respondOk(jinglePacket); + respondOk(iq); webRTCWrapper.close(); sendSessionTerminate(Reason.of(e), e.getMessage()); return; @@ -320,12 +322,12 @@ public class JingleRtpConnection extends AbstractJingleConnection new FutureCallback<>() { @Override public void onSuccess(final RtpContentMap rtpContentMap) { - receiveContentAdd(jinglePacket, rtpContentMap); + receiveContentAdd(iq, rtpContentMap); } @Override public void onFailure(@NonNull Throwable throwable) { - respondOk(jinglePacket); + respondOk(iq); final Throwable rootCause = Throwables.getRootCause(throwable); Log.d( Config.LOGTAG, @@ -339,12 +341,12 @@ public class JingleRtpConnection extends AbstractJingleConnection }, MoreExecutors.directExecutor()); } else { - terminateWithOutOfOrder(jinglePacket); + terminateWithOutOfOrder(iq); } } private void receiveContentAdd( - final JinglePacket jinglePacket, final RtpContentMap modification) { + final Iq jinglePacket, final RtpContentMap modification) { final RtpContentMap remote = getRemoteContentMap(); if (!Collections.disjoint(modification.getNames(), remote.getNames())) { respondOk(jinglePacket); @@ -396,10 +398,11 @@ public class JingleRtpConnection extends AbstractJingleConnection } } - private void receiveContentAccept(final JinglePacket jinglePacket) { + private void receiveContentAccept(final Iq jinglePacket) { + final var jingle = jinglePacket.getExtension(Jingle.class); final RtpContentMap receivedContentAccept; try { - receivedContentAccept = RtpContentMap.of(jinglePacket); + receivedContentAccept = RtpContentMap.of(jingle); receivedContentAccept.requireContentDescriptions(); } catch (final RuntimeException e) { Log.d( @@ -484,14 +487,14 @@ public class JingleRtpConnection extends AbstractJingleConnection updateEndUserState(); } - private void receiveContentModify(final JinglePacket jinglePacket) { + private void receiveContentModify(final Iq jinglePacket, final Jingle jingle) { if (this.state != State.SESSION_ACCEPTED) { terminateWithOutOfOrder(jinglePacket); return; } final Map modification = Maps.transformEntries( - jinglePacket.getJingleContents(), (key, value) -> value.getSenders()); + jingle.getJingleContents(), (key, value) -> value.getSenders()); final boolean isInitiator = isInitiator(); final RtpContentMap currentOutgoing = this.outgoingContentAdd; final RtpContentMap remoteContentMap = this.getRemoteContentMap(); @@ -594,10 +597,10 @@ public class JingleRtpConnection extends AbstractJingleConnection return candidateBuilder.build(); } - private void receiveContentReject(final JinglePacket jinglePacket) { + private void receiveContentReject(final Iq jinglePacket, final Jingle jingle) { final RtpContentMap receivedContentReject; try { - receivedContentReject = RtpContentMap.of(jinglePacket); + receivedContentReject = RtpContentMap.of(jingle); } catch (final RuntimeException e) { Log.d( Config.LOGTAG, @@ -650,10 +653,10 @@ public class JingleRtpConnection extends AbstractJingleConnection + summary); } - private void receiveContentRemove(final JinglePacket jinglePacket) { + private void receiveContentRemove(final Iq jinglePacket, final Jingle jingle) { final RtpContentMap receivedContentRemove; try { - receivedContentRemove = RtpContentMap.of(jinglePacket); + receivedContentRemove = RtpContentMap.of(jingle); receivedContentRemove.requireContentDescriptions(); } catch (final RuntimeException e) { Log.d( @@ -687,8 +690,8 @@ public class JingleRtpConnection extends AbstractJingleConnection String.format( "%s only supports %s as a means to retract a not yet accepted %s", BuildConfig.APP_NAME, - JinglePacket.Action.CONTENT_REMOVE, - JinglePacket.Action.CONTENT_ADD)); + Jingle.Action.CONTENT_REMOVE, + Jingle.Action.CONTENT_ADD)); } } @@ -713,10 +716,10 @@ public class JingleRtpConnection extends AbstractJingleConnection return; } this.outgoingContentAdd = null; - final JinglePacket retract = + final Iq retract = outgoingContentAdd .toStub() - .toJinglePacket(JinglePacket.Action.CONTENT_REMOVE, id.sessionId); + .toJinglePacket(Jingle.Action.CONTENT_REMOVE, id.sessionId); this.send(retract); Log.d( Config.LOGTAG, @@ -772,16 +775,16 @@ public class JingleRtpConnection extends AbstractJingleConnection "content addition is receive only. we want to upgrade to 'both'"); final RtpContentMap modifiedSenders = incomingContentAdd.modifiedSenders(Content.Senders.BOTH); - final JinglePacket proposedContentModification = + final Iq proposedContentModification = modifiedSenders .toStub() - .toJinglePacket(JinglePacket.Action.CONTENT_MODIFY, id.sessionId); + .toJinglePacket(Jingle.Action.CONTENT_MODIFY, id.sessionId); proposedContentModification.setTo(id.with); xmppConnectionService.sendIqPacket( id.account, proposedContentModification, - (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { Log.d( Config.LOGTAG, id.account.getJid().asBareJid() @@ -875,7 +878,7 @@ public class JingleRtpConnection extends AbstractJingleConnection @Override public void onFailure(@NonNull final Throwable throwable) { - failureToPerformAction(JinglePacket.Action.CONTENT_ACCEPT, throwable); + failureToPerformAction(Jingle.Action.CONTENT_ACCEPT, throwable); } }, MoreExecutors.directExecutor()); @@ -887,9 +890,9 @@ public class JingleRtpConnection extends AbstractJingleConnection } private void sendContentAccept(final RtpContentMap contentAcceptMap) { - final JinglePacket jinglePacket = - contentAcceptMap.toJinglePacket(JinglePacket.Action.CONTENT_ACCEPT, id.sessionId); - send(jinglePacket); + final Iq iq = + contentAcceptMap.toJinglePacket(Jingle.Action.CONTENT_ACCEPT, id.sessionId); + send(iq); } public synchronized void rejectContentAdd() { @@ -903,20 +906,20 @@ public class JingleRtpConnection extends AbstractJingleConnection } private void rejectContentAdd(final RtpContentMap contentMap) { - final JinglePacket jinglePacket = + final Iq iq = contentMap .toStub() - .toJinglePacket(JinglePacket.Action.CONTENT_REJECT, id.sessionId); + .toJinglePacket(Jingle.Action.CONTENT_REJECT, id.sessionId); Log.d( Config.LOGTAG, id.getAccount().getJid().asBareJid() + ": rejecting content " + ContentAddition.summary(contentMap)); - send(jinglePacket); + send(iq); } private boolean checkForIceRestart( - final JinglePacket jinglePacket, final RtpContentMap rtpContentMap) { + final Iq jinglePacket, final RtpContentMap rtpContentMap) { final RtpContentMap existing = getRemoteContentMap(); final Set existingCredentials; final IceUdpTransportInfo.Credentials newCredentials; @@ -995,7 +998,7 @@ public class JingleRtpConnection extends AbstractJingleConnection } private boolean applyIceRestart( - final JinglePacket jinglePacket, + final Iq jinglePacket, final RtpContentMap restartContentMap, final boolean isOffer) throws ExecutionException, InterruptedException { @@ -1096,7 +1099,7 @@ public class JingleRtpConnection extends AbstractJingleConnection } private ListenableFuture receiveRtpContentMap( - final JinglePacket jinglePacket, final boolean expectVerification) { + final Jingle jinglePacket, final boolean expectVerification) { try { return receiveRtpContentMap(RtpContentMap.of(jinglePacket), expectVerification); } catch (final Exception e) { @@ -1139,12 +1142,12 @@ public class JingleRtpConnection extends AbstractJingleConnection } } - private void receiveSessionInitiate(final JinglePacket jinglePacket) { + private void receiveSessionInitiate(final Iq jinglePacket, final Jingle jingle) { if (isInitiator()) { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_INITIATE); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_INITIATE); return; } - final ListenableFuture future = receiveRtpContentMap(jinglePacket, false); + final ListenableFuture future = receiveRtpContentMap(jingle, false); Futures.addCallback( future, new FutureCallback<>() { @@ -1163,7 +1166,7 @@ public class JingleRtpConnection extends AbstractJingleConnection } private void receiveSessionInitiate( - final JinglePacket jinglePacket, final RtpContentMap contentMap) { + final Iq jinglePacket, final RtpContentMap contentMap) { try { contentMap.requireContentDescriptions(); contentMap.requireDTLSFingerprint(true); @@ -1223,13 +1226,13 @@ public class JingleRtpConnection extends AbstractJingleConnection } } - private void receiveSessionAccept(final JinglePacket jinglePacket) { + private void receiveSessionAccept(final Iq jinglePacket, final Jingle jingle) { if (isResponder()) { - receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_ACCEPT); + receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_ACCEPT); return; } final ListenableFuture future = - receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint()); + receiveRtpContentMap(jingle, this.omemoVerification.hasFingerprint()); Futures.addCallback( future, new FutureCallback<>() { @@ -1254,7 +1257,7 @@ public class JingleRtpConnection extends AbstractJingleConnection } private void receiveSessionAccept( - final JinglePacket jinglePacket, final RtpContentMap contentMap) { + final Iq jinglePacket, final RtpContentMap contentMap) { try { contentMap.requireContentDescriptions(); contentMap.requireDTLSFingerprint(); @@ -1399,7 +1402,7 @@ public class JingleRtpConnection extends AbstractJingleConnection } private void failureToPerformAction( - final JinglePacket.Action action, final Throwable throwable) { + final Jingle.Action action, final Throwable throwable) { if (isTerminated()) { return; } @@ -1470,8 +1473,8 @@ public class JingleRtpConnection extends AbstractJingleConnection return; } transitionOrThrow(State.SESSION_ACCEPTED); - final JinglePacket sessionAccept = - rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId); + final Iq sessionAccept = + rtpContentMap.toJinglePacket(Jingle.Action.SESSION_ACCEPT, id.sessionId); send(sessionAccept); } @@ -1939,8 +1942,8 @@ public class JingleRtpConnection extends AbstractJingleConnection return; } this.transitionOrThrow(targetState); - final JinglePacket sessionInitiate = - rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); + final Iq sessionInitiate = + rtpContentMap.toJinglePacket(Jingle.Action.SESSION_INITIATE, id.sessionId); send(sessionInitiate); } @@ -2008,9 +2011,9 @@ public class JingleRtpConnection extends AbstractJingleConnection + contentName); return; } - final JinglePacket jinglePacket = - transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); - send(jinglePacket); + final Iq iq = + transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId); + send(iq); } public RtpEndUserState getEndUserState() { @@ -2364,8 +2367,8 @@ public class JingleRtpConnection extends AbstractJingleConnection } private void sendJingleMessage(final String action, final Jid to) { - final MessagePacket messagePacket = new MessagePacket(); - messagePacket.setType(MessagePacket.TYPE_CHAT); // we want to carbon copy those + final var messagePacket = new im.conversations.android.xmpp.model.stanza.Message(); + messagePacket.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); // we want to carbon copy those messagePacket.setTo(to); final Element intent = messagePacket @@ -2386,7 +2389,7 @@ public class JingleRtpConnection extends AbstractJingleConnection private void sendJingleMessageFinish(final Reason reason) { final var account = id.getAccount(); - final MessagePacket messagePacket = + final var messagePacket = xmppConnectionService .getMessageGenerator() .sessionFinish(id.with, id.sessionId, reason); @@ -2545,34 +2548,34 @@ public class JingleRtpConnection extends AbstractJingleConnection private void initiateIceRestart(final RtpContentMap rtpContentMap) { final RtpContentMap transportInfo = rtpContentMap.transportInfo(); - final JinglePacket jinglePacket = - transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); - Log.d(Config.LOGTAG, "initiating ice restart: " + jinglePacket); - jinglePacket.setTo(id.with); + final Iq iq = + transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId); + Log.d(Config.LOGTAG, "initiating ice restart: " + iq); + iq.setTo(id.with); xmppConnectionService.sendIqPacket( id.account, - jinglePacket, - (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + iq, + (response) -> { + if (response.getType() == Iq.Type.RESULT) { Log.d(Config.LOGTAG, "received success to our ice restart"); setLocalContentMap(rtpContentMap); webRTCWrapper.setIsReadyToReceiveIceCandidates(true); return; } - if (response.getType() == IqPacket.TYPE.ERROR) { + if (response.getType() == Iq.Type.ERROR) { if (isTieBreak(response)) { Log.d(Config.LOGTAG, "received tie-break as result of ice restart"); return; } handleIqErrorResponse(response); } - if (response.getType() == IqPacket.TYPE.TIMEOUT) { + if (response.getType() == Iq.Type.TIMEOUT) { handleIqTimeoutResponse(response); } }); } - private boolean isTieBreak(final IqPacket response) { + private boolean isTieBreak(final Iq response) { final Element error = response.findChild("error"); return error != null && error.hasChild("tie-break", Namespace.JINGLE_ERRORS); } @@ -2593,7 +2596,7 @@ public class JingleRtpConnection extends AbstractJingleConnection @Override public void onFailure(@NonNull Throwable throwable) { - failureToPerformAction(JinglePacket.Action.CONTENT_ADD, throwable); + failureToPerformAction(Jingle.Action.CONTENT_ADD, throwable); } }, MoreExecutors.directExecutor()); @@ -2601,21 +2604,21 @@ public class JingleRtpConnection extends AbstractJingleConnection private void sendContentAdd(final RtpContentMap contentAdd) { - final JinglePacket jinglePacket = - contentAdd.toJinglePacket(JinglePacket.Action.CONTENT_ADD, id.sessionId); - jinglePacket.setTo(id.with); + final Iq iq = + contentAdd.toJinglePacket(Jingle.Action.CONTENT_ADD, id.sessionId); + iq.setTo(id.with); xmppConnectionService.sendIqPacket( id.account, - jinglePacket, - (connection, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + iq, + (response) -> { + if (response.getType() == Iq.Type.RESULT) { Log.d( Config.LOGTAG, id.getAccount().getJid().asBareJid() + ": received ACK to our content-add"); return; } - if (response.getType() == IqPacket.TYPE.ERROR) { + if (response.getType() == Iq.Type.ERROR) { if (isTieBreak(response)) { this.outgoingContentAdd = null; Log.d(Config.LOGTAG, "received tie-break as result of our content-add"); @@ -2623,7 +2626,7 @@ public class JingleRtpConnection extends AbstractJingleConnection } handleIqErrorResponse(response); } - if (response.getType() == IqPacket.TYPE.TIMEOUT) { + if (response.getType() == Iq.Type.TIMEOUT) { handleIqTimeoutResponse(response); } }); @@ -2821,13 +2824,13 @@ public class JingleRtpConnection extends AbstractJingleConnection private void discoverIceServers(final OnIceServersDiscovered onIceServersDiscovered) { if (id.account.getXmppConnection().getFeatures().externalServiceDiscovery()) { - final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.setTo(id.account.getDomain()); request.addChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY); xmppConnectionService.sendIqPacket( id.account, request, - (account, response) -> { + (response) -> { final var iceServers = IceServers.parse(response); if (iceServers.isEmpty()) { Log.w( diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java index 9a60b3924273c14a4acc235dc5a06f9ff425db75..263905051990a159aba342f173d58aa6a07b65cf 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java @@ -1,9 +1,8 @@ package eu.siacs.conversations.xmpp.jingle; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.PacketReceived; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; +import im.conversations.android.xmpp.model.stanza.Iq; -public interface OnJinglePacketReceived extends PacketReceived { - void onJinglePacketReceived(Account account, JinglePacket packet); +public interface OnJinglePacketReceived { + void onJinglePacketReceived(Account account, Iq packet); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java index 94f8ca3002dc8490c2f4cf46dde1dea697d8848b..5036b2b858038e6064751e49e1b264c4b230ed92 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java @@ -18,9 +18,9 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; -import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; +import im.conversations.android.xmpp.model.jingle.Jingle; import java.util.Collection; import java.util.LinkedHashMap; @@ -38,7 +38,7 @@ public class RtpContentMap extends AbstractContentMap> contents = of(jinglePacket.getJingleContents()); if (isOmemoVerified(contents)) { @@ -52,7 +52,7 @@ public class RtpContentMap extends AbstractContentMap> contents) { final Collection> values = contents.values(); - if (values.size() == 0) { + if (values.isEmpty()) { return false; } for (final DescriptionTransport descriptionTransport : diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java index 3878d98d94554a539280bc402cff9b2079df60ba..0553e203e55a2f56b5ea64f57c3c8ce90a5fae68 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java @@ -15,6 +15,7 @@ import com.google.common.primitives.Longs; import eu.siacs.conversations.Config; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.xmpp.model.jingle.Jingle; import java.util.List; @@ -55,15 +56,11 @@ public class FileTransferDescription extends GenericDescription { return new File(size, name, mediaType, hashes); } - public static SessionInfo getSessionInfo(@NonNull final JinglePacket jinglePacket) { - Preconditions.checkNotNull(jinglePacket); + public static SessionInfo getSessionInfo(@NonNull final Jingle jingle) { + Preconditions.checkNotNull(jingle); Preconditions.checkArgument( - jinglePacket.getAction() == JinglePacket.Action.SESSION_INFO, + jingle.getAction() == Jingle.Action.SESSION_INFO, "jingle packet is not a session-info"); - final Element jingle = jinglePacket.findChild("jingle", Namespace.JINGLE); - if (jingle == null) { - return null; - } final Element checksum = jingle.findChild("checksum", Namespace.JINGLE_APPS_FILE_TRANSFER); if (checksum != null) { final Element file = checksum.findChild("file", Namespace.JINGLE_APPS_FILE_TRANSFER); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/InbandBytestreamsTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/InbandBytestreamsTransport.java index ce2d4b31f2a61298ca733bcb1fd1abff523fc73e..849b2a404ec13bd75330c5fed894bc3ec6b0ea6c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/InbandBytestreamsTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/InbandBytestreamsTransport.java @@ -16,7 +16,7 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jingle.stanzas.IbbTransportInfo; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import java.io.Closeable; import java.io.IOException; @@ -96,7 +96,7 @@ public class InbandBytestreamsTransport implements Transport { } private void openInBandTransport() { - final var iqPacket = new IqPacket(IqPacket.TYPE.SET); + final var iqPacket = new Iq(Iq.Type.SET); iqPacket.setTo(with); final var open = iqPacket.addChild("open", Namespace.IBB); open.setAttribute("block-size", this.blockSize); @@ -106,8 +106,8 @@ public class InbandBytestreamsTransport implements Transport { xmppConnection.sendIqPacket(iqPacket, this::receiveResponseToOpen); } - private void receiveResponseToOpen(final Account account, final IqPacket response) { - if (response.getType() == IqPacket.TYPE.RESULT) { + private void receiveResponseToOpen(final Iq response) { + if (response.getType() == Iq.Type.RESULT) { Log.d(Config.LOGTAG, "ibb open was accepted"); this.transportCallback.onTransportEstablished(); this.blockSenderThread.start(); @@ -284,7 +284,7 @@ public class InbandBytestreamsTransport implements Transport { private void sendIbbBlock(final int sequence, final byte[] block) { Log.d(Config.LOGTAG, "sending ibb block #" + sequence + " " + block.length + " bytes"); - final var iqPacket = new IqPacket(IqPacket.TYPE.SET); + final var iqPacket = new Iq(Iq.Type.SET); iqPacket.setTo(with); final var data = iqPacket.addChild("data", Namespace.IBB); data.setAttribute("sid", this.streamId); @@ -292,8 +292,8 @@ public class InbandBytestreamsTransport implements Transport { data.setContent(BaseEncoding.base64().encode(block)); this.xmppConnection.sendIqPacket( iqPacket, - (a, response) -> { - if (response.getType() != IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() != Iq.Type.RESULT) { Log.d( Config.LOGTAG, "received iq error in response to data block #" + sequence); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java index bbda1c62271125a9d0af2753c724c903aa05d5ec..2925592ea498772ccf62ab48d7b183562f21bf17 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java @@ -32,7 +32,7 @@ import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection; import eu.siacs.conversations.xmpp.jingle.DirectConnectionUtils; import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import java.io.IOException; import java.io.InputStream; @@ -250,7 +250,7 @@ public class SocksByteStreamsTransport implements Transport { private ListenableFuture activateProxy(final Candidate candidate) { Log.d(Config.LOGTAG, "trying to activate our proxy " + candidate); final SettableFuture iqFuture = SettableFuture.create(); - final IqPacket proxyActivation = new IqPacket(IqPacket.TYPE.SET); + final Iq proxyActivation = new Iq(Iq.Type.SET); proxyActivation.setTo(candidate.jid); final Element query = proxyActivation.addChild("query", Namespace.BYTE_STREAMS); query.setAttribute("sid", this.streamId); @@ -258,17 +258,18 @@ public class SocksByteStreamsTransport implements Transport { activate.setContent(id.with.toEscapedString()); xmppConnection.sendIqPacket( proxyActivation, - (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { Log.d(Config.LOGTAG, "our proxy has been activated"); transportCallback.onProxyActivated(this.streamId, candidate); iqFuture.set(candidate.cid); - } else if (response.getType() == IqPacket.TYPE.TIMEOUT) { + } else if (response.getType() == Iq.Type.TIMEOUT) { iqFuture.setException(new TimeoutException()); } else { + final var account = id.account; Log.d( Config.LOGTAG, - a.getJid().asBareJid() + account.getJid().asBareJid() + ": failed to activate proxy on " + candidate.jid); iqFuture.setException(new IllegalStateException("Proxy activation failed")); @@ -314,14 +315,14 @@ public class SocksByteStreamsTransport implements Transport { return Futures.immediateFailedFuture( new IllegalStateException("No proxy/streamer found")); } - final IqPacket iqRequest = new IqPacket(IqPacket.TYPE.GET); + final Iq iqRequest = new Iq(Iq.Type.GET); iqRequest.setTo(streamer); iqRequest.query(Namespace.BYTE_STREAMS); final SettableFuture candidateFuture = SettableFuture.create(); xmppConnection.sendIqPacket( iqRequest, - (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element query = response.findChild("query", Namespace.BYTE_STREAMS); final Element streamHost = query == null @@ -349,7 +350,7 @@ public class SocksByteStreamsTransport implements Transport { 655360 + (initiator ? 0 : 15), CandidateType.PROXY)); - } else if (response.getType() == IqPacket.TYPE.TIMEOUT) { + } else if (response.getType() == Iq.Type.TIMEOUT) { candidateFuture.setException(new TimeoutException()); } else { candidateFuture.setException( diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java index e4dd730d518cd8c3b34ba2134b298b7e65b7daff..904c4aba89568f863bef293db89959cf58e187c7 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java @@ -22,7 +22,7 @@ import eu.siacs.conversations.xmpp.jingle.IceServers; import eu.siacs.conversations.xmpp.jingle.WebRTCWrapper; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.WebRTCDataChannelTransportInfo; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import org.webrtc.CandidatePairChangeEvent; import org.webrtc.DataChannel; @@ -234,14 +234,14 @@ public class WebRTCDataChannelTransport implements Transport { if (xmppConnection.getFeatures().externalServiceDiscovery()) { final SettableFuture> iceServerFuture = SettableFuture.create(); - final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.setTo(this.account.getDomain()); request.addChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY); xmppConnection.sendIqPacket( request, - (account, response) -> { + (response) -> { final var iceServers = IceServers.parse(response); - if (iceServers.size() == 0) { + if (iceServers.isEmpty()) { Log.w( Config.LOGTAG, account.getJid().asBareJid() diff --git a/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java b/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java index ef1da85615c5c6c2db62590981dee5e312b52617..ee3770ead9f3a56322ba8fd62c4660b7f954b701 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java +++ b/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java @@ -4,7 +4,7 @@ import android.os.Bundle; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; public class PublishOptions { @@ -37,8 +37,8 @@ public class PublishOptions { return options; } - public static boolean preconditionNotMet(IqPacket response) { - final Element error = response.getType() == IqPacket.TYPE.ERROR ? response.findChild("error") : null; + public static boolean preconditionNotMet(Iq response) { + final Element error = response.getType() == Iq.Type.ERROR ? response.findChild("error") : null; return error != null && error.hasChild("precondition-not-met", Namespace.PUBSUB_ERROR); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java deleted file mode 100644 index 2291a9896110d594f99c5fda4f15210b03975cb6..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java +++ /dev/null @@ -1,42 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas; - -import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.InvalidJid; - -abstract public class AbstractAcknowledgeableStanza extends AbstractStanza { - - protected AbstractAcknowledgeableStanza(String name) { - super(name); - } - - - public String getId() { - return this.getAttribute("id"); - } - - public void setId(final String id) { - setAttribute("id", id); - } - - private Element getErrorConditionElement() { - final Element error = findChild("error"); - if (error == null) { - return null; - } - for (final Element element : error.getChildren()) { - if (!element.getName().equals("text")) { - return element; - } - } - return null; - } - - public String getErrorCondition() { - final Element condition = getErrorConditionElement(); - return condition == null ? null : condition.getName(); - } - - public boolean valid() { - return InvalidJid.isValid(getFrom()) && InvalidJid.isValid(getTo()); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java deleted file mode 100644 index c0b3d07bdd46afd9d939303c2a142a43894bd56a..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java +++ /dev/null @@ -1,53 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas; - -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.Jid; - -public class AbstractStanza extends Element { - - protected AbstractStanza(final String name) { - super(name); - } - - public Jid getTo() { - return getAttributeAsJid("to"); - } - - public Jid getFrom() { - return getAttributeAsJid("from"); - } - - public void setTo(final Jid to) { - if (to != null) { - setAttribute("to", to); - } - } - - public void setFrom(final Jid from) { - if (from != null) { - setAttribute("from", from); - } - } - - public boolean fromServer(final Account account) { - final Jid from = getFrom(); - return from == null - || from.equals(account.getDomain()) - || from.equals(account.getJid().asBareJid()) - || from.equals(account.getJid()); - } - - public boolean toServer(final Account account) { - final Jid to = getTo(); - return to == null - || to.equals(account.getDomain()) - || to.equals(account.getJid().asBareJid()) - || to.equals(account.getJid()); - } - - public boolean fromAccount(final Account account) { - final Jid from = getFrom(); - return from != null && from.asBareJid().equals(account.getJid().asBareJid()); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java deleted file mode 100644 index ba8588e1f552d5ab58387eed08929dd8a300d561..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java +++ /dev/null @@ -1,75 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas; - -import eu.siacs.conversations.xml.Element; - -public class IqPacket extends AbstractAcknowledgeableStanza { - - public enum TYPE { - ERROR, - SET, - RESULT, - GET, - INVALID, - TIMEOUT - } - - public IqPacket(final TYPE type) { - super("iq"); - if (type != TYPE.INVALID) { - this.setAttribute("type", type.toString().toLowerCase()); - } - } - - public IqPacket() { - super("iq"); - } - - public Element query() { - Element query = findChild("query"); - if (query == null) { - query = addChild("query"); - } - return query; - } - - public Element query(final String xmlns) { - final Element query = query(); - query.setAttribute("xmlns", xmlns); - return query(); - } - - public TYPE getType() { - final String type = getAttribute("type"); - if (type == null) { - return TYPE.INVALID; - } - switch (type) { - case "error": - return TYPE.ERROR; - case "result": - return TYPE.RESULT; - case "set": - return TYPE.SET; - case "get": - return TYPE.GET; - case "timeout": - return TYPE.TIMEOUT; - default: - return TYPE.INVALID; - } - } - - public IqPacket generateResponse(final TYPE type) { - final IqPacket packet = new IqPacket(type); - packet.setTo(this.getFrom()); - packet.setId(this.getId()); - return packet; - } - - @Override - public boolean valid() { - String id = getId(); - return id != null && super.valid(); - } - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java deleted file mode 100644 index 86068bf774efe9ed255c1def48812d0286ec8827..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java +++ /dev/null @@ -1,100 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas; - -import android.util.Pair; - -import eu.siacs.conversations.parser.AbstractParser; -import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xml.LocalizedContent; - -public class MessagePacket extends AbstractAcknowledgeableStanza { - public static final int TYPE_CHAT = 0; - public static final int TYPE_NORMAL = 2; - public static final int TYPE_GROUPCHAT = 3; - public static final int TYPE_ERROR = 4; - public static final int TYPE_HEADLINE = 5; - - public MessagePacket() { - super("message"); - } - - public LocalizedContent getBody() { - return findInternationalizedChildContentInDefaultNamespace("body"); - } - - public void setBody(String text) { - this.children.remove(findChild("body")); - Element body = new Element("body"); - body.setContent(text); - this.children.add(0, body); - } - - public void setAxolotlMessage(Element axolotlMessage) { - this.children.remove(findChild("body")); - this.children.add(0, axolotlMessage); - } - - public void setType(int type) { - switch (type) { - case TYPE_CHAT: - this.setAttribute("type", "chat"); - break; - case TYPE_GROUPCHAT: - this.setAttribute("type", "groupchat"); - break; - case TYPE_NORMAL: - break; - case TYPE_ERROR: - this.setAttribute("type","error"); - break; - default: - this.setAttribute("type", "chat"); - break; - } - } - - public int getType() { - String type = getAttribute("type"); - if (type == null) { - return TYPE_NORMAL; - } else if (type.equals("normal")) { - return TYPE_NORMAL; - } else if (type.equals("chat")) { - return TYPE_CHAT; - } else if (type.equals("groupchat")) { - return TYPE_GROUPCHAT; - } else if (type.equals("error")) { - return TYPE_ERROR; - } else if (type.equals("headline")) { - return TYPE_HEADLINE; - } else { - return TYPE_NORMAL; - } - } - - public Pair getForwardedMessagePacket(String name, String namespace) { - Element wrapper = findChild(name, namespace); - if (wrapper == null) { - return null; - } - Element forwarded = wrapper.findChild("forwarded", "urn:xmpp:forward:0"); - if (forwarded == null) { - return null; - } - MessagePacket packet = create(forwarded.findChild("message")); - if (packet == null) { - return null; - } - Long timestamp = AbstractParser.parseTimestamp(forwarded, null); - return new Pair(packet,timestamp); - } - - public static MessagePacket create(Element element) { - if (element == null) { - return null; - } - MessagePacket packet = new MessagePacket(); - packet.setAttributes(element.getAttributes()); - packet.setChildren(element.getChildren()); - return packet; - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java deleted file mode 100644 index c321498d86cd8bd3a7d38d8f76df7090c9212d85..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java +++ /dev/null @@ -1,8 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas; - -public class PresencePacket extends AbstractAcknowledgeableStanza { - - public PresencePacket() { - super("presence"); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java deleted file mode 100644 index e1c465f726b44c02fc28648ed893fc1b45a50ae2..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java +++ /dev/null @@ -1,11 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas.csi; - -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; - -public class ActivePacket extends AbstractStanza { - public ActivePacket() { - super("active"); - setAttribute("xmlns", Namespace.CSI); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java deleted file mode 100644 index 1b74de066d17eb5a806506a65c3fff8addcd49e7..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java +++ /dev/null @@ -1,11 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas.csi; - -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; - -public class InactivePacket extends AbstractStanza { - public InactivePacket() { - super("inactive"); - setAttribute("xmlns", Namespace.CSI); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java deleted file mode 100644 index 9e7b991a4b1c29e6399f6771398c635680cc016a..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java +++ /dev/null @@ -1,14 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas.streammgmt; - -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; - -public class AckPacket extends AbstractStanza { - - public AckPacket(final int sequence) { - super("a"); - this.setAttribute("xmlns", Namespace.STREAM_MANAGEMENT); - this.setAttribute("h", Integer.toString(sequence)); - } - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java deleted file mode 100644 index 95558b143230ba3e02e87544089c52c2b030581a..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java +++ /dev/null @@ -1,14 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas.streammgmt; - -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; - -public class EnablePacket extends AbstractStanza { - - public EnablePacket() { - super("enable"); - this.setAttribute("xmlns", Namespace.STREAM_MANAGEMENT); - this.setAttribute("resume", "true"); - } - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java deleted file mode 100644 index 4e0e0f11aa192f97bd461fffcb7d762477d90e9d..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java +++ /dev/null @@ -1,13 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas.streammgmt; - -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; - -public class RequestPacket extends AbstractStanza { - - public RequestPacket() { - super("r"); - this.setAttribute("xmlns", Namespace.STREAM_MANAGEMENT); - } - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java deleted file mode 100644 index 38681d7c1a370957ad4a251d310279c71b387ac8..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java +++ /dev/null @@ -1,15 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas.streammgmt; - -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; - -public class ResumePacket extends AbstractStanza { - - public ResumePacket(final String id, final int sequence) { - super("resume"); - this.setAttribute("xmlns", Namespace.STREAM_MANAGEMENT); - this.setAttribute("previd", id); - this.setAttribute("h", Integer.toString(sequence)); - } - -} diff --git a/src/main/java/im/conversations/android/xmpp/Entity.java b/src/main/java/im/conversations/android/xmpp/Entity.java new file mode 100644 index 0000000000000000000000000000000000000000..a578d250780e40c8b5a588a74f1ea43496f2c515 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/Entity.java @@ -0,0 +1,34 @@ +package im.conversations.android.xmpp; + +import org.jxmpp.jid.Jid; + +public abstract class Entity { + + public final Jid address; + + private Entity(final Jid address) { + this.address = address; + } + + public static class DiscoItem extends Entity { + + private DiscoItem(Jid address) { + super(address); + } + } + + public static class Presence extends Entity { + + private Presence(Jid address) { + super(address); + } + } + + public static Presence presence(final Jid address) { + return new Presence(address); + } + + public static DiscoItem discoItem(final Jid address) { + return new DiscoItem(address); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java b/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java new file mode 100644 index 0000000000000000000000000000000000000000..b282c07910706508ee0ff0a8b1ee70aa6699e400 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java @@ -0,0 +1,133 @@ +package im.conversations.android.xmpp; + +import com.google.common.base.Strings; +import com.google.common.collect.Collections2; +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Ordering; +import com.google.common.hash.Hashing; +import com.google.common.io.BaseEncoding; +import im.conversations.android.xmpp.model.data.Data; +import im.conversations.android.xmpp.model.data.Field; +import im.conversations.android.xmpp.model.disco.info.Feature; +import im.conversations.android.xmpp.model.disco.info.Identity; +import im.conversations.android.xmpp.model.disco.info.InfoQuery; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +public final class EntityCapabilities { + public static EntityCapsHash hash(final InfoQuery info) { + final StringBuilder s = new StringBuilder(); + final List orderedIdentities = + Ordering.from( + (Comparator) + (a, b) -> + ComparisonChain.start() + .compare( + blankNull(a.getCategory()), + blankNull(b.getCategory())) + .compare( + blankNull(a.getType()), + blankNull(b.getType())) + .compare( + blankNull(a.getLang()), + blankNull(b.getLang())) + .compare( + blankNull(a.getIdentityName()), + blankNull(b.getIdentityName())) + .result()) + .sortedCopy(info.getIdentities()); + + for (final Identity id : orderedIdentities) { + s.append(blankNull(id.getCategory())) + .append("/") + .append(blankNull(id.getType())) + .append("/") + .append(blankNull(id.getLang())) + .append("/") + .append(blankNull(id.getIdentityName())) + .append("<"); + } + + final List features = + Ordering.natural() + .sortedCopy(Collections2.transform(info.getFeatures(), Feature::getVar)); + for (final String feature : features) { + s.append(clean(feature)).append("<"); + } + + final List extensions = + Ordering.from(Comparator.comparing(Data::getFormType)) + .sortedCopy(info.getExtensions(Data.class)); + + for (final Data extension : extensions) { + s.append(clean(extension.getFormType())).append("<"); + final List fields = + Ordering.from( + Comparator.comparing( + (Field lhs) -> Strings.nullToEmpty(lhs.getFieldName()))) + .sortedCopy(extension.getFields()); + for (final Field field : fields) { + s.append(Strings.nullToEmpty(field.getFieldName())).append("<"); + final List values = Ordering.natural().sortedCopy(field.getValues()); + for (final String value : values) { + s.append(blankNull(value)).append("<"); + } + } + } + return new EntityCapsHash( + Hashing.sha1().hashString(s.toString(), StandardCharsets.UTF_8).asBytes()); + } + + private static String clean(String s) { + return s.replace("<", "<"); + } + + private static String blankNull(String s) { + return s == null ? "" : clean(s); + } + + public abstract static class Hash { + public final byte[] hash; + + protected Hash(byte[] hash) { + this.hash = hash; + } + + public String encoded() { + return BaseEncoding.base64().encode(hash); + } + + public abstract String capabilityNode(final String node); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Hash hash1 = (Hash) o; + return Arrays.equals(hash, hash1.hash); + } + + @Override + public int hashCode() { + return Arrays.hashCode(hash); + } + } + + public static class EntityCapsHash extends Hash { + + protected EntityCapsHash(byte[] hash) { + super(hash); + } + + @Override + public String capabilityNode(String node) { + return String.format("%s#%s", node, encoded()); + } + + public static EntityCapsHash of(final String encoded) { + return new EntityCapsHash(BaseEncoding.base64().decode(encoded)); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java b/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java new file mode 100644 index 0000000000000000000000000000000000000000..1d8a35a68d2d5822c2d0ada756b91e473bb698e9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java @@ -0,0 +1,185 @@ +package im.conversations.android.xmpp; + +import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.collect.Collections2; +import com.google.common.collect.Ordering; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; +import com.google.common.io.BaseEncoding; +import com.google.common.primitives.Bytes; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.xmpp.model.Hash; +import im.conversations.android.xmpp.model.data.Data; +import im.conversations.android.xmpp.model.data.Field; +import im.conversations.android.xmpp.model.data.Value; +import im.conversations.android.xmpp.model.disco.info.Feature; +import im.conversations.android.xmpp.model.disco.info.Identity; +import im.conversations.android.xmpp.model.disco.info.InfoQuery; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Objects; + +public class EntityCapabilities2 { + + private static final char UNIT_SEPARATOR = 0x1f; + private static final char RECORD_SEPARATOR = 0x1e; + + private static final char GROUP_SEPARATOR = 0x1d; + + private static final char FILE_SEPARATOR = 0x1c; + + public static EntityCaps2Hash hash(final InfoQuery info) { + return hash(Hash.Algorithm.SHA_256, info); + } + + public static EntityCaps2Hash hash(final Hash.Algorithm algorithm, final InfoQuery info) { + final String result = algorithm(info); + final var hashFunction = toHashFunction(algorithm); + return new EntityCaps2Hash( + algorithm, hashFunction.hashString(result, StandardCharsets.UTF_8).asBytes()); + } + + private static HashFunction toHashFunction(final Hash.Algorithm algorithm) { + switch (algorithm) { + case SHA_1: + return Hashing.sha1(); + case SHA_256: + return Hashing.sha256(); + case SHA_512: + return Hashing.sha512(); + default: + throw new IllegalArgumentException("Unknown hash algorithm"); + } + } + + private static String asHex(final String message) { + return Joiner.on(' ') + .join( + Collections2.transform( + Bytes.asList(message.getBytes(StandardCharsets.UTF_8)), + b -> String.format("%02x", b))); + } + + private static String algorithm(final InfoQuery infoQuery) { + return features(infoQuery.getFeatures()) + + identities(infoQuery.getIdentities()) + + extensions(infoQuery.getExtensions(Data.class)); + } + + private static String identities(final Collection identities) { + return Joiner.on("") + .join( + Ordering.natural() + .sortedCopy( + Collections2.transform( + identities, EntityCapabilities2::identity))) + + FILE_SEPARATOR; + } + + private static String identity(final Identity identity) { + return Strings.nullToEmpty(identity.getCategory()) + + UNIT_SEPARATOR + + Strings.nullToEmpty(identity.getType()) + + UNIT_SEPARATOR + + Strings.nullToEmpty(identity.getLang()) + + UNIT_SEPARATOR + + Strings.nullToEmpty(identity.getIdentityName()) + + UNIT_SEPARATOR + + RECORD_SEPARATOR; + } + + private static String features(Collection features) { + return Joiner.on("") + .join( + Ordering.natural() + .sortedCopy( + Collections2.transform( + features, EntityCapabilities2::feature))) + + FILE_SEPARATOR; + } + + private static String feature(final Feature feature) { + return Strings.nullToEmpty(feature.getVar()) + UNIT_SEPARATOR; + } + + private static String value(final Value value) { + return Strings.nullToEmpty(value.getContent()) + UNIT_SEPARATOR; + } + + private static String values(final Collection values) { + return Joiner.on("") + .join( + Ordering.natural() + .sortedCopy( + Collections2.transform( + values, EntityCapabilities2::value))); + } + + private static String field(final Field field) { + return Strings.nullToEmpty(field.getFieldName()) + + UNIT_SEPARATOR + + values(field.getExtensions(Value.class)) + + RECORD_SEPARATOR; + } + + private static String fields(final Collection fields) { + return Joiner.on("") + .join( + Ordering.natural() + .sortedCopy( + Collections2.transform( + fields, EntityCapabilities2::field))) + + GROUP_SEPARATOR; + } + + private static String extension(final Data data) { + return fields(data.getExtensions(Field.class)); + } + + private static String extensions(final Collection extensions) { + return Joiner.on("") + .join( + Ordering.natural() + .sortedCopy( + Collections2.transform( + extensions, + EntityCapabilities2::extension))) + + FILE_SEPARATOR; + } + + public static class EntityCaps2Hash extends EntityCapabilities.Hash { + + public final Hash.Algorithm algorithm; + + protected EntityCaps2Hash(final Hash.Algorithm algorithm, byte[] hash) { + super(hash); + this.algorithm = algorithm; + } + + public static EntityCaps2Hash of(final Hash.Algorithm algorithm, final String encoded) { + return new EntityCaps2Hash(algorithm, BaseEncoding.base64().decode(encoded)); + } + + @Override + public String capabilityNode(String node) { + return String.format( + "%s#%s.%s", Namespace.ENTITY_CAPABILITIES_2, algorithm.toString(), encoded()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + EntityCaps2Hash that = (EntityCaps2Hash) o; + return algorithm == that.algorithm; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), algorithm); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java b/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..04352b559312c5930456d4729783f49e691d4bf9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java @@ -0,0 +1,78 @@ +package im.conversations.android.xmpp; + + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + +import eu.siacs.conversations.xml.Element; + +import im.conversations.android.xmpp.model.Extension; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +public final class ExtensionFactory { + + public static Element create(final String name, final String namespace) { + final Class clazz = of(name, namespace); + if (clazz == null) { + return new Element(name, namespace); + } + final Constructor constructor; + try { + constructor = clazz.getDeclaredConstructor(); + } catch (final NoSuchMethodException e) { + throw new IllegalStateException( + String.format("%s has no default constructor", clazz.getName()),e); + } + try { + return constructor.newInstance(); + } catch (final IllegalAccessException + | InstantiationException + | InvocationTargetException e) { + throw new IllegalStateException( + String.format("%s has inaccessible default constructor", clazz.getName()),e); + } + } + + private static Class of(final String name, final String namespace) { + return Extensions.EXTENSION_CLASS_MAP.get(new Id(name, namespace)); + } + + public static Id id(final Class clazz) { + return Extensions.EXTENSION_CLASS_MAP.inverse().get(clazz); + } + + private ExtensionFactory() {} + + public static class Id { + public final String name; + public final String namespace; + + public Id(String name, String namespace) { + this.name = name; + this.namespace = namespace; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Id id = (Id) o; + return Objects.equal(name, id.name) && Objects.equal(namespace, id.namespace); + } + + @Override + public int hashCode() { + return Objects.hashCode(name, namespace); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("name", name) + .add("namespace", namespace) + .toString(); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java b/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..81a55f18c5a80b7acf2a90ccda9661169ff0e05f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java @@ -0,0 +1,112 @@ +package im.conversations.android.xmpp; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.common.collect.ImmutableMap; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +public class NodeConfiguration implements Map { + + private static final String PERSIST_ITEMS = "pubsub#persist_items"; + private static final String ACCESS_MODEL = "pubsub#access_model"; + private static final String SEND_LAST_PUBLISHED_ITEM = "pubsub#send_last_published_item"; + private static final String MAX_ITEMS = "pubsub#max_items"; + private static final String NOTIFY_DELETE = "pubsub#notify_delete"; + private static final String NOTIFY_RETRACT = "pubsub#notify_retract"; + + public static final NodeConfiguration OPEN = + new NodeConfiguration( + new ImmutableMap.Builder() + .put(PERSIST_ITEMS, Boolean.TRUE) + .put(ACCESS_MODEL, "open") + .build()); + public static final NodeConfiguration PRESENCE = + new NodeConfiguration( + new ImmutableMap.Builder() + .put(PERSIST_ITEMS, Boolean.TRUE) + .put(ACCESS_MODEL, "presence") + .build()); + public static final NodeConfiguration WHITELIST_MAX_ITEMS = + new NodeConfiguration( + new ImmutableMap.Builder() + .put(PERSIST_ITEMS, Boolean.TRUE) + .put(ACCESS_MODEL, "whitelist") + .put(SEND_LAST_PUBLISHED_ITEM, "never") + .put(MAX_ITEMS, "max") + .put(NOTIFY_DELETE, Boolean.TRUE) + .put(NOTIFY_RETRACT, Boolean.TRUE) + .build()); + private final Map delegate; + + private NodeConfiguration(Map map) { + this.delegate = map; + } + + @Override + public int size() { + return this.delegate.size(); + } + + @Override + public boolean isEmpty() { + return this.delegate.isEmpty(); + } + + @Override + public boolean containsKey(@Nullable Object o) { + return this.delegate.containsKey(o); + } + + @Override + public boolean containsValue(@Nullable Object o) { + return this.delegate.containsValue(o); + } + + @Nullable + @Override + public Object get(@Nullable Object o) { + return this.delegate.get(o); + } + + @Nullable + @Override + public Object put(String s, Object o) { + return this.delegate.put(s, o); + } + + @Nullable + @Override + public Object remove(@Nullable Object o) { + return this.delegate.remove(o); + } + + @Override + public void putAll(@NonNull Map map) { + this.delegate.putAll(map); + } + + @Override + public void clear() { + this.delegate.clear(); + } + + @NonNull + @Override + public Set keySet() { + return this.delegate.keySet(); + } + + @NonNull + @Override + public Collection values() { + return this.delegate.values(); + } + + @NonNull + @Override + public Set> entrySet() { + return this.delegate.entrySet(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/Page.java b/src/main/java/im/conversations/android/xmpp/Page.java new file mode 100644 index 0000000000000000000000000000000000000000..21aa219a44731d091955bf4791fbdffa70fee2f5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/Page.java @@ -0,0 +1,31 @@ +package im.conversations.android.xmpp; + +import androidx.annotation.NonNull; +import com.google.common.base.MoreObjects; + +public class Page { + + public final String first; + public final String last; + public final Integer count; + + public Page(String first, String last, Integer count) { + this.first = first; + this.last = last; + this.count = count; + } + + public static Page emptyWithCount(final String id, final Integer count) { + return new Page(id, id, count); + } + + @NonNull + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("first", first) + .add("last", last) + .add("count", count) + .toString(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/Range.java b/src/main/java/im/conversations/android/xmpp/Range.java new file mode 100644 index 0000000000000000000000000000000000000000..8aff5094e91d54fbde845cefc481c0156fbca3b1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/Range.java @@ -0,0 +1,40 @@ +package im.conversations.android.xmpp; + +import androidx.annotation.NonNull; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; + +public class Range { + + public final Order order; + public final String id; + + public Range(final Order order, final String id) { + this.order = order; + this.id = id; + } + + @NonNull + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("order", order).add("id", id).toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Range range = (Range) o; + return order == range.order && Objects.equal(id, range.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(order, id); + } + + public enum Order { + NORMAL, + REVERSE + } +} diff --git a/src/main/java/im/conversations/android/xmpp/Timestamps.java b/src/main/java/im/conversations/android/xmpp/Timestamps.java new file mode 100644 index 0000000000000000000000000000000000000000..0135901abf1dca3b2a8292c9bb8b0d538bb5ab13 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/Timestamps.java @@ -0,0 +1,44 @@ +package im.conversations.android.xmpp; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public final class Timestamps { + + private Timestamps() { + throw new IllegalStateException("Do not instantiate me"); + } + + public static long parse(final String input) throws ParseException { + if (input == null) { + throw new IllegalArgumentException("timestamp should not be null"); + } + final String timestamp = input.replace("Z", "+0000"); + final SimpleDateFormat simpleDateFormat = + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US); + final long milliseconds = getMilliseconds(timestamp); + final String formatted = + timestamp.substring(0, 19) + timestamp.substring(timestamp.length() - 5); + final Date date = simpleDateFormat.parse(formatted); + if (date == null) { + throw new IllegalArgumentException("Date was null"); + } + return date.getTime() + milliseconds; + } + + private static long getMilliseconds(final String timestamp) { + if (timestamp.length() >= 25 && timestamp.charAt(19) == '.') { + final String millis = timestamp.substring(19, timestamp.length() - 5); + try { + double fractions = Double.parseDouble("0" + millis); + return Math.round(1000 * fractions); + } catch (final NumberFormatException e) { + return 0; + } + } else { + return 0; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/AuthenticationStreamFeature.java b/src/main/java/im/conversations/android/xmpp/model/AuthenticationStreamFeature.java new file mode 100644 index 0000000000000000000000000000000000000000..3b9a03761125dd451ea7ce4a96d66a979f2440d8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/AuthenticationStreamFeature.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model; + +import java.util.Collection; + +public abstract class AuthenticationStreamFeature extends StreamFeature{ + + public AuthenticationStreamFeature(final Class clazz) { + super(clazz); + } + + public abstract Collection getMechanismNames(); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/ByteContent.java b/src/main/java/im/conversations/android/xmpp/model/ByteContent.java new file mode 100644 index 0000000000000000000000000000000000000000..0ca6212ff75a2deadcb807c049c4c7917bbc5abe --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/ByteContent.java @@ -0,0 +1,33 @@ +package im.conversations.android.xmpp.model; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Strings; +import com.google.common.io.BaseEncoding; + +import eu.siacs.conversations.xml.Element; + +public interface ByteContent { + + String getContent(); + + default byte[] asBytes() { + final var content = this.getContent(); + if (Strings.isNullOrEmpty(content)) { + throw new IllegalStateException( + String.format("%s element is lacking content", getClass().getName())); + } + final var contentCleaned = CharMatcher.whitespace().removeFrom(content); + if (BaseEncoding.base64().canDecode(contentCleaned)) { + return BaseEncoding.base64().decode(contentCleaned); + } else { + throw new IllegalStateException( + String.format("%s element contains invalid base64", getClass().getName())); + } + } + + default void setContent(final byte[] bytes) { + setContent(BaseEncoding.base64().encode(bytes)); + } + + Element setContent(final String content); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java b/src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java new file mode 100644 index 0000000000000000000000000000000000000000..00e2b652aa9e1970191a9f1e053c9ed6f2e4b8a4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model; + +public abstract class DeliveryReceipt extends Extension { + + protected DeliveryReceipt(Class clazz) { + super(clazz); + } + + public abstract String getId(); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java b/src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..a5a7533bbc23902494dc345f9b0146bb29e2f4f1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java @@ -0,0 +1,8 @@ +package im.conversations.android.xmpp.model; + +public abstract class DeliveryReceiptRequest extends Extension { + + protected DeliveryReceiptRequest(Class clazz) { + super(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/Extension.java b/src/main/java/im/conversations/android/xmpp/model/Extension.java new file mode 100644 index 0000000000000000000000000000000000000000..094ed6ae5146b7f5abac8f41dcd6af389cc87e61 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/Extension.java @@ -0,0 +1,62 @@ +package im.conversations.android.xmpp.model; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Collections2; +import com.google.common.collect.Iterables; + +import eu.siacs.conversations.xml.Element; + +import im.conversations.android.xmpp.ExtensionFactory; + +import java.util.Collection; + +public class Extension extends Element { + + private Extension(final ExtensionFactory.Id id) { + super(id.name, id.namespace); + } + + public Extension(final Class clazz) { + this( + Preconditions.checkNotNull( + ExtensionFactory.id(clazz), + String.format( + "%s does not seem to be annotated with @XmlElement", + clazz.getName()))); + Preconditions.checkArgument( + getClass().equals(clazz), "clazz passed in constructor must match class"); + } + + public boolean hasExtension(final Class clazz) { + return Iterables.any(this.children, clazz::isInstance); + } + + public E getExtension(final Class clazz) { + final var extension = Iterables.find(this.children, clazz::isInstance, null); + if (extension == null) { + return null; + } + return clazz.cast(extension); + } + + public Collection getExtensions(final Class clazz) { + return Collections2.transform( + Collections2.filter(this.children, clazz::isInstance), clazz::cast); + } + + public Collection getExtensionIds() { + return Collections2.transform( + this.children, c -> new ExtensionFactory.Id(c.getName(), c.getNamespace())); + } + + public T addExtension(T child) { + this.addChild(child); + return child; + } + + public void addExtensions(final Collection extensions) { + for (final Extension extension : extensions) { + addExtension(extension); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/Hash.java b/src/main/java/im/conversations/android/xmpp/model/Hash.java new file mode 100644 index 0000000000000000000000000000000000000000..8c41add8d9eefd69ddf246534e66b7bd74256769 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/Hash.java @@ -0,0 +1,46 @@ +package im.conversations.android.xmpp.model; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.common.base.CaseFormat; +import com.google.common.base.Strings; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; + +@XmlElement(namespace = Namespace.HASHES) +public class Hash extends Extension { + public Hash() { + super(Hash.class); + } + + public Algorithm getAlgorithm() { + return Algorithm.tryParse(this.getAttribute("algo")); + } + + public void setAlgorithm(final Algorithm algorithm) { + this.setAttribute("algo", algorithm.toString()); + } + + public enum Algorithm { + SHA_1, + SHA_256, + SHA_512; + + public static Algorithm tryParse(@Nullable final String name) { + try { + return valueOf( + CaseFormat.LOWER_HYPHEN.to( + CaseFormat.UPPER_UNDERSCORE, Strings.nullToEmpty(name))); + } catch (final IllegalArgumentException e) { + return null; + } + } + + @NonNull + @Override + public String toString() { + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, super.toString()); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/StreamElement.java b/src/main/java/im/conversations/android/xmpp/model/StreamElement.java new file mode 100644 index 0000000000000000000000000000000000000000..ca5fd0053b0839a479d5fddedc999b64b09f764f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/StreamElement.java @@ -0,0 +1,8 @@ +package im.conversations.android.xmpp.model; + +public abstract class StreamElement extends Extension { + + protected StreamElement(Class clazz) { + super(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/StreamFeature.java b/src/main/java/im/conversations/android/xmpp/model/StreamFeature.java new file mode 100644 index 0000000000000000000000000000000000000000..eadd8d8c4407e9b175ab8e5359e64cd5d505dbd5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/StreamFeature.java @@ -0,0 +1,8 @@ +package im.conversations.android.xmpp.model; + +public abstract class StreamFeature extends Extension{ + + public StreamFeature(Class clazz) { + super(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/addressing/Address.java b/src/main/java/im/conversations/android/xmpp/model/addressing/Address.java new file mode 100644 index 0000000000000000000000000000000000000000..f812ec53beac1689a78daf6fd10f5127c2d1f9e1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/addressing/Address.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.addressing; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Address extends Extension { + public Address() { + super(Address.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/addressing/Addresses.java b/src/main/java/im/conversations/android/xmpp/model/addressing/Addresses.java new file mode 100644 index 0000000000000000000000000000000000000000..3ecafc53095f671f5b4de839d2e58b4074c0e3e9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/addressing/Addresses.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.addressing; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Addresses extends Extension { + public Addresses() { + super(Addresses.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/addressing/package-info.java b/src/main/java/im/conversations/android/xmpp/model/addressing/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..6c504489e2943155c7d2a23cb19354beefea692f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/addressing/package-info.java @@ -0,0 +1,6 @@ +@XmlPackage(namespace = Namespace.ADDRESSING) +package im.conversations.android.xmpp.model.addressing; + +import eu.siacs.conversations.xml.Namespace; + +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/avatar/Data.java b/src/main/java/im/conversations/android/xmpp/model/avatar/Data.java new file mode 100644 index 0000000000000000000000000000000000000000..b661bca3a329475738cf5167871380828e6f7825 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/avatar/Data.java @@ -0,0 +1,14 @@ +package im.conversations.android.xmpp.model.avatar; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.ByteContent; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.AVATAR_DATA) +public class Data extends Extension implements ByteContent { + + public Data() { + super(Data.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java b/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java new file mode 100644 index 0000000000000000000000000000000000000000..f544af72fc784e0b935bbac0c767bd3504194807 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java @@ -0,0 +1,37 @@ +package im.conversations.android.xmpp.model.avatar; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.AVATAR_METADATA) +public class Info extends Extension { + + public Info() { + super(Info.class); + } + + public long getHeight() { + return this.getLongAttribute("height"); + } + + public long getWidth() { + return this.getLongAttribute("width"); + } + + public long getBytes() { + return this.getLongAttribute("bytes"); + } + + public String getType() { + return this.getAttribute("type"); + } + + public String getUrl() { + return this.getAttribute("url"); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java b/src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java new file mode 100644 index 0000000000000000000000000000000000000000..400f989572c2813aaeacdce5863e012ba128af3e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.avatar; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.AVATAR_METADATA) +public class Metadata extends Extension { + + public Metadata() { + super(Metadata.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java new file mode 100644 index 0000000000000000000000000000000000000000..2321c2e49b6ebed7b8bb2b3a4bce3f9e41fb459a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java @@ -0,0 +1,60 @@ +package im.conversations.android.xmpp.model.axolotl; + +import com.google.common.collect.Iterables; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.whispersystems.libsignal.ecc.ECPublicKey; +import org.whispersystems.libsignal.state.PreKeyRecord; + +@XmlElement +public class Bundle extends Extension { + + public Bundle() { + super(Bundle.class); + } + + public SignedPreKey getSignedPreKey() { + return this.getExtension(SignedPreKey.class); + } + + public SignedPreKeySignature getSignedPreKeySignature() { + return this.getExtension(SignedPreKeySignature.class); + } + + public IdentityKey getIdentityKey() { + return this.getExtension(IdentityKey.class); + } + + public PreKey getRandomPreKey() { + final var preKeys = this.getExtension(PreKeys.class); + final Collection preKeyList = + preKeys == null ? Collections.emptyList() : preKeys.getExtensions(PreKey.class); + return Iterables.get(preKeyList, (int) (preKeyList.size() * Math.random()), null); + } + + public void setIdentityKey(final ECPublicKey ecPublicKey) { + final var identityKey = this.addExtension(new IdentityKey()); + identityKey.setContent(ecPublicKey); + } + + public void setSignedPreKey( + final int id, final ECPublicKey ecPublicKey, final byte[] signature) { + final var signedPreKey = this.addExtension(new SignedPreKey()); + signedPreKey.setId(id); + signedPreKey.setContent(ecPublicKey); + final var signedPreKeySignature = this.addExtension(new SignedPreKeySignature()); + signedPreKeySignature.setContent(signature); + } + + public void addPreKeys(final List preKeyRecords) { + final var preKeys = this.addExtension(new PreKeys()); + for (final PreKeyRecord preKeyRecord : preKeyRecords) { + final var preKey = preKeys.addExtension(new PreKey()); + preKey.setId(preKeyRecord.getId()); + preKey.setContent(preKeyRecord.getKeyPair().getPublicKey()); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java new file mode 100644 index 0000000000000000000000000000000000000000..0ad10d7025e912b3b9d88952e64000ce7db4c46f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java @@ -0,0 +1,22 @@ +package im.conversations.android.xmpp.model.axolotl; + +import com.google.common.base.Strings; +import com.google.common.primitives.Ints; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Device extends Extension { + + public Device() { + super(Device.class); + } + + public Integer getDeviceId() { + return Ints.tryParse(Strings.nullToEmpty(this.getAttribute("id"))); + } + + public void setDeviceId(int deviceId) { + this.setAttribute("id", deviceId); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java new file mode 100644 index 0000000000000000000000000000000000000000..ec4fce469437793b0a7d63d129d12fb3f45cd1a1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java @@ -0,0 +1,35 @@ +package im.conversations.android.xmpp.model.axolotl; + +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableSet; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import java.util.Objects; +import java.util.Set; + +@XmlElement(name = "list") +public class DeviceList extends Extension { + + public DeviceList() { + super(DeviceList.class); + } + + public Collection getDevices() { + return this.getExtensions(Device.class); + } + + public Set getDeviceIds() { + return ImmutableSet.copyOf( + Collections2.filter( + Collections2.transform(getDevices(), Device::getDeviceId), + Objects::nonNull)); + } + + public void setDeviceIds(Collection deviceIds) { + for (final Integer deviceId : deviceIds) { + final var device = this.addExtension(new Device()); + device.setDeviceId(deviceId); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java new file mode 100644 index 0000000000000000000000000000000000000000..2008fb017d2a9cdba76c7756ea6187a2c15fd1ec --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java @@ -0,0 +1,23 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.xmpp.model.ByteContent; +import org.whispersystems.libsignal.InvalidKeyException; +import org.whispersystems.libsignal.ecc.Curve; +import org.whispersystems.libsignal.ecc.ECPublicKey; + +public interface ECPublicKeyContent extends ByteContent { + + default ECPublicKey asECPublicKey() { + try { + return Curve.decodePoint(asBytes(), 0); + } catch (InvalidKeyException e) { + throw new IllegalStateException( + String.format("%s does not contain a valid ECPublicKey", getClass().getName()), + e); + } + } + + default void setContent(final ECPublicKey ecPublicKey) { + setContent(ecPublicKey.serialize()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java new file mode 100644 index 0000000000000000000000000000000000000000..1a98068ab73cedbef8f385cae450cfaf37ad6f64 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java @@ -0,0 +1,24 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Encrypted extends Extension { + + public Encrypted() { + super(Encrypted.class); + } + + public boolean hasPayload() { + return hasExtension(Payload.class); + } + + public Header getHeader() { + return getExtension(Header.class); + } + + public Payload getPayload() { + return getExtension(Payload.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java new file mode 100644 index 0000000000000000000000000000000000000000..91e2bd87ba6d44f152d8ee90d3eaafc639b0a369 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java @@ -0,0 +1,45 @@ +package im.conversations.android.xmpp.model.axolotl; + +import com.google.common.base.Optional; +import com.google.common.collect.Iterables; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import java.util.Objects; + +@XmlElement +public class Header extends Extension { + + public Header() { + super(Header.class); + } + + public void addIv(byte[] iv) { + this.addExtension(new IV()).setContent(iv); + } + + public void setSourceDevice(long sourceDeviceId) { + this.setAttribute("sid", sourceDeviceId); + } + + public Optional getSourceDevice() { + return getOptionalIntAttribute("sid"); + } + + public Collection getKeys() { + return this.getExtensions(Key.class); + } + + public Key getKey(final int deviceId) { + return Iterables.find( + getKeys(), key -> Objects.equals(key.getRemoteDeviceId(), deviceId), null); + } + + public byte[] getIv() { + final IV iv = this.getExtension(IV.class); + if (iv == null) { + throw new IllegalStateException("No IV in header"); + } + return iv.asBytes(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java new file mode 100644 index 0000000000000000000000000000000000000000..22164976a92c84dfbcd9a2a12c9161ce5c5f998a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.ByteContent; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "iv") +public class IV extends Extension implements ByteContent { + + public IV() { + super(IV.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java new file mode 100644 index 0000000000000000000000000000000000000000..f48fcbd7cb3bd3c0aa6a7992c2c6b7ebe0ba959f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "identityKey") +public class IdentityKey extends Extension implements ECPublicKeyContent { + + public IdentityKey() { + super(IdentityKey.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Key.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Key.java new file mode 100644 index 0000000000000000000000000000000000000000..3ad7357b8dd934303b42d71e5b37f941e95abbca --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Key.java @@ -0,0 +1,29 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.ByteContent; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Key extends Extension implements ByteContent { + + public Key() { + super(Key.class); + } + + public void setIsPreKey(boolean isPreKey) { + this.setAttribute("prekey", isPreKey); + } + + public boolean isPreKey() { + return this.getAttributeAsBoolean("prekey"); + } + + public void setRemoteDeviceId(final int remoteDeviceId) { + this.setAttribute("rid", remoteDeviceId); + } + + public Integer getRemoteDeviceId() { + return getOptionalIntAttribute("rid").orNull(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java new file mode 100644 index 0000000000000000000000000000000000000000..9c58701100005988ebd2684d5e0475fdb1d0dcf0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.ByteContent; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Payload extends Extension implements ByteContent { + + public Payload() { + super(Payload.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java new file mode 100644 index 0000000000000000000000000000000000000000..a7d39c1daf82b541387ee867ffb29c57e0cdcaf6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.axolotl; + +import com.google.common.primitives.Ints; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "preKeyPublic") +public class PreKey extends Extension implements ECPublicKeyContent { + + public PreKey() { + super(PreKey.class); + } + + public int getId() { + return Ints.saturatedCast(this.getLongAttribute("preKeyId")); + } + + public void setId(int id) { + this.setAttribute("preKeyId", id); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java new file mode 100644 index 0000000000000000000000000000000000000000..3613b8aa8a9ade6129bbeaa9462314246d282e5d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "prekeys") +public class PreKeys extends Extension { + + public PreKeys() { + super(PreKeys.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java new file mode 100644 index 0000000000000000000000000000000000000000..0e0ca728231e71a4f4dd9df3227a6fed537a59db --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.axolotl; + +import com.google.common.primitives.Ints; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "signedPreKeyPublic") +public class SignedPreKey extends Extension implements ECPublicKeyContent { + + public SignedPreKey() { + super(SignedPreKey.class); + } + + public int getId() { + return Ints.saturatedCast(this.getLongAttribute("signedPreKeyId")); + } + + public void setId(final int id) { + this.setAttribute("signedPreKeyId", id); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java new file mode 100644 index 0000000000000000000000000000000000000000..5051cb1b14fc3c9e996cf7bd6cbe966eba130fee --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.ByteContent; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "signedPreKeySignature") +public class SignedPreKeySignature extends Extension implements ByteContent { + + public SignedPreKeySignature() { + super(SignedPreKeySignature.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..ad3d21c16c79dfbbcf989c77f28cd94cd91de124 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.AXOLOTL) +package im.conversations.android.xmpp.model.axolotl; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java b/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java new file mode 100644 index 0000000000000000000000000000000000000000..27264f7545e079f2c3cb6415f4763ab3c334dc3b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java @@ -0,0 +1,34 @@ +package im.conversations.android.xmpp.model.bind; + +import com.google.common.base.Strings; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Bind extends Extension { + + public Bind() { + super(Bind.class); + } + + public void setResource(final String resource) { + this.addExtension(new Resource(resource)); + } + + public eu.siacs.conversations.xmpp.Jid getJid() { + final var jidExtension = this.getExtension(Jid.class); + if (jidExtension == null) { + return null; + } + final var content = jidExtension.getContent(); + if (Strings.isNullOrEmpty(content)) { + return null; + } + try { + return eu.siacs.conversations.xmpp.Jid.ofEscaped(content); + } catch (final IllegalArgumentException e) { + return null; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/Jid.java b/src/main/java/im/conversations/android/xmpp/model/bind/Jid.java new file mode 100644 index 0000000000000000000000000000000000000000..04633a009929cce3b7776f87f5cd28c3f767cd49 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind/Jid.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.bind; + + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Jid extends Extension { + + public Jid() { + super(Jid.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/Resource.java b/src/main/java/im/conversations/android/xmpp/model/bind/Resource.java new file mode 100644 index 0000000000000000000000000000000000000000..b3fd1e5c17d070ba994754f499e74a0dd0d7cf31 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind/Resource.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.bind; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Resource extends Extension { + public Resource() { + super(Resource.class); + } + + public Resource(final String resource) { + this(); + this.setContent(resource); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/package-info.java b/src/main/java/im/conversations/android/xmpp/model/bind/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..aebaeeb729420e22431f57a7834d23c9578f6be0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.BIND) +package im.conversations.android.xmpp.model.bind; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java new file mode 100644 index 0000000000000000000000000000000000000000..bff72a06b5f595aa0f3d184c0b3cabad96317c46 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java @@ -0,0 +1,24 @@ +package im.conversations.android.xmpp.model.bind2; + +import java.util.Collection; +import java.util.Collections; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Bind extends Extension { + + public Bind() { + super(Bind.class); + } + + public Inline getInline() { + return this.getExtension(Inline.class); + } + + public Collection getInlineFeatures() { + final var inline = getInline(); + return inline == null ? Collections.emptyList() : inline.getExtensions(Feature.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Bound.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Bound.java new file mode 100644 index 0000000000000000000000000000000000000000..0144edb9147ae909c1e11626fcd38c8a20c6cde7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Bound.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.bind2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Bound extends Extension { + public Bound() { + super(Bound.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Feature.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Feature.java new file mode 100644 index 0000000000000000000000000000000000000000..66720abbc631da53c7308095d87138db0d561156 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Feature.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.bind2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Feature extends Extension { + + public Feature() { + super(Feature.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Inline.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Inline.java new file mode 100644 index 0000000000000000000000000000000000000000..641a9d4f4556673493d9648c4d8e80e0e73123f5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Inline.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.bind2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Inline extends Extension { + + public Inline() { + super(Inline.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/package-info.java b/src/main/java/im/conversations/android/xmpp/model/bind2/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..2d8c5e92cb122bfb76f8dada143389791de0295d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.BIND2) +package im.conversations.android.xmpp.model.bind2; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Block.java b/src/main/java/im/conversations/android/xmpp/model/blocking/Block.java new file mode 100644 index 0000000000000000000000000000000000000000..6f5d00b3e566d1651059d75fb350c2a233959439 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/Block.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.blocking; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Block extends Extension { + + public Block() { + super(Block.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java b/src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java new file mode 100644 index 0000000000000000000000000000000000000000..a56662d77152bf2d22bc11d76f8d01cb90f30c52 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.blocking; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Blocklist extends Extension { + public Blocklist() { + super(Blocklist.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java b/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java new file mode 100644 index 0000000000000000000000000000000000000000..647b0ae9912de215bffb4f5057bbc95dae3e6bfd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.blocking; + +import eu.siacs.conversations.xmpp.Jid; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Item extends Extension { + + public Item() { + super(Item.class); + } + + public Jid getJid() { + return getAttributeAsJid("jid"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java b/src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java new file mode 100644 index 0000000000000000000000000000000000000000..90cec110c050788661af7afbae475d68a7906d26 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.blocking; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Unblock extends Extension { + + public Unblock() { + super(Unblock.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java b/src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..22d8f0e1f0b7bdb1d92d58403e5fdf3c6a3d459f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.BLOCKING) +package im.conversations.android.xmpp.model.blocking; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java b/src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java new file mode 100644 index 0000000000000000000000000000000000000000..0f924e8883b40157d0aa96676ab5f6412e971138 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java @@ -0,0 +1,32 @@ +package im.conversations.android.xmpp.model.bookmark; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Conference extends Extension { + + public Conference() { + super(Conference.class); + } + + public boolean isAutoJoin() { + return this.getAttributeAsBoolean("autojoin"); + } + + public String getConferenceName() { + return this.getAttribute("name"); + } + + public void setAutoJoin(boolean autoJoin) { + setAttribute("autojoin", autoJoin); + } + + public Nick getNick() { + return this.getExtension(Nick.class); + } + + public Extensions getExtensions() { + return this.getExtension(Extensions.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/Extensions.java b/src/main/java/im/conversations/android/xmpp/model/bookmark/Extensions.java new file mode 100644 index 0000000000000000000000000000000000000000..b9385cf5473de88d5d43d771a57fe88995bc2536 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bookmark/Extensions.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.bookmark; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Extensions extends Extension { + + public Extensions() { + super(Extensions.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/Nick.java b/src/main/java/im/conversations/android/xmpp/model/bookmark/Nick.java new file mode 100644 index 0000000000000000000000000000000000000000..ee5efa3864baebb0c848c65769b6b0a8d4a69022 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bookmark/Nick.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.bookmark; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Nick extends Extension { + + public Nick() { + super(Nick.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java b/src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..1bb963be849a57cdfeae7a2eb1c8930db49c799a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.BOOKMARKS2) +package im.conversations.android.xmpp.model.bookmark; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java b/src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java new file mode 100644 index 0000000000000000000000000000000000000000..d0f23b2833e9d1e0694d168e5fe0d65769887d4a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java @@ -0,0 +1,43 @@ +package im.conversations.android.xmpp.model.capabilties; + +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; +import com.google.common.io.BaseEncoding; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.EntityCapabilities2; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.Hash; + +@XmlElement(name = "c", namespace = Namespace.ENTITY_CAPABILITIES_2) +public class Capabilities extends Extension { + + public Capabilities() { + super(Capabilities.class); + } + + public EntityCapabilities2.EntityCaps2Hash getHash() { + final Optional sha256Hash = + Iterables.tryFind( + getExtensions(Hash.class), h -> h.getAlgorithm() == Hash.Algorithm.SHA_256); + if (sha256Hash.isPresent()) { + final String content = sha256Hash.get().getContent(); + if (Strings.isNullOrEmpty(content)) { + return null; + } + if (BaseEncoding.base64().canDecode(content)) { + return EntityCapabilities2.EntityCaps2Hash.of(Hash.Algorithm.SHA_256, content); + } + } + return null; + } + + public void setHash(final EntityCapabilities2.EntityCaps2Hash caps2Hash) { + final Hash hash = new Hash(); + hash.setAlgorithm(caps2Hash.algorithm); + hash.setContent(caps2Hash.encoded()); + this.addExtension(hash); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java b/src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java new file mode 100644 index 0000000000000000000000000000000000000000..f8ed4ef66196030c51ad5bdac612ffc417e0c2c7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java @@ -0,0 +1,39 @@ +package im.conversations.android.xmpp.model.capabilties; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import im.conversations.android.xmpp.model.Extension; + +public interface EntityCapabilities { + + E getExtension(final Class clazz); + + default NodeHash getCapabilities() { + final String node; + final im.conversations.android.xmpp.EntityCapabilities.Hash hash; + final var capabilities = this.getExtension(Capabilities.class); + final var legacyCapabilities = this.getExtension(LegacyCapabilities.class); + if (capabilities != null) { + node = null; + hash = capabilities.getHash(); + } else if (legacyCapabilities != null) { + node = legacyCapabilities.getNode(); + hash = legacyCapabilities.getHash(); + } else { + return null; + } + return hash == null ? null : new NodeHash(node, hash); + } + + class NodeHash { + public final String node; + public final im.conversations.android.xmpp.EntityCapabilities.Hash hash; + + private NodeHash( + @Nullable String node, + @NonNull final im.conversations.android.xmpp.EntityCapabilities.Hash hash) { + this.node = node; + this.hash = hash; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java b/src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java new file mode 100644 index 0000000000000000000000000000000000000000..797d627cf10ce40f3c7c54841092d4ae012e65c1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java @@ -0,0 +1,45 @@ +package im.conversations.android.xmpp.model.capabilties; + +import com.google.common.base.Strings; +import com.google.common.io.BaseEncoding; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.EntityCapabilities; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "c", namespace = Namespace.ENTITY_CAPABILITIES) +public class LegacyCapabilities extends Extension { + + private static final String HASH_ALGORITHM = "sha-1"; + + public LegacyCapabilities() { + super(LegacyCapabilities.class); + } + + public String getNode() { + return this.getAttribute("node"); + } + + public EntityCapabilities.EntityCapsHash getHash() { + final String hash = getAttribute("hash"); + final String ver = getAttribute("ver"); + if (Strings.isNullOrEmpty(ver) || Strings.isNullOrEmpty(hash)) { + return null; + } + if (HASH_ALGORITHM.equals(hash) && BaseEncoding.base64().canDecode(ver)) { + return EntityCapabilities.EntityCapsHash.of(ver); + } else { + return null; + } + } + + public void setNode(final String node) { + this.setAttribute("node", node); + } + + public void setHash(final EntityCapabilities.EntityCapsHash hash) { + this.setAttribute("hash", HASH_ALGORITHM); + this.setAttribute("ver", hash.encoded()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java b/src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java new file mode 100644 index 0000000000000000000000000000000000000000..38b740e8c0c04ac1b59ce4edf5e906bba9e0ed3d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.carbons; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Enable extends Extension { + + public Enable() { + super(Enable.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java b/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java new file mode 100644 index 0000000000000000000000000000000000000000..507869a60c5967760f42c916d5d5544a29b5ff71 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.carbons; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.forward.Forwarded; + +@XmlElement +public class Received extends Extension { + + public Received() { + super(Received.class); + } + + public Forwarded getForwarded() { + return this.getExtension(Forwarded.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java b/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java new file mode 100644 index 0000000000000000000000000000000000000000..0201c53c636cf8e70a88de457c7a354ce435cc10 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.carbons; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.forward.Forwarded; + +@XmlElement +public class Sent extends Extension { + + public Sent() { + super(Sent.class); + } + + public Forwarded getForwarded() { + return this.getExtension(Forwarded.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java b/src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..f4c76376a384778c84484ee269d9e0135f69ce18 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.CARBONS) +package im.conversations.android.xmpp.model.carbons; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/correction/Replace.java b/src/main/java/im/conversations/android/xmpp/model/correction/Replace.java new file mode 100644 index 0000000000000000000000000000000000000000..dbd7395572c7b614f9c7caf83996fbe94e8aca55 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/correction/Replace.java @@ -0,0 +1,24 @@ +package im.conversations.android.xmpp.model.correction; + +import androidx.annotation.NonNull; +import com.google.common.base.Strings; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.LAST_MESSAGE_CORRECTION) +public class Replace extends Extension { + + public Replace() { + super(Replace.class); + } + + public String getId() { + return Strings.emptyToNull(this.getAttribute("id")); + } + + public void setId(@NonNull final String id) { + this.setAttribute("id", id); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/Active.java b/src/main/java/im/conversations/android/xmpp/model/csi/Active.java new file mode 100644 index 0000000000000000000000000000000000000000..21fb65bb4a7d22e05c6ac8f6ba8cc15dd75f9551 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/csi/Active.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.csi; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Active extends StreamElement { + + public Active() { + super(Active.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/ClientStateIndication.java b/src/main/java/im/conversations/android/xmpp/model/csi/ClientStateIndication.java new file mode 100644 index 0000000000000000000000000000000000000000..60bd59edb145ce99cee42e4d8aa0e4f67e759307 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/csi/ClientStateIndication.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.csi; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamFeature; + +@XmlElement(name = "csi") +public class ClientStateIndication extends StreamFeature { + + public ClientStateIndication() { + super(ClientStateIndication.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java b/src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java new file mode 100644 index 0000000000000000000000000000000000000000..7c36b593d544f29a7c2859a3ebd6ccee39c3a05b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.csi; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Inactive extends StreamElement { + + public Inactive() { + super(Inactive.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/package-info.java b/src/main/java/im/conversations/android/xmpp/model/csi/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..58d26b1f13186513a6338807cca6eacb2102c11f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/csi/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.CSI) +package im.conversations.android.xmpp.model.csi; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Data.java b/src/main/java/im/conversations/android/xmpp/model/data/Data.java new file mode 100644 index 0000000000000000000000000000000000000000..c754ee48de9364084628b26fc596d2645741ead3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/Data.java @@ -0,0 +1,110 @@ +package im.conversations.android.xmpp.model.data; + +import com.google.common.collect.Collections2; +import com.google.common.collect.Iterables; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import java.util.Map; + +@XmlElement(name = "x") +public class Data extends Extension { + + private static final String FORM_TYPE = "FORM_TYPE"; + private static final String FIELD_TYPE_HIDDEN = "hidden"; + private static final String FORM_TYPE_SUBMIT = "submit"; + + public Data() { + super(Data.class); + } + + public String getFormType() { + final var fields = this.getExtensions(Field.class); + final var formTypeField = Iterables.find(fields, f -> FORM_TYPE.equals(f.getFieldName())); + return Iterables.getFirst(formTypeField.getValues(), null); + } + + public Collection getFields() { + return Collections2.filter( + this.getExtensions(Field.class), f -> !FORM_TYPE.equals(f.getFieldName())); + } + + private void addField(final String name, final Object value) { + addField(name, value, null); + } + + private void addField(final String name, final Object value, final String type) { + if (value == null) { + throw new IllegalArgumentException("Null values are not supported on data fields"); + } + final var field = this.addExtension(new Field()); + field.setFieldName(name); + if (type != null) { + field.setType(type); + } + if (value instanceof Collection) { + for (final Object subValue : (Collection) value) { + if (subValue instanceof String) { + final var valueExtension = field.addExtension(new Value()); + valueExtension.setContent((String) subValue); + } else { + throw new IllegalArgumentException( + String.format( + "%s is not a supported field value", + subValue.getClass().getSimpleName())); + } + } + } else { + final var valueExtension = field.addExtension(new Value()); + if (value instanceof String) { + valueExtension.setContent((String) value); + } else if (value instanceof Integer) { + valueExtension.setContent(String.valueOf(value)); + } else if (value instanceof Boolean) { + valueExtension.setContent(Boolean.TRUE.equals(value) ? "1" : "0"); + } else { + throw new IllegalArgumentException( + String.format( + "%s is not a supported field value", + value.getClass().getSimpleName())); + } + } + } + + private void setFormType(final String formType) { + this.addField(FORM_TYPE, formType, FIELD_TYPE_HIDDEN); + } + + public static Data of(final String formType, final Map values) { + final var data = new Data(); + data.setType(FORM_TYPE_SUBMIT); + data.setFormType(formType); + for (final Map.Entry entry : values.entrySet()) { + data.addField(entry.getKey(), entry.getValue()); + } + return data; + } + + public Data submit(final Map values) { + final String formType = this.getFormType(); + final var submit = new Data(); + submit.setType(FORM_TYPE_SUBMIT); + if (formType != null) { + submit.setFormType(formType); + } + for (final Field existingField : this.getFields()) { + final var fieldName = existingField.getFieldName(); + final Object submittedValue = values.get(fieldName); + if (submittedValue != null) { + submit.addField(fieldName, submittedValue); + } else { + submit.addField(fieldName, existingField.getValues()); + } + } + return submit; + } + + private void setType(final String type) { + this.setAttribute("type", type); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Field.java b/src/main/java/im/conversations/android/xmpp/model/data/Field.java new file mode 100644 index 0000000000000000000000000000000000000000..f3f72fab86e7924e107674d02de4707030ebbf13 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/Field.java @@ -0,0 +1,29 @@ +package im.conversations.android.xmpp.model.data; +import eu.siacs.conversations.xml.Element; +import com.google.common.collect.Collections2; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; + +@XmlElement +public class Field extends Extension { + public Field() { + super(Field.class); + } + + public String getFieldName() { + return getAttribute("var"); + } + + public Collection getValues() { + return Collections2.transform(getExtensions(Value.class), Element::getContent); + } + + public void setFieldName(String name) { + this.setAttribute("var", name); + } + + public void setType(String type) { + this.setAttribute("type", type); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Option.java b/src/main/java/im/conversations/android/xmpp/model/data/Option.java new file mode 100644 index 0000000000000000000000000000000000000000..b9c3e9aae4f1d7285dcfa07ed4f5a20f984f32d4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/Option.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.data; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Option extends Extension { + + public Option() { + super(Option.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Value.java b/src/main/java/im/conversations/android/xmpp/model/data/Value.java new file mode 100644 index 0000000000000000000000000000000000000000..8e9eccc4d715f6151e7cff6c9b9065f4cb3c6adc --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/Value.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.data; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Value extends Extension { + + public Value() { + super(Value.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/data/package-info.java b/src/main/java/im/conversations/android/xmpp/model/data/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..fcc0e1f790ba4f9a77e79115244ff4528f507abd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.DATA) +package im.conversations.android.xmpp.model.data; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/delay/Delay.java b/src/main/java/im/conversations/android/xmpp/model/delay/Delay.java new file mode 100644 index 0000000000000000000000000000000000000000..b294f83d456fb0fa17779f187eb7838200e34cce --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/delay/Delay.java @@ -0,0 +1,30 @@ +package im.conversations.android.xmpp.model.delay; + +import com.google.common.base.Strings; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.Timestamps; +import im.conversations.android.xmpp.model.Extension; +import java.text.ParseException; +import java.time.Instant; + +@XmlElement(namespace = Namespace.DELAY) +public class Delay extends Extension { + + public Delay() { + super(Delay.class); + } + + public Instant getStamp() { + final var stamp = this.getAttribute("stamp"); + if (Strings.isNullOrEmpty(stamp)) { + return null; + } + try { + return Instant.ofEpochMilli(Timestamps.parse(stamp)); + } catch (final IllegalArgumentException | ParseException e) { + return null; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/external/Service.java b/src/main/java/im/conversations/android/xmpp/model/disco/external/Service.java new file mode 100644 index 0000000000000000000000000000000000000000..86a93af0dcfd00eb77688ca5f3da5074a190b27c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/external/Service.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.disco.external; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Service extends Extension { + + public Service() { + super(Service.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/external/Services.java b/src/main/java/im/conversations/android/xmpp/model/disco/external/Services.java new file mode 100644 index 0000000000000000000000000000000000000000..36338083da5d03530ee42027f618225e8c248632 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/external/Services.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.disco.external; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Services extends Extension { + + public Services() { + super(Services.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/external/package-info.java b/src/main/java/im/conversations/android/xmpp/model/disco/external/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..868a5a17531c7b5fedb1764f24afcc15fcb3cb82 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/external/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.EXTERNAL_SERVICE_DISCOVERY) +package im.conversations.android.xmpp.model.disco.external; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java b/src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java new file mode 100644 index 0000000000000000000000000000000000000000..dd288918cba6541a123dea1544edee5aa8689591 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java @@ -0,0 +1,19 @@ +package im.conversations.android.xmpp.model.disco.info; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Feature extends Extension { + public Feature() { + super(Feature.class); + } + + public String getVar() { + return this.getAttribute("var"); + } + + public void setVar(final String feature) { + this.setAttribute("var", feature); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java b/src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java new file mode 100644 index 0000000000000000000000000000000000000000..6da0a4aa22f1770095dbf0bae98de2a6344a453b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java @@ -0,0 +1,39 @@ +package im.conversations.android.xmpp.model.disco.info; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Identity extends Extension { + public Identity() { + super(Identity.class); + } + + public String getCategory() { + return this.getAttribute("category"); + } + + public String getType() { + return this.getAttribute("type"); + } + + public String getLang() { + return this.getAttribute("xml:lang"); + } + + public String getIdentityName() { + return this.getAttribute("name"); + } + + public void setIdentityName(final String name) { + this.setAttribute("name", name); + } + + public void setType(final String type) { + this.setAttribute("type", type); + } + + public void setCategory(final String category) { + this.setAttribute("category", category); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java b/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..55f104e25bb547419e1e74ffa077ec563a354ea3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java @@ -0,0 +1,38 @@ +package im.conversations.android.xmpp.model.disco.info; + +import com.google.common.collect.Iterables; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; + +@XmlElement(name = "query") +public class InfoQuery extends Extension { + + public InfoQuery() { + super(InfoQuery.class); + } + + public void setNode(final String node) { + this.setAttribute("node", node); + } + + public String getNode() { + return this.getAttribute("node"); + } + + public Collection getFeatures() { + return this.getExtensions(Feature.class); + } + + public boolean hasFeature(final String feature) { + return Iterables.any(getFeatures(), f -> feature.equals(f.getVar())); + } + + public Collection getIdentities() { + return this.getExtensions(Identity.class); + } + + public boolean hasIdentityWithCategory(final String category) { + return Iterables.any(getIdentities(), i -> category.equals(i.getCategory())); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java b/src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..60eb24a5983d156b91500f99418ae0b1788e7e02 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.DISCO_INFO) +package im.conversations.android.xmpp.model.disco.info; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java b/src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java new file mode 100644 index 0000000000000000000000000000000000000000..f5bf2b98400d94fcbcc11c815638d15f645170bb --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java @@ -0,0 +1,22 @@ +package im.conversations.android.xmpp.model.disco.items; + +import androidx.annotation.Nullable; + +import eu.siacs.conversations.xmpp.Jid; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Item extends Extension { + public Item() { + super(Item.class); + } + + public Jid getJid() { + return getAttributeAsJid("jid"); + } + + public @Nullable String getNode() { + return this.getAttribute("node"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java b/src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..981132ed6905fc901ca3c5f208d2b6af993de4cd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java @@ -0,0 +1,19 @@ +package im.conversations.android.xmpp.model.disco.items; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "query") +public class ItemsQuery extends Extension { + public ItemsQuery() { + super(ItemsQuery.class); + } + + public void setNode(final String node) { + this.setAttribute("node", node); + } + + public String getNode() { + return this.getAttribute("node"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java b/src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..a170e5cee398c171c36b5fa396362aa836ad8dcb --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.DISCO_ITEMS) +package im.conversations.android.xmpp.model.disco.items; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/error/Condition.java b/src/main/java/im/conversations/android/xmpp/model/error/Condition.java new file mode 100644 index 0000000000000000000000000000000000000000..bd68c2c433cad9bb56a6b70a3c74629cf347f6d0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/error/Condition.java @@ -0,0 +1,188 @@ +package im.conversations.android.xmpp.model.error; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +public abstract class Condition extends Extension { + + private Condition(Class clazz) { + super(clazz); + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class BadRequest extends Condition { + + public BadRequest() { + super(BadRequest.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class Conflict extends Condition { + + public Conflict() { + super(Conflict.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class FeatureNotImplemented extends Condition { + + public FeatureNotImplemented() { + super(FeatureNotImplemented.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class Forbidden extends Condition { + + public Forbidden() { + super(Forbidden.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class Gone extends Condition { + + public Gone() { + super(Gone.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class InternalServerError extends Condition { + + public InternalServerError() { + super(InternalServerError.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class ItemNotFound extends Condition { + + public ItemNotFound() { + super(ItemNotFound.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class JidMalformed extends Condition { + + public JidMalformed() { + super(JidMalformed.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class NotAcceptable extends Condition { + + public NotAcceptable() { + super(NotAcceptable.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class NotAllowed extends Condition { + + public NotAllowed() { + super(NotAllowed.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class NotAuthorized extends Condition { + + public NotAuthorized() { + super(NotAuthorized.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class PaymentRequired extends Condition { + + public PaymentRequired() { + super(PaymentRequired.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class RecipientUnavailable extends Condition { + + public RecipientUnavailable() { + super(RecipientUnavailable.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class Redirect extends Condition { + + public Redirect() { + super(Redirect.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class RegistrationRequired extends Condition { + + public RegistrationRequired() { + super(RegistrationRequired.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class RemoteServerNotFound extends Condition { + + public RemoteServerNotFound() { + super(RemoteServerNotFound.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class RemoteServerTimeout extends Condition { + + public RemoteServerTimeout() { + super(RemoteServerTimeout.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class ResourceConstraint extends Condition { + + public ResourceConstraint() { + super(ResourceConstraint.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class ServiceUnavailable extends Condition { + + public ServiceUnavailable() { + super(ServiceUnavailable.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class SubscriptionRequired extends Condition { + + public SubscriptionRequired() { + super(SubscriptionRequired.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class UndefinedCondition extends Condition { + + public UndefinedCondition() { + super(UndefinedCondition.class); + } + } + + @XmlElement(namespace = Namespace.STANZAS) + public static class UnexpectedRequest extends Condition { + + public UnexpectedRequest() { + super(UnexpectedRequest.class); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/error/Error.java b/src/main/java/im/conversations/android/xmpp/model/error/Error.java new file mode 100644 index 0000000000000000000000000000000000000000..0a07e73f9d078a9412fedae7c34438567ba077d8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/error/Error.java @@ -0,0 +1,55 @@ +package im.conversations.android.xmpp.model.error; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Locale; +import eu.siacs.conversations.xml.Namespace; + +@XmlElement(namespace = Namespace.JABBER_CLIENT) +public class Error extends Extension { + + public Error() { + super(Error.class); + } + + public Condition getCondition() { + return this.getExtension(Condition.class); + } + + public void setCondition(final Condition condition) { + this.addExtension(condition); + } + + public Text getText() { + return this.getExtension(Text.class); + } + + public String getTextAsString() { + final var text = getText(); + return text == null ? null : text.getContent(); + } + + public void setType(final Type type) { + this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT)); + } + + public void addExtensions(final Extension[] extensions) { + for (final Extension extension : extensions) { + this.addExtension(extension); + } + } + + public enum Type { + MODIFY, + CANCEL, + AUTH, + WAIT + } + + public static class Extension extends im.conversations.android.xmpp.model.Extension { + + public Extension(Class clazz) { + super(clazz); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/error/Text.java b/src/main/java/im/conversations/android/xmpp/model/error/Text.java new file mode 100644 index 0000000000000000000000000000000000000000..478b1f5cd531ea54ff7402c7a5e9c7ec4c23bec7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/error/Text.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.error; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.STANZAS) +public class Text extends Extension { + + public Text() { + super(Text.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/Fast.java b/src/main/java/im/conversations/android/xmpp/model/fast/Fast.java new file mode 100644 index 0000000000000000000000000000000000000000..1291d8ea06a5c566c8672c0d71d7e1e6f1d7490f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/Fast.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.fast; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Fast extends Extension { + public Fast() { + super(Fast.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/Mechanism.java b/src/main/java/im/conversations/android/xmpp/model/fast/Mechanism.java new file mode 100644 index 0000000000000000000000000000000000000000..240f5de0e13d9b9dbd4cf08c7369469600ab8b4a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/Mechanism.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.fast; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Mechanism extends Extension { + public Mechanism() { + super(Mechanism.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/RequestToken.java b/src/main/java/im/conversations/android/xmpp/model/fast/RequestToken.java new file mode 100644 index 0000000000000000000000000000000000000000..4ac5a9205f4189d246d95b00aa82d99188e5e148 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/RequestToken.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.fast; + +import eu.siacs.conversations.crypto.sasl.HashedToken; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class RequestToken extends Extension { + public RequestToken() { + super(RequestToken.class); + } + + public RequestToken(final HashedToken.Mechanism mechanism) { + this(); + this.setAttribute("mechanism", mechanism.name()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/Token.java b/src/main/java/im/conversations/android/xmpp/model/fast/Token.java new file mode 100644 index 0000000000000000000000000000000000000000..258cd9abadf650fa97e15ab4d45e1e5aa990d69d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/Token.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.fast; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Token extends Extension { + + public Token() { + super(Token.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/package-info.java b/src/main/java/im/conversations/android/xmpp/model/fast/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..effc9e511cb824aff5454a29e3a300f4e6121e11 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.FAST) +package im.conversations.android.xmpp.model.fast; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java b/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java new file mode 100644 index 0000000000000000000000000000000000000000..80a646a41df7901ab9fbf18842f37f19757972c0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java @@ -0,0 +1,18 @@ +package im.conversations.android.xmpp.model.forward; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.stanza.Message; + +@XmlElement(namespace = Namespace.FORWARD) +public class Forwarded extends Extension { + + public Forwarded() { + super(Forwarded.class); + } + + public Message getMessage() { + return this.getExtension(Message.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/hints/Store.java b/src/main/java/im/conversations/android/xmpp/model/hints/Store.java new file mode 100644 index 0000000000000000000000000000000000000000..fe82612adcc61412c55a664610cac99372e2188d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/hints/Store.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.hints; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Store extends Extension { + + public Store() { + super(Store.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/hints/package-info.java b/src/main/java/im/conversations/android/xmpp/model/hints/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..76c25d6551e88a2647b9afa5dba86676743fa930 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/hints/package-info.java @@ -0,0 +1,6 @@ +@XmlPackage(namespace = Namespace.HINTS) +package im.conversations.android.xmpp.model.hints; + +import eu.siacs.conversations.xml.Namespace; + +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Body.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Body.java new file mode 100644 index 0000000000000000000000000000000000000000..5857f058518768c5ac26016552de2cad6b291baf --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Body.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Body extends Extension { + + public Body() { + super(Body.class); + } + + public Body(final String content) { + this(); + setContent(content); + } + + public String getLang() { + return this.getAttribute("xml:lang"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Priority.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Priority.java new file mode 100644 index 0000000000000000000000000000000000000000..7c5b3bdc98fdedf964b766ada9f7f47645e936fd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Priority.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Priority extends Extension { + + public Priority() { + super(Priority.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Show.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Show.java new file mode 100644 index 0000000000000000000000000000000000000000..44dc512be24e2aa4c32de44cfe4aa0f82eb25f8a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Show.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Show extends Extension { + public Show() { + super(Show.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Status.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Status.java new file mode 100644 index 0000000000000000000000000000000000000000..3175230d7191576d7c836350a4ca75cfe5273467 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Status.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Status extends Extension { + + + public Status() { + super(Status.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Subject.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Subject.java new file mode 100644 index 0000000000000000000000000000000000000000..4ae3b8ed519a234698cc3ec30f12d4f1a25d44f4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Subject.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Subject extends Extension { + + public Subject() { + super(Subject.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java new file mode 100644 index 0000000000000000000000000000000000000000..703429ef0ba9fb9d3122af6f222f619cf4bb9705 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Thread extends Extension { + + public Thread() { + super(Thread.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java b/src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..448804d7c8c6d93849f8c151009486937c74c3d8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.JABBER_CLIENT) +package im.conversations.android.xmpp.model.jabber; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java b/src/main/java/im/conversations/android/xmpp/model/jingle/Jingle.java similarity index 62% rename from src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java rename to src/main/java/im/conversations/android/xmpp/model/jingle/Jingle.java index a24040d3dbe82efcbfc558e0ca56c006cf4d82bd..aeb79ffd2f328e340348a472f86289004cd90f72 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java +++ b/src/main/java/im/conversations/android/xmpp/model/jingle/Jingle.java @@ -1,4 +1,4 @@ -package eu.siacs.conversations.xmpp.jingle.stanzas; +package im.conversations.android.xmpp.model.jingle; import androidx.annotation.NonNull; @@ -10,66 +10,38 @@ import com.google.common.collect.ImmutableMap; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import eu.siacs.conversations.xmpp.jingle.stanzas.Content; +import eu.siacs.conversations.xmpp.jingle.stanzas.Group; +import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; -import java.util.Map; - -public class JinglePacket extends IqPacket { - - private JinglePacket() { - super(); - } +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; - public JinglePacket(final Action action, final String sessionId) { - super(TYPE.SET); - final Element jingle = addChild("jingle", Namespace.JINGLE); - jingle.setAttribute("sid", sessionId); - jingle.setAttribute("action", action.toString()); - } +import java.util.Map; - public static JinglePacket upgrade(final IqPacket iqPacket) { - Preconditions.checkArgument(iqPacket.hasChild("jingle", Namespace.JINGLE)); - Preconditions.checkArgument(iqPacket.getType() == TYPE.SET); - final JinglePacket jinglePacket = new JinglePacket(); - jinglePacket.setAttributes(iqPacket.getAttributes()); - jinglePacket.setChildren(iqPacket.getChildren()); - return jinglePacket; - } +@XmlElement +public class Jingle extends Extension { - // TODO deprecate this somehow and make file transfer fail if there are multiple (or something) - public Content getJingleContent() { - final Element content = getJingleChild("content"); - return content == null ? null : Content.upgrade(content); + public Jingle() { + super(Jingle.class); } - public Group getGroup() { - final Element jingle = findChild("jingle", Namespace.JINGLE); - final Element group = jingle.findChild("group", Namespace.JINGLE_APPS_GROUPING); - return group == null ? null : Group.upgrade(group); + public Jingle(final Action action, final String sessionId) { + this(); + this.setAttribute("sid", sessionId); + this.setAttribute("action", action.toString()); } - public void addGroup(final Group group) { - this.addJingleChild(group); - } - - public Map getJingleContents() { - final Element jingle = findChild("jingle", Namespace.JINGLE); - ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); - for (final Element child : jingle.getChildren()) { - if ("content".equals(child.getName())) { - final Content content = Content.upgrade(child); - builder.put(content.getContentName(), content); - } - } - return builder.build(); + public String getSessionId() { + return this.getAttribute("sid"); } - public void addJingleContent(final Content content) { // take content interface - addJingleChild(content); + public Action getAction() { + return Action.of(this.getAttribute("action")); } public ReasonWrapper getReason() { - final Element reasonElement = getJingleChild("reason"); + final Element reasonElement = this.findChild("reason"); if (reasonElement == null) { return new ReasonWrapper(Reason.UNKNOWN, null); } @@ -86,8 +58,7 @@ public class JinglePacket extends IqPacket { } public void setReason(final Reason reason, final String text) { - final Element jingle = findChild("jingle", Namespace.JINGLE); - final Element reasonElement = jingle.addChild("reason"); + final Element reasonElement = this.addChild("reason"); reasonElement.addChild(reason.toString()); if (!Strings.isNullOrEmpty(text)) { reasonElement.addChild("text").setContent(text); @@ -97,31 +68,44 @@ public class JinglePacket extends IqPacket { // RECOMMENDED for session-initiate, NOT RECOMMENDED otherwise public void setInitiator(final Jid initiator) { Preconditions.checkArgument(initiator.isFullJid(), "initiator should be a full JID"); - findChild("jingle", Namespace.JINGLE).setAttribute("initiator", initiator); + this.setAttribute("initiator", initiator); } // RECOMMENDED for session-accept, NOT RECOMMENDED otherwise - public void setResponder(Jid responder) { + public void setResponder(final Jid responder) { Preconditions.checkArgument(responder.isFullJid(), "responder should be a full JID"); - findChild("jingle", Namespace.JINGLE).setAttribute("responder", responder); + this.setAttribute("responder", responder); } - public Element getJingleChild(final String name) { - final Element jingle = findChild("jingle", Namespace.JINGLE); - return jingle == null ? null : jingle.findChild(name); + public Group getGroup() { + final Element group = this.findChild("group", Namespace.JINGLE_APPS_GROUPING); + return group == null ? null : Group.upgrade(group); } - public void addJingleChild(final Element child) { - final Element jingle = findChild("jingle", Namespace.JINGLE); - jingle.addChild(child); + public void addGroup(final Group group) { + this.addChild(group); } - public String getSessionId() { - return findChild("jingle", Namespace.JINGLE).getAttribute("sid"); + // TODO deprecate this somehow and make file transfer fail if there are multiple (or something) + public Content getJingleContent() { + final Element content = this.findChild("content"); + return content == null ? null : Content.upgrade(content); } - public Action getAction() { - return Action.of(findChild("jingle", Namespace.JINGLE).getAttribute("action")); + public void addJingleContent(final Content content) { // take content interface + this.addChild(content); + } + + + public Map getJingleContents() { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + for (final Element child : this.getChildren()) { + if ("content".equals(child.getName())) { + final Content content = Content.upgrade(child); + builder.put(content.getContentName(), content); + } + } + return builder.build(); } public enum Action { diff --git a/src/main/java/im/conversations/android/xmpp/model/jingle/error/JingleCondition.java b/src/main/java/im/conversations/android/xmpp/model/jingle/error/JingleCondition.java new file mode 100644 index 0000000000000000000000000000000000000000..a0c6dfbbd76a9b48d975ea8a63fdac2c39957858 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jingle/error/JingleCondition.java @@ -0,0 +1,44 @@ +package im.conversations.android.xmpp.model.jingle.error; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.error.Error; + +public abstract class JingleCondition extends Error.Extension { + + private JingleCondition(Class clazz) { + super(clazz); + } + + @XmlElement(namespace = Namespace.JINGLE_ERRORS) + public static class OutOfOrder extends JingleCondition { + + public OutOfOrder() { + super(OutOfOrder.class); + } + } + + @XmlElement(namespace = Namespace.JINGLE_ERRORS) + public static class TieBreak extends JingleCondition { + + public TieBreak() { + super(TieBreak.class); + } + } + + @XmlElement(namespace = Namespace.JINGLE_ERRORS) + public static class UnknownSession extends JingleCondition { + + public UnknownSession() { + super(UnknownSession.class); + } + } + + @XmlElement(namespace = Namespace.JINGLE_ERRORS) + public static class UnsupportedInfo extends JingleCondition { + + public UnsupportedInfo() { + super(UnsupportedInfo.class); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jingle/package-info.java b/src/main/java/im/conversations/android/xmpp/model/jingle/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..6af2511e8da94ec172d8f1276422988db9812426 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jingle/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.JINGLE) +package im.conversations.android.xmpp.model.jingle; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Accept.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Accept.java new file mode 100644 index 0000000000000000000000000000000000000000..20ae15aee379933b996ec257fcc49acdc3c0a3f5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Accept.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.jmi; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Accept extends JingleMessage { + + public Accept() { + super(Accept.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/JingleMessage.java b/src/main/java/im/conversations/android/xmpp/model/jmi/JingleMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..0045844f0a47231e302d51db057c862a5a994a3c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/JingleMessage.java @@ -0,0 +1,14 @@ +package im.conversations.android.xmpp.model.jmi; + +import im.conversations.android.xmpp.model.Extension; + +public abstract class JingleMessage extends Extension { + + public JingleMessage(Class clazz) { + super(clazz); + } + + public String getSessionId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Proceed.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Proceed.java new file mode 100644 index 0000000000000000000000000000000000000000..b6be44ee091b85165958c182ebef230dd6c1bbe8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Proceed.java @@ -0,0 +1,24 @@ +package im.conversations.android.xmpp.model.jmi; + +import com.google.common.primitives.Ints; + +import eu.siacs.conversations.xml.Element; +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Proceed extends JingleMessage { + + public Proceed() { + super(Proceed.class); + } + + public Integer getDeviceId() { + // TODO use proper namespace and create extension + final Element device = this.findChild("device"); + final String id = device == null ? null : device.getAttribute("id"); + if (id == null) { + return null; + } + return Ints.tryParse(id); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Propose.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Propose.java new file mode 100644 index 0000000000000000000000000000000000000000..d5a48a4033f8bcadda046185e7266655ac4f52df --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Propose.java @@ -0,0 +1,38 @@ +package im.conversations.android.xmpp.model.jmi; + +import com.google.common.collect.ImmutableList; + +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription; +import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription; +import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; +import im.conversations.android.annotation.XmlElement; + +import java.util.List; + +@XmlElement +public class Propose extends JingleMessage { + + public Propose() { + super(Propose.class); + } + + public List getDescriptions() { + final ImmutableList.Builder builder = new ImmutableList.Builder<>(); + // TODO create proper extension for description + for (final Element child : this.children) { + if ("description".equals(child.getName())) { + final String namespace = child.getNamespace(); + if (Namespace.JINGLE_APPS_FILE_TRANSFER.contains(namespace)) { + builder.add(FileTransferDescription.upgrade(child)); + } else if (Namespace.JINGLE_APPS_RTP.equals(namespace)) { + builder.add(RtpDescription.upgrade(child)); + } else { + builder.add(GenericDescription.upgrade(child)); + } + } + } + return builder.build(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Reject.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Reject.java new file mode 100644 index 0000000000000000000000000000000000000000..e71206fd619b09378c35ba0fd450e227a15545eb --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Reject.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.jmi; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Reject extends JingleMessage { + + public Reject() { + super(Reject.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Retract.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Retract.java new file mode 100644 index 0000000000000000000000000000000000000000..7c507156d85d9e7653d9c61b0261187f92ef7beb --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Retract.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.jmi; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Retract extends JingleMessage { + + public Retract() { + super(Retract.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/package-info.java b/src/main/java/im/conversations/android/xmpp/model/jmi/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..9ce640b1f9619957a7127e4e6ce325f3d2c3c65a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.JINGLE_MESSAGE) +package im.conversations.android.xmpp.model.jmi; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/End.java b/src/main/java/im/conversations/android/xmpp/model/mam/End.java new file mode 100644 index 0000000000000000000000000000000000000000..757ed60c69995b98183bee3ccb22324214baa533 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/End.java @@ -0,0 +1,15 @@ +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class End extends Extension { + public End() { + super(End.class); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Fin.java b/src/main/java/im/conversations/android/xmpp/model/mam/Fin.java new file mode 100644 index 0000000000000000000000000000000000000000..534072647563b9f55b1e9df190a3de8d309fc8af --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Fin.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Fin extends Extension { + + public Fin() { + super(Fin.class); + } + + public boolean isComplete() { + return this.getAttributeAsBoolean("complete"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Metadata.java b/src/main/java/im/conversations/android/xmpp/model/mam/Metadata.java new file mode 100644 index 0000000000000000000000000000000000000000..9f05e08fc21fc33bc708294497b09a0549697395 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Metadata.java @@ -0,0 +1,20 @@ +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Metadata extends Extension { + + public Metadata() { + super(Metadata.class); + } + + public Start getStart() { + return this.getExtension(Start.class); + } + + public End getEnd() { + return this.getExtension(End.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Query.java b/src/main/java/im/conversations/android/xmpp/model/mam/Query.java new file mode 100644 index 0000000000000000000000000000000000000000..d8f701d91de17b8e3f00da00bcb9dcfb8019fee7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Query.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Query extends Extension { + + public Query() { + super(Query.class); + } + + public void setQueryId(final String id) { + this.setAttribute("queryid", id); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Result.java b/src/main/java/im/conversations/android/xmpp/model/mam/Result.java new file mode 100644 index 0000000000000000000000000000000000000000..253499756ba26bf1387fd4191fa126d4f1ec53b9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Result.java @@ -0,0 +1,25 @@ +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.forward.Forwarded; + +@XmlElement +public class Result extends Extension { + + public Result() { + super(Result.class); + } + + public Forwarded getForwarded() { + return this.getExtension(Forwarded.class); + } + + public String getId() { + return this.getAttribute("id"); + } + + public String getQueryId() { + return this.getAttribute("queryid"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Start.java b/src/main/java/im/conversations/android/xmpp/model/mam/Start.java new file mode 100644 index 0000000000000000000000000000000000000000..9ff84b2564062b368ac48e093f1d2ac7a8b77884 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Start.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Start extends Extension { + + public Start() { + super(Start.class); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/package-info.java b/src/main/java/im/conversations/android/xmpp/model/mam/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..1aa4982e6af857936aa753c81aa602de71b57050 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.MESSAGE_ARCHIVE_MANAGEMENT) +package im.conversations.android.xmpp.model.mam; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java b/src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java new file mode 100644 index 0000000000000000000000000000000000000000..be31df35d6b0259c9391d96f45c6ad8cadc9a81d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.markers; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Displayed extends Extension { + + public Displayed() { + super(Displayed.class); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/Markable.java b/src/main/java/im/conversations/android/xmpp/model/markers/Markable.java new file mode 100644 index 0000000000000000000000000000000000000000..08161af709d9747f6c0dc58ece892232dd39e281 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/markers/Markable.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.markers; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.DeliveryReceiptRequest; + +@XmlElement +public class Markable extends DeliveryReceiptRequest { + + public Markable() { + super(Markable.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/Received.java b/src/main/java/im/conversations/android/xmpp/model/markers/Received.java new file mode 100644 index 0000000000000000000000000000000000000000..7007cd176271e4a46ab335e4b7c4e70409caa24e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/markers/Received.java @@ -0,0 +1,20 @@ +package im.conversations.android.xmpp.model.markers; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.DeliveryReceipt; + +@XmlElement +public class Received extends DeliveryReceipt { + + public Received() { + super(Received.class); + } + + public void setId(String id) { + this.setAttribute("id", id); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/package-info.java b/src/main/java/im/conversations/android/xmpp/model/markers/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..950963d4f00924052c6b4007cf343b7f52da65df --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/markers/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.CHAT_MARKERS) +package im.conversations.android.xmpp.model.markers; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/mds/Displayed.java b/src/main/java/im/conversations/android/xmpp/model/mds/Displayed.java new file mode 100644 index 0000000000000000000000000000000000000000..9f5275371c3564ee52d1825bc67fda4327d36514 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mds/Displayed.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.mds; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.MDS_DISPLAYED) +public class Displayed extends Extension { + public Displayed() { + super(Displayed.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java b/src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java new file mode 100644 index 0000000000000000000000000000000000000000..6502a16e72a41aea353c8d0467d38c6d22455c2a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java @@ -0,0 +1,9 @@ +package im.conversations.android.xmpp.model.muc; + +public enum Affiliation { + OWNER, + ADMIN, + MEMBER, + OUTCAST, + NONE; +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/History.java b/src/main/java/im/conversations/android/xmpp/model/muc/History.java new file mode 100644 index 0000000000000000000000000000000000000000..e09210e60277c22491ae60940543a77d4e3ab523 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/History.java @@ -0,0 +1,20 @@ +package im.conversations.android.xmpp.model.muc; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class History extends Extension { + + public History() { + super(History.class); + } + + public void setMaxChars(final int maxChars) { + this.setAttribute("maxchars", maxChars); + } + + public void setMaxStanzas(final int maxStanzas) { + this.setAttribute("maxstanzas", maxStanzas); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/MultiUserChat.java b/src/main/java/im/conversations/android/xmpp/model/muc/MultiUserChat.java new file mode 100644 index 0000000000000000000000000000000000000000..33da7b9af38b4083739aba6f0dd7a004d522b8cf --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/MultiUserChat.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.muc; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "x") +public class MultiUserChat extends Extension { + + public MultiUserChat() { + super(MultiUserChat.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/Role.java b/src/main/java/im/conversations/android/xmpp/model/muc/Role.java new file mode 100644 index 0000000000000000000000000000000000000000..9e9d3d165666b4f310b809adfb2ee1c764623a39 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/Role.java @@ -0,0 +1,8 @@ +package im.conversations.android.xmpp.model.muc; + +public enum Role { + MODERATOR, + VISITOR, + PARTICIPANT, + NONE +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/package-info.java b/src/main/java/im/conversations/android/xmpp/model/muc/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..41d652f204e92fd1e77bd5ff447efa959c4af9f2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.MUC) +package im.conversations.android.xmpp.model.muc; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/Item.java b/src/main/java/im/conversations/android/xmpp/model/muc/user/Item.java new file mode 100644 index 0000000000000000000000000000000000000000..7ff712aeaba976c687928c61c8255b4fb19967b1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/user/Item.java @@ -0,0 +1,58 @@ +package im.conversations.android.xmpp.model.muc.user; + +import android.util.Log; + +import com.google.common.base.Strings; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.xmpp.Jid; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.muc.Affiliation; +import im.conversations.android.xmpp.model.muc.Role; + +import java.util.Locale; + +@XmlElement +public class Item extends Extension { + + + public Item() { + super(Item.class); + } + + public Affiliation getAffiliation() { + final var affiliation = this.getAttribute("affiliation"); + if (Strings.isNullOrEmpty(affiliation)) { + return Affiliation.NONE; + } + try { + return Affiliation.valueOf(affiliation.toUpperCase(Locale.ROOT)); + } catch (final IllegalArgumentException e) { + Log.d(Config.LOGTAG,"could not parse affiliation "+affiliation); + return Affiliation.NONE; + } + } + + public Role getRole() { + final var role = this.getAttribute("role"); + if (Strings.isNullOrEmpty(role)) { + return Role.NONE; + } + try { + return Role.valueOf(role.toUpperCase(Locale.ROOT)); + } catch (final IllegalArgumentException e) { + Log.d(Config.LOGTAG,"could not parse role "+ role); + return Role.NONE; + } + } + + public String getNick() { + return this.getAttribute("nick"); + } + + public Jid getJid() { + return this.getAttributeAsJid("jid"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/MucUser.java b/src/main/java/im/conversations/android/xmpp/model/muc/user/MucUser.java new file mode 100644 index 0000000000000000000000000000000000000000..5496c3ef204cdc2ef51de90f1b60285608408768 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/user/MucUser.java @@ -0,0 +1,27 @@ +package im.conversations.android.xmpp.model.muc.user; + +import com.google.common.collect.Collections2; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import java.util.Objects; + +@XmlElement(name = "x") +public class MucUser extends Extension { + + public static final int STATUS_CODE_SELF_PRESENCE = 110; + + public MucUser() { + super(MucUser.class); + } + + public Item getItem() { + return this.getExtension(Item.class); + } + + public Collection getStatus() { + return Collections2.filter( + Collections2.transform(getExtensions(Status.class), Status::getCode), + Objects::nonNull); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/Status.java b/src/main/java/im/conversations/android/xmpp/model/muc/user/Status.java new file mode 100644 index 0000000000000000000000000000000000000000..0706585af418b6f18c561b86e0688a7987815ff6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/user/Status.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.muc.user; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Status extends Extension { + + public Status() { + super(Status.class); + } + + public Integer getCode() { + return this.getOptionalIntAttribute("code").orNull(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java b/src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..f5bfcaeda0112563ec3a67faff002586518ebe65 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.MUC_USER) +package im.conversations.android.xmpp.model.muc.user; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java b/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java new file mode 100644 index 0000000000000000000000000000000000000000..e9a98512823681750c9bdd5c79c8507ea2b9ce23 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.nick; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.NICK) +public class Nick extends Extension { + + public Nick() { + super(Nick.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java b/src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java new file mode 100644 index 0000000000000000000000000000000000000000..29ffc739f6b419b388929fdb53666aa5ec39e6e6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java @@ -0,0 +1,19 @@ +package im.conversations.android.xmpp.model.occupant; + +import com.google.common.base.Strings; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.OCCUPANT_ID) +public class OccupantId extends Extension { + + public OccupantId() { + super(OccupantId.class); + } + + public String getId() { + return Strings.emptyToNull(this.getAttribute("id")); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java b/src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java new file mode 100644 index 0000000000000000000000000000000000000000..b324332a93828be7faa2aa5e7e8806e076e29e2f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java @@ -0,0 +1,18 @@ +package im.conversations.android.xmpp.model.oob; + +import com.google.common.base.Strings; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "x") +public class OutOfBandData extends Extension { + + public OutOfBandData() { + super(OutOfBandData.class); + } + + public String getURL() { + final URL url = this.getExtension(URL.class); + return url == null ? null : Strings.emptyToNull(url.getContent()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/oob/URL.java b/src/main/java/im/conversations/android/xmpp/model/oob/URL.java new file mode 100644 index 0000000000000000000000000000000000000000..008b084800de7fb8fc65d2ef102154cdfb823bd3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/oob/URL.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.oob; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "url") +public class URL extends Extension { + + public URL() { + super(URL.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/oob/package-info.java b/src/main/java/im/conversations/android/xmpp/model/oob/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..aec4dee2466834735e150cce4569a1aac5fe4e33 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/oob/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.OOB) +package im.conversations.android.xmpp.model.oob; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java b/src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java new file mode 100644 index 0000000000000000000000000000000000000000..d3d4b391034608fbcc7b3e52757f0b69403c5962 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.pars; + +import im.conversations.android.annotation.XmlElement; +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.PARS) +public class PreAuth extends Extension { + + public PreAuth() { + super(PreAuth.class); + } + + public void setToken(final String token) { + this.setAttribute("token", token); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pgp/Encrypted.java b/src/main/java/im/conversations/android/xmpp/model/pgp/Encrypted.java new file mode 100644 index 0000000000000000000000000000000000000000..43e4e2354b5e6be25564deb883dabab1287b7808 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pgp/Encrypted.java @@ -0,0 +1,14 @@ +package im.conversations.android.xmpp.model.pgp; + +import eu.siacs.conversations.xml.Namespace; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "x", namespace = Namespace.PGP_ENCRYPTED) +public class Encrypted extends Extension { + + public Encrypted() { + super(Encrypted.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pgp/Signed.java b/src/main/java/im/conversations/android/xmpp/model/pgp/Signed.java new file mode 100644 index 0000000000000000000000000000000000000000..c75413972f521366f46f542d222f0d851cd7219c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pgp/Signed.java @@ -0,0 +1,15 @@ +package im.conversations.android.xmpp.model.pgp; + +import eu.siacs.conversations.xml.Namespace; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "x",namespace = Namespace.PGP_SIGNED) +public class Signed extends Extension { + + + public Signed() { + super(Signed.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/ping/Ping.java b/src/main/java/im/conversations/android/xmpp/model/ping/Ping.java new file mode 100644 index 0000000000000000000000000000000000000000..7f8f1c3a0505d65182c3e7ea05ecef51c8f20f19 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/ping/Ping.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.ping; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.PING) +public class Ping extends Extension { + + public Ping() { + super(Ping.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java new file mode 100644 index 0000000000000000000000000000000000000000..dbf2c3c239a6a9f2009c25fb2e28bb8e96e4e56f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model.pubsub; + +import im.conversations.android.xmpp.model.Extension; + +public interface Item { + + T getExtension(final Class clazz); + + String getId(); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java new file mode 100644 index 0000000000000000000000000000000000000000..ceb1931ca35cbb1048e5c66f14cfe5ea9eff2303 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java @@ -0,0 +1,52 @@ +package im.conversations.android.xmpp.model.pubsub; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.pubsub.event.Retract; +import java.util.Collection; +import java.util.Map; +import java.util.NoSuchElementException; + +public interface Items { + + Collection getItems(); + + String getNode(); + + Collection getRetractions(); + + default Map getItemMap(final Class clazz) { + final ImmutableMap.Builder builder = ImmutableMap.builder(); + for (final Item item : getItems()) { + final var id = item.getId(); + final T extension = item.getExtension(clazz); + if (extension == null || Strings.isNullOrEmpty(id)) { + continue; + } + builder.put(id, extension); + } + return builder.buildKeepingLast(); + } + + default T getItemOrThrow(final String id, final Class clazz) { + final var map = getItemMap(clazz); + final var item = map.get(id); + if (item == null) { + throw new NoSuchElementException( + String.format("An item with id %s does not exist", id)); + } + return item; + } + + default T getFirstItem(final Class clazz) { + final var map = getItemMap(clazz); + return Iterables.getFirst(map.values(), null); + } + + default T getOnlyItem(final Class clazz) { + final var map = getItemMap(clazz); + return Iterables.getOnlyElement(map.values()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java new file mode 100644 index 0000000000000000000000000000000000000000..a4fc1ee8ecc2f36ad5cc087373594fc4a6db3cb7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java @@ -0,0 +1,64 @@ +package im.conversations.android.xmpp.model.pubsub; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.pubsub.event.Retract; +import java.util.Collection; + +@XmlElement(name = "pubsub") +public class PubSub extends Extension { + + public PubSub() { + super(PubSub.class); + } + + public Items getItems() { + return this.getExtension(ItemsWrapper.class); + } + + @XmlElement(name = "items") + public static class ItemsWrapper extends Extension implements Items { + + public ItemsWrapper() { + super(ItemsWrapper.class); + } + + public String getNode() { + return this.getAttribute("node"); + } + + public Collection getItems() { + return this.getExtensions(Item.class); + } + + public Collection getRetractions() { + return this.getExtensions(Retract.class); + } + + public void setNode(String node) { + this.setAttribute("node", node); + } + + public void setMaxItems(final int maxItems) { + this.setAttribute("max_items", maxItems); + } + } + + @XmlElement(name = "item") + public static class Item extends Extension + implements im.conversations.android.xmpp.model.pubsub.Item { + + public Item() { + super(Item.class); + } + + @Override + public String getId() { + return this.getAttribute("id"); + } + + public void setId(String itemId) { + this.setAttribute("id", itemId); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java new file mode 100644 index 0000000000000000000000000000000000000000..7a384f5489745dad052c06c0d52806c2acc8a4e5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.pubsub; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Publish extends Extension { + + public Publish() { + super(Publish.class); + } + + public void setNode(String node) { + this.setAttribute("node", node); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..ec94f0604df8e042030d3ff4d7dbeaceebe08a75 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.pubsub; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.NodeConfiguration; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.data.Data; + +@XmlElement +public class PublishOptions extends Extension { + + public PublishOptions() { + super(PublishOptions.class); + } + + public static PublishOptions of(NodeConfiguration nodeConfiguration) { + final var publishOptions = new PublishOptions(); + publishOptions.addExtension(Data.of(Namespace.PUBSUB_PUBLISH_OPTIONS, nodeConfiguration)); + return publishOptions; + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java new file mode 100644 index 0000000000000000000000000000000000000000..309381197e0a5f226bba63fd80b7b3077ed03e60 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java @@ -0,0 +1,20 @@ +package im.conversations.android.xmpp.model.pubsub; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Retract extends Extension { + + public Retract() { + super(Retract.class); + } + + public void setNode(String node) { + this.setAttribute("node", node); + } + + public void setNotify(boolean notify) { + this.setAttribute("notify", notify ? 1 : 0); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java new file mode 100644 index 0000000000000000000000000000000000000000..a1c81a659d8c397d0fa977544fd2f89a2f0cfa59 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java @@ -0,0 +1,19 @@ +package im.conversations.android.xmpp.model.pubsub.error; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +public abstract class PubSubError extends Extension { + + private PubSubError(Class clazz) { + super(clazz); + } + + @XmlElement + public static class PreconditionNotMet extends PubSubError { + + public PreconditionNotMet() { + super(PreconditionNotMet.class); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..49d45f8c59e55b0022880a4e6609bf093fffe966 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.PUBSUB_ERROR) +package im.conversations.android.xmpp.model.pubsub.error; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java new file mode 100644 index 0000000000000000000000000000000000000000..1e180c460053ee0bb74cbefa753c86b63f175e2f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java @@ -0,0 +1,56 @@ +package im.conversations.android.xmpp.model.pubsub.event; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.pubsub.Items; +import java.util.Collection; + +@XmlElement +public class Event extends Extension { + + public Event() { + super(Event.class); + } + + public Items getItems() { + return this.getExtension(ItemsWrapper.class); + } + + public Purge getPurge() { + return this.getExtension(Purge.class); + } + + @XmlElement(name = "items") + public static class ItemsWrapper extends Extension implements Items { + + public ItemsWrapper() { + super(ItemsWrapper.class); + } + + public String getNode() { + return this.getAttribute("node"); + } + + public Collection getItems() { + return this.getExtensions(Item.class); + } + + public Collection getRetractions() { + return this.getExtensions(Retract.class); + } + } + + @XmlElement(name = "item") + public static class Item extends Extension + implements im.conversations.android.xmpp.model.pubsub.Item { + + public Item() { + super(Item.class); + } + + @Override + public String getId() { + return this.getAttribute("id"); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java new file mode 100644 index 0000000000000000000000000000000000000000..64550e0b77f1ca33d57e2b501204995545bfcac6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.pubsub.event; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Purge extends Extension { + + public Purge() { + super(Purge.class); + } + + public String getNode() { + return this.getAttribute("node"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java new file mode 100644 index 0000000000000000000000000000000000000000..139a49522c48cb74aa9b4469768baf43305fb446 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.pubsub.event; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Retract extends Extension { + + public Retract() { + super(Retract.class); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..223345c68b18021198477ec4168c2ef4cc0ddd9b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.PUBSUB_EVENT) +package im.conversations.android.xmpp.model.pubsub.event; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java new file mode 100644 index 0000000000000000000000000000000000000000..53b987f53c9c7fddbb2c3766e3731bd104722a67 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.pubsub.owner; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.data.Data; + +@XmlElement +public class Configure extends Extension { + + public Configure() { + super(Configure.class); + } + + public void setNode(final String node) { + this.setAttribute("node", node); + } + + public Data getData() { + return this.getExtension(Data.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java new file mode 100644 index 0000000000000000000000000000000000000000..c3a61e6195750b2c42524d3e5309aa004918b083 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.pubsub.owner; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "pubsub") +public class PubSubOwner extends Extension { + + public PubSubOwner() { + super(PubSubOwner.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..d3ecb89aa94dbbaba381de987db16a19d03c3747 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.PUBSUB_OWNER) +package im.conversations.android.xmpp.model.pubsub.owner; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..a68a021fd5cbc339dd345d8e0121d44fe0f71541 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.PUBSUB) +package im.conversations.android.xmpp.model.pubsub; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java b/src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java new file mode 100644 index 0000000000000000000000000000000000000000..1d854a83a4def96a087612a5bf66621a22e20961 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.reactions; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Reaction extends Extension { + + public Reaction() { + super(Reaction.class); + } + + public Reaction(final String reaction) { + this(); + setContent(reaction); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java b/src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java new file mode 100644 index 0000000000000000000000000000000000000000..ec3ae989176bc25d1cef26cafa8137c8ef707d46 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java @@ -0,0 +1,36 @@ +package im.conversations.android.xmpp.model.reactions; + +import com.google.common.base.Strings; +import com.google.common.collect.Collections2; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import java.util.Objects; + +@XmlElement +public class Reactions extends Extension { + + public Reactions() { + super(Reactions.class); + } + + public Collection getReactions() { + return Collections2.filter( + Collections2.transform(getExtensions(Reaction.class), Reaction::getContent), + r -> Objects.nonNull(Strings.nullToEmpty(r))); + } + + public String getId() { + return this.getAttribute("id"); + } + + public void setId(String id) { + this.setAttribute("id", id); + } + + public static Reactions to(final String id) { + final var reactions = new Reactions(); + reactions.setId(id); + return reactions; + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java b/src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..bdb8a8dca23d7ba4a17dba9c09089793959780b2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.REACTIONS) +package im.conversations.android.xmpp.model.reactions; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/receipts/Received.java b/src/main/java/im/conversations/android/xmpp/model/receipts/Received.java new file mode 100644 index 0000000000000000000000000000000000000000..71fe922c158adeb678b30e6181047329e5dc0784 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/receipts/Received.java @@ -0,0 +1,20 @@ +package im.conversations.android.xmpp.model.receipts; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.DeliveryReceipt; + +@XmlElement +public class Received extends DeliveryReceipt { + + public Received() { + super(Received.class); + } + + public void setId(String id) { + this.setAttribute("id", id); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/receipts/Request.java b/src/main/java/im/conversations/android/xmpp/model/receipts/Request.java new file mode 100644 index 0000000000000000000000000000000000000000..684477af354890b8cc9ef7487e4850926925b8f6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/receipts/Request.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.receipts; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.DeliveryReceiptRequest; + +@XmlElement +public class Request extends DeliveryReceiptRequest { + + public Request() { + super(Request.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java b/src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..8e3de2cad52e6ee3300ac4ea0e284cce1b8c40c0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.DELIVERY_RECEIPTS) +package im.conversations.android.xmpp.model.receipts; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Instructions.java b/src/main/java/im/conversations/android/xmpp/model/register/Instructions.java new file mode 100644 index 0000000000000000000000000000000000000000..cd22f2a3a8427cd0d58ff511a6daa2d5525215c3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Instructions.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model.register; + +import im.conversations.android.xmpp.model.Extension; + +public class Instructions extends Extension { + + public Instructions() { + super(Instructions.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Password.java b/src/main/java/im/conversations/android/xmpp/model/register/Password.java new file mode 100644 index 0000000000000000000000000000000000000000..9da687c213e2cc26dfbc6eaec8710a84a8ff71f7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Password.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model.register; + +import im.conversations.android.xmpp.model.Extension; + +public class Password extends Extension { + + public Password() { + super(Password.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Register.java b/src/main/java/im/conversations/android/xmpp/model/register/Register.java new file mode 100644 index 0000000000000000000000000000000000000000..4a48bd8d15283ab5d2f6cda5f0a782735d84cbf2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Register.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.register; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import org.jxmpp.jid.parts.Localpart; + +@XmlElement(name = "query") +public class Register extends Extension { + + public Register() { + super(Register.class); + } + + public void addUsername(final Localpart username) { + this.addExtension(new Username()).setContent(username.toString()); + } + + public void addPassword(final String password) { + this.addExtension(new Password()).setContent(password); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Remove.java b/src/main/java/im/conversations/android/xmpp/model/register/Remove.java new file mode 100644 index 0000000000000000000000000000000000000000..bbd327bfd66130779102cadb4817602e1979c40a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Remove.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model.register; + +import im.conversations.android.xmpp.model.Extension; + +public class Remove extends Extension { + + public Remove() { + super(Remove.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Username.java b/src/main/java/im/conversations/android/xmpp/model/register/Username.java new file mode 100644 index 0000000000000000000000000000000000000000..bc93581b66937b358324193a00b9e4e85907aa8b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Username.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.register; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Username extends Extension { + + public Username() { + super(Username.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/package-info.java b/src/main/java/im/conversations/android/xmpp/model/register/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..9e7a3e8f33d5fc7afe2eef429b23023e94e755c5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.REGISTER) +package im.conversations.android.xmpp.model.register; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Group.java b/src/main/java/im/conversations/android/xmpp/model/roster/Group.java new file mode 100644 index 0000000000000000000000000000000000000000..9f36efae7c7d6defa57a3b142a55725e2ae386df --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/roster/Group.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.roster; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Group extends Extension { + + public Group() { + super(Group.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Item.java b/src/main/java/im/conversations/android/xmpp/model/roster/Item.java new file mode 100644 index 0000000000000000000000000000000000000000..0a2e0ef54add741e8141d333dc9d21e510e309ec --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/roster/Item.java @@ -0,0 +1,61 @@ +package im.conversations.android.xmpp.model.roster; + +import com.google.common.collect.Collections2; + +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.Jid; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +@XmlElement +public class Item extends Extension { + + public static final List RESULT_SUBSCRIPTIONS = + Arrays.asList(Subscription.NONE, Subscription.TO, Subscription.FROM, Subscription.BOTH); + + public Item() { + super(Item.class); + } + + public Jid getJid() { + return getAttributeAsJid("jid"); + } + + public String getItemName() { + return this.getAttribute("name"); + } + + public boolean isPendingOut() { + return "subscribe".equalsIgnoreCase(this.getAttribute("ask")); + } + + public Subscription getSubscription() { + final String value = this.getAttribute("subscription"); + try { + return value == null ? null : Subscription.valueOf(value.toUpperCase(Locale.ROOT)); + } catch (final IllegalArgumentException e) { + return null; + } + } + + public Collection getGroups() { + return Collections2.filter( + Collections2.transform(getExtensions(Group.class), Element::getContent), + Objects::nonNull); + } + + public enum Subscription { + NONE, + TO, + FROM, + BOTH, + REMOVE + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Query.java b/src/main/java/im/conversations/android/xmpp/model/roster/Query.java new file mode 100644 index 0000000000000000000000000000000000000000..616f6ae0b68cd6a13fdc9096d18df47f5852a8fb --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/roster/Query.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.roster; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "query", namespace = Namespace.ROSTER) +public class Query extends Extension { + + public Query() { + super(Query.class); + } + + public void setVersion(final String rosterVersion) { + this.setAttribute("ver", rosterVersion); + } + + public String getVersion() { + return this.getAttribute("ver"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/package-info.java b/src/main/java/im/conversations/android/xmpp/model/roster/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..eea0703fd6f0ea886d484914a512398c1b6713d5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/roster/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.ROSTER) +package im.conversations.android.xmpp.model.roster; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/After.java b/src/main/java/im/conversations/android/xmpp/model/rsm/After.java new file mode 100644 index 0000000000000000000000000000000000000000..90179bff0a1af8f678196858701e4df7f1a7916f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/After.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.rsm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class After extends Extension { + + public After() { + super(After.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Before.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Before.java new file mode 100644 index 0000000000000000000000000000000000000000..c3c6ac1a89a3c07b1b10e4d77d25d39b06602ab4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Before.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.rsm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Before extends Extension { + + public Before() { + super(Before.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Count.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Count.java new file mode 100644 index 0000000000000000000000000000000000000000..c54f9d5e08fc4f8980712bfbebaed0a4d0e5f49e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Count.java @@ -0,0 +1,23 @@ +package im.conversations.android.xmpp.model.rsm; + +import com.google.common.base.Strings; +import com.google.common.primitives.Ints; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Count extends Extension { + + public Count() { + super(Count.class); + } + + public Integer getCount() { + final var content = getContent(); + if (Strings.isNullOrEmpty(content)) { + return null; + } else { + return Ints.tryParse(content); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/First.java b/src/main/java/im/conversations/android/xmpp/model/rsm/First.java new file mode 100644 index 0000000000000000000000000000000000000000..b976632e43eb993c3b06bf8e4231748f1c1190c1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/First.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.rsm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class First extends Extension { + + public First() { + super(First.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Last.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Last.java new file mode 100644 index 0000000000000000000000000000000000000000..01d53e07304c62e7846fc70414f146e1bceb2cbd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Last.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.rsm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Last extends Extension { + + public Last() { + super(Last.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Max.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Max.java new file mode 100644 index 0000000000000000000000000000000000000000..06908be8b8c761284c65762541794ef7f1e83757 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Max.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.rsm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Max extends Extension { + + public Max() { + super(Max.class); + } + + public void setMax(final int max) { + this.setContent(String.valueOf(max)); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Set.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Set.java new file mode 100644 index 0000000000000000000000000000000000000000..6f428565c47e7aef7fe9d013d64ef524b963e87d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Set.java @@ -0,0 +1,55 @@ +package im.conversations.android.xmpp.model.rsm; + +import com.google.common.base.Strings; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.Page; +import im.conversations.android.xmpp.Range; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Set extends Extension { + + public Set() { + super(Set.class); + } + + public static Set of(final Range range, final Integer max) { + final var set = new Set(); + if (range.order == Range.Order.NORMAL) { + final var after = set.addExtension(new After()); + after.setContent(range.id); + } else if (range.order == Range.Order.REVERSE) { + final var before = set.addExtension(new Before()); + before.setContent(range.id); + } else { + throw new IllegalArgumentException("Invalid order"); + } + if (max != null) { + set.addExtension(new Max()).setMax(max); + } + return set; + } + + public Page asPage() { + final var first = this.getExtension(First.class); + final var last = this.getExtension(Last.class); + + final var firstId = first == null ? null : first.getContent(); + final var lastId = last == null ? null : last.getContent(); + if (Strings.isNullOrEmpty(firstId) || Strings.isNullOrEmpty(lastId)) { + throw new IllegalStateException("Invalid page. Missing first or last"); + } + return new Page(firstId, lastId, this.getCount()); + } + + public boolean isEmpty() { + final var first = this.getExtension(First.class); + final var last = this.getExtension(Last.class); + return first == null && last == null; + } + + public Integer getCount() { + final var count = this.getExtension(Count.class); + return count == null ? null : count.getCount(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/package-info.java b/src/main/java/im/conversations/android/xmpp/model/rsm/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..c00fd37c9bd4f6a8544e25651780b4365c6b75cc --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.RESULT_SET_MANAGEMENT) +package im.conversations.android.xmpp.model.rsm; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java new file mode 100644 index 0000000000000000000000000000000000000000..668d10d80f8bcb56971968d41d500615d129a987 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sasl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Auth extends StreamElement { + + public Auth() { + super(Auth.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanism.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanism.java new file mode 100644 index 0000000000000000000000000000000000000000..e23087d8924afb6de4747b58c012527da85bbcfc --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanism.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sasl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Mechanism extends Extension { + + public Mechanism() { + super(Mechanism.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanisms.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanisms.java new file mode 100644 index 0000000000000000000000000000000000000000..7612ba3583e0e7a3576198f682d556f083366b65 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanisms.java @@ -0,0 +1,29 @@ +package im.conversations.android.xmpp.model.sasl; + +import com.google.common.collect.Collections2; + +import eu.siacs.conversations.xml.Element; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.AuthenticationStreamFeature; +import im.conversations.android.xmpp.model.StreamFeature; + +import java.util.Collection; +import java.util.Objects; + +@XmlElement +public class Mechanisms extends AuthenticationStreamFeature { + + + public Mechanisms() { + super(Mechanisms.class); + } + + public Collection getMechanisms() { + return getExtensions(Mechanism.class); + } + + public Collection getMechanismNames() { + return Collections2.filter(Collections2.transform(getMechanisms(), Element::getContent), Objects::nonNull); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Response.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Response.java new file mode 100644 index 0000000000000000000000000000000000000000..5e2ab626e1ca067204e9bdb25fd19b1a15c72d9a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Response.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sasl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Response extends StreamElement { + + public Response() { + super(Response.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Success.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Success.java new file mode 100644 index 0000000000000000000000000000000000000000..d7323e4789e7afebd17ed31b95070e4e6a710397 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Success.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.sasl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Success extends StreamElement { + + + public Success() { + super(Success.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/package-info.java b/src/main/java/im/conversations/android/xmpp/model/sasl/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..3b0de4f4a8ba4a30ce6852b0b2de9c983113b0ff --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.SASL) +package im.conversations.android.xmpp.model.sasl; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java new file mode 100644 index 0000000000000000000000000000000000000000..7869bf010600d2525add357171a4f57601977db7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sasl2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Authenticate extends StreamElement { + + public Authenticate() { + super(Authenticate.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Authentication.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authentication.java new file mode 100644 index 0000000000000000000000000000000000000000..ad26d37e6ea9d06fb8f3363fa3a39d1e5de435d2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authentication.java @@ -0,0 +1,30 @@ +package im.conversations.android.xmpp.model.sasl2; + +import com.google.common.collect.Collections2; + +import eu.siacs.conversations.xml.Element; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.AuthenticationStreamFeature; +import im.conversations.android.xmpp.model.StreamFeature; + +import java.util.Collection; +import java.util.Objects; + +@XmlElement +public class Authentication extends AuthenticationStreamFeature { + public Authentication() { + super(Authentication.class); + } + + public Collection getMechanisms() { + return getExtensions(Mechanism.class); + } + + public Collection getMechanismNames() { + return Collections2.filter(Collections2.transform(getMechanisms(), Element::getContent), Objects::nonNull); + } + + public Inline getInline() { + return this.getExtension(Inline.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java new file mode 100644 index 0000000000000000000000000000000000000000..e29ae7dea3ced429d4e25d17f1fe1954017b5d98 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java @@ -0,0 +1,28 @@ +package im.conversations.android.xmpp.model.sasl2; + +import com.google.common.base.Strings; + +import eu.siacs.conversations.xmpp.Jid; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class AuthorizationIdentifier extends Extension { + + + public AuthorizationIdentifier() { + super(AuthorizationIdentifier.class); + } + + public Jid get() { + final var content = getContent(); + if ( Strings.isNullOrEmpty(content)) { + return null; + } + try { + return Jid.ofEscaped(content); + } catch (final IllegalArgumentException e) { + return null; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java new file mode 100644 index 0000000000000000000000000000000000000000..6a6ad0dd8b1fa526a5015eb0dcaca4d475586504 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java @@ -0,0 +1,34 @@ +package im.conversations.android.xmpp.model.sasl2; + +import com.google.common.collect.Collections2; + +import eu.siacs.conversations.xml.Element; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.fast.Fast; +import im.conversations.android.xmpp.model.fast.Mechanism; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +@XmlElement +public class Inline extends Extension { + + public Inline() { + super(Inline.class); + } + + public Fast getFast() { + return this.getExtension(Fast.class); + } + + public Collection getFastMechanisms() { + final var fast = getFast(); + final Collection mechanisms = + fast == null ? Collections.emptyList() : fast.getExtensions(Mechanism.class); + return Collections2.filter( + Collections2.transform(mechanisms, Element::getContent), Objects::nonNull); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Mechanism.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Mechanism.java new file mode 100644 index 0000000000000000000000000000000000000000..d0a615777be37c9ba979e4db03879954ebe95a1e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Mechanism.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sasl2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Mechanism extends Extension { + + public Mechanism() { + super(Mechanism.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Response.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Response.java new file mode 100644 index 0000000000000000000000000000000000000000..91f1b7dab6a6e2dc48c40d8e9638085b9ed4404b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Response.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sasl2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Response extends StreamElement { + + public Response() { + super(Response.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Success.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Success.java new file mode 100644 index 0000000000000000000000000000000000000000..17673b35a7035d6229e1d808d336839b05b43aa3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Success.java @@ -0,0 +1,23 @@ +package im.conversations.android.xmpp.model.sasl2; + +import eu.siacs.conversations.xmpp.Jid; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Success extends StreamElement { + + + public Success() { + super(Success.class); + } + + public Jid getAuthorizationIdentifier() { + final var id = this.getExtension(AuthorizationIdentifier.class); + if (id == null) { + return null; + } + return id.get(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..10a61d1098dcaded964807e1610ec2d1f3b46336 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.SASL_2) +package im.conversations.android.xmpp.model.sasl2; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Ack.java b/src/main/java/im/conversations/android/xmpp/model/sm/Ack.java new file mode 100644 index 0000000000000000000000000000000000000000..5cafc8c1d41a53281c2e27cde0503ced3c0d880a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Ack.java @@ -0,0 +1,23 @@ +package im.conversations.android.xmpp.model.sm; + +import com.google.common.base.Optional; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement(name = "a") +public class Ack extends StreamElement { + + public Ack() { + super(Ack.class); + } + + public Ack(final int sequence) { + super(Ack.class); + this.setAttribute("h", sequence); + } + + public Optional getHandled() { + return this.getOptionalIntAttribute("h"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Enable.java b/src/main/java/im/conversations/android/xmpp/model/sm/Enable.java new file mode 100644 index 0000000000000000000000000000000000000000..9b80a93baa0f565c05b2cbe7df7146cf396397dd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Enable.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.sm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Enable extends StreamElement { + + public Enable() { + super(Enable.class); + this.setAttribute("resume", "true"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Enabled.java b/src/main/java/im/conversations/android/xmpp/model/sm/Enabled.java new file mode 100644 index 0000000000000000000000000000000000000000..b900d435cd81b47e0e36b2a94c1efc01dc2e03ff --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Enabled.java @@ -0,0 +1,35 @@ +package im.conversations.android.xmpp.model.sm; + +import com.google.common.base.Optional; +import com.google.common.base.Strings; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Enabled extends StreamElement { + + public Enabled() { + super(Enabled.class); + } + + public boolean isResume() { + return this.getAttributeAsBoolean("resume"); + } + + public String getLocation() { + return this.getAttribute("location"); + } + + public Optional getResumeId() { + final var id = this.getAttribute("id"); + if (Strings.isNullOrEmpty(id)) { + return Optional.absent(); + } + if (isResume()) { + return Optional.of(id); + } else { + return Optional.absent(); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Failed.java b/src/main/java/im/conversations/android/xmpp/model/sm/Failed.java new file mode 100644 index 0000000000000000000000000000000000000000..1e15bfe6c0b46fa97d2ddc4b7bcbe4243ed19217 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Failed.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.sm; + +import com.google.common.base.Optional; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Failed extends StreamElement { + public Failed() { + super(Failed.class); + } + + public Optional getHandled() { + return this.getOptionalIntAttribute("h"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Request.java b/src/main/java/im/conversations/android/xmpp/model/sm/Request.java new file mode 100644 index 0000000000000000000000000000000000000000..ad1de61bc827995471cde86b6d437cf81ad3db1c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Request.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement(name = "r") +public class Request extends StreamElement { + + public Request() { + super(Request.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Resume.java b/src/main/java/im/conversations/android/xmpp/model/sm/Resume.java new file mode 100644 index 0000000000000000000000000000000000000000..e47b19966ca7b344296c6580797d639a7996ff83 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Resume.java @@ -0,0 +1,18 @@ +package im.conversations.android.xmpp.model.sm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Resume extends StreamElement { + + public Resume() { + super(Resume.class); + } + + public Resume(final String id, final int sequence) { + super(Resume.class); + this.setAttribute("previd", id); + this.setAttribute("h", sequence); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Resumed.java b/src/main/java/im/conversations/android/xmpp/model/sm/Resumed.java new file mode 100644 index 0000000000000000000000000000000000000000..eb240745fd4b1402e96c838ef71a868f1d7dcf68 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Resumed.java @@ -0,0 +1,18 @@ +package im.conversations.android.xmpp.model.sm; + +import com.google.common.base.Optional; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Resumed extends StreamElement { + + public Resumed() { + super(Resumed.class); + } + + public Optional getHandled() { + return this.getOptionalIntAttribute("h"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/StreamManagement.java b/src/main/java/im/conversations/android/xmpp/model/sm/StreamManagement.java new file mode 100644 index 0000000000000000000000000000000000000000..48103755a14d5fc335808ff76972e57e02d26048 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/StreamManagement.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sm; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamFeature; + +@XmlElement(name = "sm") +public class StreamManagement extends StreamFeature { + + public StreamManagement() { + super(StreamManagement.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/package-info.java b/src/main/java/im/conversations/android/xmpp/model/sm/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..dd2e036fcc2527fbca9acd8283189f9852f4b133 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.STREAM_MANAGEMENT) +package im.conversations.android.xmpp.model.sm; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java new file mode 100644 index 0000000000000000000000000000000000000000..9f94400c32692b9a734c5d503e98d3151738d532 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java @@ -0,0 +1,77 @@ +package im.conversations.android.xmpp.model.stanza; + +import com.google.common.base.Strings; + +import eu.siacs.conversations.xml.Element; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.error.Error; + +import java.util.Locale; + +@XmlElement +public class Iq extends Stanza { + + public static Iq TIMEOUT = new Iq(Type.TIMEOUT); + + public Iq() { + super(Iq.class); + } + + public Iq(final Type type) { + super(Iq.class); + this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT)); + } + + // TODO get rid of timeout + public enum Type { + SET, + GET, + ERROR, + RESULT, + TIMEOUT + } + + public Type getType() { + return Type.valueOf( + Strings.nullToEmpty(this.getAttribute("type")).toUpperCase(Locale.ROOT)); + } + + @Override + public boolean isInvalid() { + final var id = getId(); + if (Strings.isNullOrEmpty(id)) { + return true; + } + return super.isInvalid(); + } + + // Legacy methods that need to be refactored: + + public Element query() { + final Element query = findChild("query"); + if (query != null) { + return query; + } + return addChild("query"); + } + + public Element query(final String xmlns) { + final Element query = query(); + query.setAttribute("xmlns", xmlns); + return query(); + } + + public Iq generateResponse(final Iq.Type type) { + final var packet = new Iq(type); + packet.setTo(this.getFrom()); + packet.setId(this.getId()); + return packet; + } + + public String getErrorCondition() { + final Error error = getError(); + final var condition = error == null ? null : error.getCondition(); + return condition == null ? null : condition.getName(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java new file mode 100644 index 0000000000000000000000000000000000000000..a1c981b9d994fee3705efb7a6b4c6bcb86ecea02 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java @@ -0,0 +1,64 @@ +package im.conversations.android.xmpp.model.stanza; + +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.LocalizedContent; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.jabber.Body; + +import java.util.Locale; + +@XmlElement +public class Message extends Stanza { + + public Message() { + super(Message.class); + } + + public Message(Type type) { + this(); + this.setType(type); + } + + public LocalizedContent getBody() { + return findInternationalizedChildContentInDefaultNamespace("body"); + } + + public Type getType() { + final var value = this.getAttribute("type"); + if (value == null) { + return Type.NORMAL; + } else { + try { + return Type.valueOf(value.toUpperCase(Locale.ROOT)); + } catch (final IllegalArgumentException e) { + return null; + } + } + } + + public void setType(final Type type) { + if (type == null || type == Type.NORMAL) { + this.removeAttribute("type"); + } else { + this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT)); + } + } + + public void setBody(final String text) { + this.addExtension(new Body(text)); + } + + public void setAxolotlMessage(Element axolotlMessage) { + this.children.remove(findChild("body")); + this.children.add(0, axolotlMessage); + } + + public enum Type { + ERROR, + NORMAL, + GROUPCHAT, + HEADLINE, + CHAT + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java new file mode 100644 index 0000000000000000000000000000000000000000..129660b000cd1565956f10703afe828599eb8bf8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.stanza; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.capabilties.EntityCapabilities; + +@XmlElement +public class Presence extends Stanza implements EntityCapabilities { + + public Presence() { + super(Presence.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java new file mode 100644 index 0000000000000000000000000000000000000000..82a8ce3dfbfa1f225e6030ace9f746cdff09c9cd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java @@ -0,0 +1,74 @@ +package im.conversations.android.xmpp.model.stanza; + +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.xmpp.InvalidJid; +import eu.siacs.conversations.xmpp.Jid; + +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.StreamElement; +import im.conversations.android.xmpp.model.error.Error; + +public abstract class Stanza extends StreamElement { + + protected Stanza(final Class clazz) { + super(clazz); + } + + public Jid getTo() { + return this.getAttributeAsJid("to"); + } + + public Jid getFrom() { + return this.getAttributeAsJid("from"); + } + + public String getId() { + return this.getAttribute("id"); + } + + public void setId(final String id) { + this.setAttribute("id", id); + } + + public void setFrom(final Jid from) { + this.setAttribute("from", from); + } + + public void setTo(final Jid to) { + this.setAttribute("to", to); + } + + public Error getError() { + return this.getExtension(Error.class); + } + + public boolean isInvalid() { + final var to = getTo(); + final var from = getFrom(); + if (to instanceof InvalidJid || from instanceof InvalidJid) { + return true; + } + return false; + } + + public boolean fromServer(final Account account) { + final Jid from = getFrom(); + return from == null + || from.equals(account.getDomain()) + || from.equals(account.getJid().asBareJid()) + || from.equals(account.getJid()); + } + + public boolean toServer(final Account account) { + final Jid to = getTo(); + return to == null + || to.equals(account.getDomain()) + || to.equals(account.getJid().asBareJid()) + || to.equals(account.getJid()); + } + + public boolean fromAccount(final Account account) { + final Jid from = getFrom(); + return from != null && from.asBareJid().equals(account.getJid().asBareJid()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java b/src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..d12fe56dbe53003ddbd8a9d81bc960d1cea69da9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.JABBER_CLIENT) +package im.conversations.android.xmpp.model.stanza; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Active.java b/src/main/java/im/conversations/android/xmpp/model/state/Active.java new file mode 100644 index 0000000000000000000000000000000000000000..15970bc5bf5eb6ea9d7bf21d319d22d2d637ddaf --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Active.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Active extends ChatStateNotification { + + public Active() { + super(Active.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java b/src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java new file mode 100644 index 0000000000000000000000000000000000000000..642ed519d15a34e6d368086ecf0f49d53303c1a2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.xmpp.model.Extension; + +public abstract class ChatStateNotification extends Extension { + + protected ChatStateNotification(Class clazz) { + super(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Composing.java b/src/main/java/im/conversations/android/xmpp/model/state/Composing.java new file mode 100644 index 0000000000000000000000000000000000000000..9871952e0de7d631364f53f31e8e289c052e39ba --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Composing.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Composing extends ChatStateNotification { + + public Composing() { + super(Composing.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Gone.java b/src/main/java/im/conversations/android/xmpp/model/state/Gone.java new file mode 100644 index 0000000000000000000000000000000000000000..a0a74e788c99fa10810afa00f6d11b70538d52ea --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Gone.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Gone extends ChatStateNotification { + + public Gone() { + super(Gone.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java b/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java new file mode 100644 index 0000000000000000000000000000000000000000..4a3670308ac0067d55cb4c6e0009d98514765382 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Inactive extends ChatStateNotification { + + public Inactive() { + super(Inactive.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Paused.java b/src/main/java/im/conversations/android/xmpp/model/state/Paused.java new file mode 100644 index 0000000000000000000000000000000000000000..f97f3e5045b54e4a318cdf5843766f6175bb40da --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Paused.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.annotation.XmlElement; + +@XmlElement +public class Paused extends ChatStateNotification { + + public Paused() { + super(Paused.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/package-info.java b/src/main/java/im/conversations/android/xmpp/model/state/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..a0cc97debfa49a575c48b8c6f5dd53086a75b52e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.CHAT_STATES) +package im.conversations.android.xmpp.model.state; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/streams/Features.java b/src/main/java/im/conversations/android/xmpp/model/streams/Features.java new file mode 100644 index 0000000000000000000000000000000000000000..0597c2241cfb809a46d7747ce309fc01a01fe15c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/streams/Features.java @@ -0,0 +1,33 @@ +package im.conversations.android.xmpp.model.streams; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.StreamElement; +import im.conversations.android.xmpp.model.StreamFeature; +import im.conversations.android.xmpp.model.capabilties.EntityCapabilities; +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.xmpp.model.sm.StreamManagement; + +@XmlElement +public class Features extends StreamElement implements EntityCapabilities { + public Features() { + super(Features.class); + } + + public boolean streamManagement() { + return hasStreamFeature(StreamManagement.class); + } + + public boolean invite() { + return this.hasChild("register", Namespace.INVITE); + } + + public boolean clientStateIndication() { + return this.hasChild("csi", Namespace.CSI); + } + + + public boolean hasStreamFeature(final Class clazz) { + return hasExtension(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/streams/package-info.java b/src/main/java/im/conversations/android/xmpp/model/streams/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..56900532c47a7a502beada11ecb22577a0583a07 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/streams/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.STREAMS) +package im.conversations.android.xmpp.model.streams; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/tls/Proceed.java b/src/main/java/im/conversations/android/xmpp/model/tls/Proceed.java new file mode 100644 index 0000000000000000000000000000000000000000..3e2cf454c9c3df9ec8b6b6b71e74648daaddc8e8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/tls/Proceed.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.tls; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement +public class Proceed extends StreamElement { + + public Proceed() { + super(Proceed.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/tls/Required.java b/src/main/java/im/conversations/android/xmpp/model/tls/Required.java new file mode 100644 index 0000000000000000000000000000000000000000..60f4652ba8b6293b6ecbb610a37e75c5815b0d4a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/tls/Required.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.tls; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Required extends Extension { + public Required() { + super(Required.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/tls/StartTls.java b/src/main/java/im/conversations/android/xmpp/model/tls/StartTls.java new file mode 100644 index 0000000000000000000000000000000000000000..337371c7b6aef6fef4b2d69faa1725091db20bd2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/tls/StartTls.java @@ -0,0 +1,15 @@ +package im.conversations.android.xmpp.model.tls; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.StreamElement; + +@XmlElement(name = "starttls") +public class StartTls extends StreamElement { + public StartTls() { + super(StartTls.class); + } + + public boolean isRequired() { + return hasExtension(Required.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/tls/package-info.java b/src/main/java/im/conversations/android/xmpp/model/tls/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..de3ed3ecdbc1274997673d6aef30d8042ab78540 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/tls/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.TLS) +package im.conversations.android.xmpp.model.tls; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java b/src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java new file mode 100644 index 0000000000000000000000000000000000000000..31a93962104ef834cae83978ce89d65946fb61b8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.unique; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class OriginId extends Extension { + + public OriginId() { + super(OriginId.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java b/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java new file mode 100644 index 0000000000000000000000000000000000000000..23b0fdcac823de630719bf14f8c47a559a2fbcfd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.unique; + +import eu.siacs.conversations.xmpp.Jid; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class StanzaId extends Extension { + + public StanzaId() { + super(StanzaId.class); + } + + public Jid getBy() { + return this.getAttributeAsJid("by"); + } + + public String getId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/unique/package-info.java b/src/main/java/im/conversations/android/xmpp/model/unique/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..31209ee24f25dc21e2c57aaad97c54fb38ae6066 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/unique/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.STANZA_IDS) +package im.conversations.android.xmpp.model.unique; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Get.java b/src/main/java/im/conversations/android/xmpp/model/upload/Get.java new file mode 100644 index 0000000000000000000000000000000000000000..5fad9afd409fe0d21d3f8a17cc68c7f7a22d5de1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Get.java @@ -0,0 +1,22 @@ +package im.conversations.android.xmpp.model.upload; + +import com.google.common.base.Strings; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import okhttp3.HttpUrl; + +@XmlElement +public class Get extends Extension { + + public Get() { + super(Get.class); + } + + public HttpUrl getUrl() { + final var url = this.getAttribute("url"); + if (Strings.isNullOrEmpty(url)) { + return null; + } + return HttpUrl.parse(url); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Header.java b/src/main/java/im/conversations/android/xmpp/model/upload/Header.java new file mode 100644 index 0000000000000000000000000000000000000000..00546d0d984f66542270ac645f9bf094035f74b1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Header.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.upload; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Header extends Extension { + + public Header() { + super(Header.class); + } + + public String getHeaderName() { + return this.getAttribute("name"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Put.java b/src/main/java/im/conversations/android/xmpp/model/upload/Put.java new file mode 100644 index 0000000000000000000000000000000000000000..1b52a495c551b4acb34778f09c45aff11999ed12 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Put.java @@ -0,0 +1,27 @@ +package im.conversations.android.xmpp.model.upload; + +import com.google.common.base.Strings; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import java.util.Collection; +import okhttp3.HttpUrl; + +@XmlElement +public class Put extends Extension { + + public Put() { + super(Put.class); + } + + public HttpUrl getUrl() { + final var url = this.getAttribute("url"); + if (Strings.isNullOrEmpty(url)) { + return null; + } + return HttpUrl.parse(url); + } + + public Collection
getHeaders() { + return this.getExtensions(Header.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Request.java b/src/main/java/im/conversations/android/xmpp/model/upload/Request.java new file mode 100644 index 0000000000000000000000000000000000000000..bbf8a98c1632aca15b6f39015448b1b3a611777d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Request.java @@ -0,0 +1,24 @@ +package im.conversations.android.xmpp.model.upload; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Request extends Extension { + + public Request() { + super(Request.class); + } + + public void setFilename(String filename) { + this.setAttribute("filename", filename); + } + + public void setSize(long size) { + this.setAttribute("size", size); + } + + public void setContentType(String type) { + this.setAttribute("content-ype", type); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java b/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java new file mode 100644 index 0000000000000000000000000000000000000000..df90157812be59d01d54ceca65ee47b7d9e764ff --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.upload; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Slot extends Extension { + + public Slot() { + super(Slot.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/package-info.java b/src/main/java/im/conversations/android/xmpp/model/upload/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..e4ccf3d8dc5087ad0c9eb589ba05a2f5ad9ec1ca --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.HTTP_UPLOAD) +package im.conversations.android.xmpp.model.upload; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/BinaryValue.java b/src/main/java/im/conversations/android/xmpp/model/vcard/BinaryValue.java new file mode 100644 index 0000000000000000000000000000000000000000..273dcfb25f8265d127db82c9d837a175e0d026ce --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/BinaryValue.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model.vcard; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.ByteContent; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "BINVAL") +public class BinaryValue extends Extension implements ByteContent { + + public BinaryValue() { + super(BinaryValue.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java b/src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java new file mode 100644 index 0000000000000000000000000000000000000000..92adc6831c5346cc15a1d2a5663341e29497e2c5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.vcard; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "PHOTO") +public class Photo extends Extension { + public Photo() { + super(Photo.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/VCard.java b/src/main/java/im/conversations/android/xmpp/model/vcard/VCard.java new file mode 100644 index 0000000000000000000000000000000000000000..20a6949775b4348d1c703d3362e570ff98b4f07c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/VCard.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.vcard; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "vCard") +public class VCard extends Extension { + + public VCard() { + super(VCard.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/package-info.java b/src/main/java/im/conversations/android/xmpp/model/vcard/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..7ee576ca27cc6113a5e8ab2a3dad72c1e8aa2339 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.VCARD_TEMP) +package im.conversations.android.xmpp.model.vcard; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/update/Photo.java b/src/main/java/im/conversations/android/xmpp/model/vcard/update/Photo.java new file mode 100644 index 0000000000000000000000000000000000000000..cb1f86d053eeba029c5339922bb63f614ed35f32 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/update/Photo.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.vcard.update; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Photo extends Extension { + + public Photo() { + super(Photo.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/update/VCardUpdate.java b/src/main/java/im/conversations/android/xmpp/model/vcard/update/VCardUpdate.java new file mode 100644 index 0000000000000000000000000000000000000000..0be3f94b9fef7428b4977ebde3cdb728f55b1e7e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/update/VCardUpdate.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.vcard.update; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "x") +public class VCardUpdate extends Extension { + + public VCardUpdate() { + super(VCardUpdate.class); + } + + public Photo getPhoto() { + return this.getExtension(Photo.class); + } + + public String getHash() { + final var photo = getPhoto(); + return photo == null ? null : photo.getContent(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/update/package-info.java b/src/main/java/im/conversations/android/xmpp/model/vcard/update/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..efed1536072f01dbe4279eafffe010740dda8ee6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/update/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.VCARD_TEMP_UPDATE) +package im.conversations.android.xmpp.model.vcard.update; + +import im.conversations.android.annotation.XmlPackage; +import eu.siacs.conversations.xml.Namespace; diff --git a/src/main/java/im/conversations/android/xmpp/model/version/Version.java b/src/main/java/im/conversations/android/xmpp/model/version/Version.java new file mode 100644 index 0000000000000000000000000000000000000000..7cbd5d22a805a46e2763d7425502d83e862790bd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/version/Version.java @@ -0,0 +1,25 @@ +package im.conversations.android.xmpp.model.version; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import eu.siacs.conversations.xml.Namespace; + +@XmlElement(name = "query", namespace = Namespace.VERSION) +public class Version extends Extension { + + public Version() { + super(Version.class); + } + + public void setSoftwareName(final String name) { + this.addChild("name").setContent(name); + } + + public void setVersion(final String version) { + this.addChild("version").setContent(version); + } + + public void setOs(final String os) { + this.addChild("os").setContent(os); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..bc8097fda6620d6f7a1e51cab4e529fb837eab28 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java @@ -0,0 +1,90 @@ +package im.conversations.android.xmpp.processor; + +import android.text.TextUtils; +import android.util.Log; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.generator.IqGenerator; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.xmpp.XmppConnection; + +import im.conversations.android.xmpp.model.stanza.Iq; + +public class BindProcessor implements Runnable { + + + private final XmppConnectionService service; + private final Account account; + + public BindProcessor(XmppConnectionService service, Account account) { + this.service = service; + this.account = account; + } + + @Override + public void run() { + final XmppConnection connection = account.getXmppConnection(); + service.cancelAvatarFetches(account); + final boolean loggedInSuccessfully = account.setOption(Account.OPTION_LOGGED_IN_SUCCESSFULLY, true); + final boolean gainedFeature = account.setOption(Account.OPTION_HTTP_UPLOAD_AVAILABLE, connection.getFeatures().httpUpload(0)); + if (loggedInSuccessfully || gainedFeature) { + service.databaseBackend.updateAccount(account); + } + + if (loggedInSuccessfully) { + if (!TextUtils.isEmpty(account.getDisplayName())) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": display name wasn't empty on first log in. publishing"); + service.publishDisplayName(account); + } + } + + account.getRoster().clearPresences(); + synchronized (account.inProgressConferenceJoins) { + account.inProgressConferenceJoins.clear(); + } + synchronized (account.inProgressConferencePings) { + account.inProgressConferencePings.clear(); + } + service.getJingleConnectionManager().notifyRebound(account); + service.getQuickConversationsService().considerSyncBackground(false); + + + connection.fetchRoster(); + + if (connection.getFeatures().bookmarks2()) { + service.fetchBookmarks2(account); + } else if (!connection.getFeatures().bookmarksConversion()) { + service.fetchBookmarks(account); + } + + if (connection.getFeatures().mds()) { + service.fetchMessageDisplayedSynchronization(account); + } else { + Log.d(Config.LOGTAG,account.getJid()+": server has no support for mds"); + } + final boolean flexible = connection.getFeatures().flexibleOfflineMessageRetrieval(); + final boolean catchup = service.getMessageArchiveService().inCatchup(account); + final boolean trackOfflineMessageRetrieval; + if (flexible && catchup && connection.isMamPreferenceAlways()) { + trackOfflineMessageRetrieval = false; + connection.sendIqPacket(IqGenerator.purgeOfflineMessages(), (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully purged offline messages"); + } + }); + } else { + trackOfflineMessageRetrieval = true; + } + service.sendPresence(account); + connection.trackOfflineMessageRetrieval(trackOfflineMessageRetrieval); + if (service.getPushManagementService().available(account)) { + service.getPushManagementService().registerPushTokenOnServer(account); + } + service.connectMultiModeConversations(account); + service.syncDirtyContacts(account); + + service.getUnifiedPushBroker().renewUnifiedPushEndpointsOnBind(account); + + } +} diff --git a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java index 45180b5d5dfb835a5db8df7c40868889bbaaebca..e5f4b9d7c83fc7463b1283c60326441d929eece2 100644 --- a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java +++ b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java @@ -15,7 +15,8 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.forms.Data; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; + +import im.conversations.android.xmpp.model.stanza.Iq; public class PushManagementService { @@ -25,7 +26,7 @@ public class PushManagementService { this.mXmppConnectionService = service; } - private static Data findResponseData(IqPacket response) { + private static Data findResponseData(Iq response) { final Element command = response.findChild("command", Namespace.COMMANDS); final Element x = command == null ? null : command.findChild("x", Namespace.DATA); return x == null ? null : Data.parse(x); @@ -35,43 +36,70 @@ public class PushManagementService { return Jid.of(mXmppConnectionService.getString(R.string.app_server)); } - void registerPushTokenOnServer(final Account account) { + public void registerPushTokenOnServer(final Account account) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has push support"); - retrieveFcmInstanceToken(token -> { - final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService); - final IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(getAppServer(), token, androidId); - mXmppConnectionService.sendIqPacket(account, packet, (a, response) -> { - final Data data = findResponseData(response); - if (response.getType() == IqPacket.TYPE.RESULT && data != null) { - try { - String node = data.getValue("node"); - String secret = data.getValue("secret"); - Jid jid = Jid.of(data.getValue("jid")); - if (node != null && secret != null) { - enablePushOnServer(a, jid, node, secret); - } - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } - } else { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": failed to enable push. invalid response from app server " + response); - } - }); - }); + retrieveFcmInstanceToken( + token -> { + final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService); + final var packet = + mXmppConnectionService + .getIqGenerator() + .pushTokenToAppServer(getAppServer(), token, androidId); + mXmppConnectionService.sendIqPacket( + account, + packet, + (response) -> { + final Data data = findResponseData(response); + if (response.getType() == Iq.Type.RESULT && data != null) { + final Jid jid; + try { + jid = Jid.ofEscaped(data.getValue("jid")); + } catch (final IllegalArgumentException e) { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": failed to enable push. invalid jid"); + return; + } + final String node = data.getValue("node"); + final String secret = data.getValue("secret"); + if (node != null && secret != null) { + enablePushOnServer(account, jid, node, secret); + } + } else { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": failed to enable push. invalid response from app server " + + response); + } + }); + }); } - private void enablePushOnServer(final Account account, final Jid appServer, final String node, final String secret) { - final IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret); - mXmppConnectionService.sendIqPacket(account, enable, (a, p) -> { - if (p.getType() == IqPacket.TYPE.RESULT) { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on server"); - } else if (p.getType() == IqPacket.TYPE.ERROR) { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": enabling push on server failed"); - } - }); + private void enablePushOnServer( + final Account account, final Jid appServer, final String node, final String secret) { + final Iq enable = + mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret); + mXmppConnectionService.sendIqPacket( + account, + enable, + (p) -> { + if (p.getType() == Iq.Type.RESULT) { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": successfully enabled push on server"); + } else if (p.getType() == Iq.Type.ERROR) { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + ": enabling push on server failed"); + } + }); } - private void retrieveFcmInstanceToken(final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) { + private void retrieveFcmInstanceToken( + final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) { final FirebaseMessaging firebaseMessaging; try { firebaseMessaging = FirebaseMessaging.getInstance(); @@ -79,26 +107,33 @@ public class PushManagementService { Log.d(Config.LOGTAG, "unable to get firebase instance token ", e); return; } - firebaseMessaging.getToken().addOnCompleteListener(task -> { - if (!task.isSuccessful()) { - Log.d(Config.LOGTAG, "unable to get Firebase instance token", task.getException()); - } - final String result; - try { - result = task.getResult(); - } catch (Exception e) { - Log.d(Config.LOGTAG, "unable to get Firebase instance token due to bug in library ", e); - return; - } - if (result != null) { - instanceTokenRetrieved.onGcmInstanceTokenRetrieved(result); - } - }); - + firebaseMessaging + .getToken() + .addOnCompleteListener( + task -> { + if (!task.isSuccessful()) { + Log.d( + Config.LOGTAG, + "unable to get Firebase instance token", + task.getException()); + } + final String result; + try { + result = task.getResult(); + } catch (Exception e) { + Log.d( + Config.LOGTAG, + "unable to get Firebase instance token due to bug in library ", + e); + return; + } + if (result != null) { + instanceTokenRetrieved.onGcmInstanceTokenRetrieved(result); + } + }); } - - public boolean available(Account account) { + public boolean available(final Account account) { final XmppConnection connection = account.getXmppConnection(); return connection != null && connection.getFeatures().sm() @@ -107,7 +142,9 @@ public class PushManagementService { } private boolean playServicesAvailable() { - return GoogleApiAvailabilityLight.getInstance().isGooglePlayServicesAvailable(mXmppConnectionService) == ConnectionResult.SUCCESS; + return GoogleApiAvailabilityLight.getInstance() + .isGooglePlayServicesAvailable(mXmppConnectionService) + == ConnectionResult.SUCCESS; } public boolean isStub() { diff --git a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java index 69cef954841e74a6fb668290f19c230d7f936e7b..8cf8fe20296d24b9763d8ee263f3796c9acef97c 100644 --- a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java +++ b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java @@ -69,7 +69,7 @@ import eu.siacs.conversations.utils.TLSSocketFactory; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; -import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import im.conversations.android.xmpp.model.stanza.Iq; import io.michaelrocks.libphonenumber.android.Phonenumber; public class QuickConversationsService extends AbstractQuickConversationsService { @@ -463,15 +463,15 @@ public class QuickConversationsService extends AbstractQuickConversationsService for (final PhoneNumberContact c : contacts.values()) { entries.add(new Element("entry").setAttribute("number", c.getPhoneNumber())); } - final IqPacket query = new IqPacket(IqPacket.TYPE.GET); + final Iq query = new Iq(Iq.Type.GET); query.setTo(syncServer); final Element book = new Element("phone-book", Namespace.SYNCHRONIZATION).setChildren(entries); final String statusQuo = Entry.statusQuo(contacts.values(), account.getRoster().getWithSystemAccounts(PhoneNumberContact.class)); book.setAttribute("ver", statusQuo); query.addChild(book); mLastSyncAttempt = Attempt.create(hash); - service.sendIqPacket(account, query, (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + service.sendIqPacket(account, query, (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element phoneBook = response.findChild("phone-book", Namespace.SYNCHRONIZATION); if (phoneBook != null) { final List withSystemAccounts = account.getRoster().getWithSystemAccounts(PhoneNumberContact.class); @@ -498,7 +498,7 @@ public class QuickConversationsService extends AbstractQuickConversationsService } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": phone number contact list remains unchanged"); } - } else if (response.getType() == IqPacket.TYPE.TIMEOUT) { + } else if (response.getType() == Iq.Type.TIMEOUT) { mLastSyncAttempt = Attempt.NULL; } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": failed to sync contact list with api server"); From 6a298ed5ff9c004c9a4fae433bbd0b1a06418233 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 24 May 2024 08:59:28 +0200 Subject: [PATCH 079/192] bump various libraries --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 7381247022b8a3125ac0d51b99aa3cb77440bcef..5443d33a91d61d48b45ea3040e69310edf09b02f 100644 --- a/build.gradle +++ b/build.gradle @@ -83,12 +83,12 @@ dependencies { implementation 'me.drakeet.support:toastcompat:1.1.0' implementation "com.leinardi.android:speed-dial:3.3.0" - implementation "com.squareup.retrofit2:retrofit:2.9.0" - implementation "com.squareup.retrofit2:converter-gson:2.9.0" + implementation "com.squareup.retrofit2:retrofit:2.11.0" + implementation "com.squareup.retrofit2:converter-gson:2.11.0" implementation "com.squareup.okhttp3:okhttp:4.12.0" implementation 'com.google.guava:guava:32.1.3-android' - quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.13.28' + quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.13.35' implementation 'im.conversations.webrtc:webrtc-android:119.0.1' } From c06ec217437074f5589944d6b792dc5260277011 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 May 2024 19:14:22 +0200 Subject: [PATCH 080/192] fix 'allow pm' checkbox default --- .../java/eu/siacs/conversations/entities/MucOptions.java | 6 ++++++ .../eu/siacs/conversations/ui/util/MucConfiguration.java | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 38e97957a29d706b5817bfaa3312c6f090697f49..0f6580005da1e6fe8e261b20b572026285c27958 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -18,6 +18,7 @@ import eu.siacs.conversations.xmpp.forms.Field; import eu.siacs.conversations.xmpp.pep.Avatar; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -192,6 +193,11 @@ public class MucOptions { } } + public boolean allowPmRaw() { + final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowpm"); + return field == null || Arrays.asList("anyone","participants").contains(field.getValue()); + } + public boolean participating() { return self.getRole().ranks(Role.PARTICIPANT) || !moderated(); } diff --git a/src/main/java/eu/siacs/conversations/ui/util/MucConfiguration.java b/src/main/java/eu/siacs/conversations/ui/util/MucConfiguration.java index 1b6c4e6d6ee391e91147c013c421b65fda20e5fd..f46b9ea28119c82e23d0346888730b0553e2ca84 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/MucConfiguration.java +++ b/src/main/java/eu/siacs/conversations/ui/util/MucConfiguration.java @@ -57,7 +57,7 @@ public class MucConfiguration { mucOptions.nonanonymous(), mucOptions.participantsCanChangeSubject(), mucOptions.moderated(), - mucOptions.allowPm() + mucOptions.allowPmRaw() }; options = new Option[] { From 987b32fb8462a9007c96d7a0d33446b6799218ec Mon Sep 17 00:00:00 2001 From: mazhe Date: Fri, 24 May 2024 08:00:16 +0000 Subject: [PATCH 081/192] Translated using Weblate (French) Currently translated at 99.9% (1019 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/fr/ --- src/main/res/values-fr/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 39731e001bdf799c84a292c0bc71523e653fec8d..eb03186aa95fc6b20e6876b11e475abbac807b0c 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -1090,4 +1090,5 @@ Accepter les invitations aux conversations de groupes provenant d\'inconnus Grande police Augmenter la taille de la police dans les bulles de message + Autoriser les messages privés \ No newline at end of file From a2f78b34361a9b25eb023743563ea3e98c8b8006 Mon Sep 17 00:00:00 2001 From: random_r Date: Fri, 24 May 2024 10:08:21 +0000 Subject: [PATCH 082/192] Translated using Weblate (Italian) Currently translated at 100.0% (67 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/it/ --- fastlane/metadata/android/it-IT/changelogs/4211204.txt | 2 ++ fastlane/metadata/android/it-IT/changelogs/4211304.txt | 1 + 2 files changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/4211204.txt create mode 100644 fastlane/metadata/android/it-IT/changelogs/4211304.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/4211204.txt b/fastlane/metadata/android/it-IT/changelogs/4211204.txt new file mode 100644 index 0000000000000000000000000000000000000000..2e101d8e001d989001f6753f5a893bc77f371a89 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* Corrette le chiamate che venivano smutate cambiando dispositivi di output +* Esclusi tutti i dispositivi Umidigi dall'integrazione di chiamate diff --git a/fastlane/metadata/android/it-IT/changelogs/4211304.txt b/fastlane/metadata/android/it-IT/changelogs/4211304.txt new file mode 100644 index 0000000000000000000000000000000000000000..5a83a3a699dbe94ffb849d281babd0902a94803c --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/4211304.txt @@ -0,0 +1 @@ +* Avvia backup come servizio in primo piano per impedire che il servizio si fermi dopo 10 minuti From 44b6fc66d5fd5e9f302141b274e99d7b7d1aab2b Mon Sep 17 00:00:00 2001 From: nautilusx Date: Fri, 24 May 2024 15:11:37 +0000 Subject: [PATCH 083/192] Translated using Weblate (German) Currently translated at 100.0% (67 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/de/ --- fastlane/metadata/android/de-DE/changelogs/4211104.txt | 2 +- fastlane/metadata/android/de-DE/changelogs/4211204.txt | 2 ++ fastlane/metadata/android/de-DE/changelogs/4211304.txt | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/de-DE/changelogs/4211204.txt create mode 100644 fastlane/metadata/android/de-DE/changelogs/4211304.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/4211104.txt b/fastlane/metadata/android/de-DE/changelogs/4211104.txt index 80db378170e7d475fff12f70327e5e6bbbde7e1c..fa21b66e5b7c02df54d55478daeb4396e98518fd 100644 --- a/fastlane/metadata/android/de-DE/changelogs/4211104.txt +++ b/fastlane/metadata/android/de-DE/changelogs/4211104.txt @@ -1,3 +1,3 @@ * Wiederkehrende Sicherungen planen -* Realme Geräte von der Anrufintegration ausgenommen +* Realme-Geräte von der Anrufintegration ausgenommen * Kleine Designverbesserungen (Chatblasen) diff --git a/fastlane/metadata/android/de-DE/changelogs/4211204.txt b/fastlane/metadata/android/de-DE/changelogs/4211204.txt new file mode 100644 index 0000000000000000000000000000000000000000..42a52939380d1ef3f5cc47a144a8f516e18b395c --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* Anruf wird nicht mehr stummgeschaltet, wenn das Ausgabegerät gewechselt wird +* Umidigi-Geräte von der Anrufintegration ausgenommen diff --git a/fastlane/metadata/android/de-DE/changelogs/4211304.txt b/fastlane/metadata/android/de-DE/changelogs/4211304.txt new file mode 100644 index 0000000000000000000000000000000000000000..127af85651e5b7d26fb5c33b045a9d5e0c82369e --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4211304.txt @@ -0,0 +1 @@ +* Sicherung als Vordergrunddienst ausführen, damit der Prozess nicht nach 10 Minuten gestoppt wird From 66a5ff6781ffe726b335e6f7d208ef0c35be3c0f Mon Sep 17 00:00:00 2001 From: Besnik_b Date: Fri, 24 May 2024 09:12:26 +0000 Subject: [PATCH 084/192] Translated using Weblate (Albanian) Currently translated at 100.0% (67 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/sq/ --- fastlane/metadata/android/sq/changelogs/4211304.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/sq/changelogs/4211304.txt diff --git a/fastlane/metadata/android/sq/changelogs/4211304.txt b/fastlane/metadata/android/sq/changelogs/4211304.txt new file mode 100644 index 0000000000000000000000000000000000000000..9b0191f551f5a645a7ddffb93828f0a0a8f7d9d1 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/4211304.txt @@ -0,0 +1 @@ +* Xhirim Kopjeruajtje si shërbim në prapaskenë, për të parandaluar ndalimin e procesit pas 10 minutash From 2ca7b9eb6c2957be98404530800f0a8761d205e2 Mon Sep 17 00:00:00 2001 From: NameLessGO Date: Sun, 26 May 2024 21:45:28 +0000 Subject: [PATCH 085/192] Translated using Weblate (Portuguese (Brazil)) Currently translated at 89.0% (908 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/pt_BR/ --- src/main/res/values-pt-rBR/strings.xml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index 329dfd8c80470a666ce8ffaa5217b4b9520d7be3..98f8386f3faf9cc6374fb6d854b2f33cae3ee5dd 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -40,7 +40,7 @@ Moderador Participante Visitante - Deseja remover %s da sua lista de contatos? As conversas associadas a esse contato não serão removidas. + Gostaria de remover %s da sua lista de contatos? O chat com este contato não será removido. Deseja bloquear o recebimento de mensagens de %s? Deseja desbloquear o recebimento de mensagens de %s? Bloquear todos os contatos de %s? @@ -78,19 +78,19 @@ Preparando para enviar as imagens Compartilhando arquivos. Por favor, aguarde... Limpar o histórico - Limpa o histórico de conversas + Limpar histórico do chat Deseja excluir todas as mensagens dessa conversa? \n \nAtenção: Isso não afetará mensagens armazenadas em outros dispositivos ou servidores. Excluir arquivo Deseja realmente excluir este arquivo?\n\nAtenção: Isso não excluirá cópias deste arquivo armazenadas em outros dispositivos ou servidores. Selecione o dispositivo - Enviar mensagem não criptografada + Enviar mensagem de texto não criptografada Enviar mensagem Enviar mensagem para %s Enviar mensagem criptografada via v\\OMEMO Novo apelido em uso - Enviar descriptografada + Enviar texto não criptografada Não foi possível descriptografar. Talvez você não tenha a chave privada apropriada. OpenKeychain OpenKeychain para criptografar e descriptografar as mensagens e gerenciar suas chaves públicas.

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

(Por favor reinicie o %1$s em seguida).]]>
@@ -163,7 +163,7 @@ Cliente incompatível Erro de fluxo Erro na abertura do fluxo - Descriptografada + Texto não criptografado OTR OpenPGP OMEMO @@ -175,7 +175,7 @@ Tem certeza que deseja remover sua chave pública OpenPGP do seu anúncio de presença?\nSeus contatos não poderão mais enviar mensagens criptografadas com o OpenPGP para você. A chave pública do OpenPGP foi publicada. Habilitar a conta - Se você excluir a sua conta todo o seu histórico de conversas será apagado + Tem certeza de que deseja excluir sua conta? Excluir sua conta apaga todo o seu histórico do chat Gravar voz Endereço XMPP Bloquear endereço XMPP @@ -983,4 +983,13 @@ As chamadas estão desabilitadas ao usar Tor Mudar para vídeo Recusar requisição de mudança para vídeo + Enviar relatórios de erro + Gostaria de remover o favorito de %s e arquivar chat? + Exclua o chat depois + Compartilhar com… + Arquivar chat + Novo chat + Enviar mensagem criptografada + Desconectado + Gostaria de remover o favorito de %s? \ No newline at end of file From 242a2f36432244afa3528238bf6b13c62f78b26d Mon Sep 17 00:00:00 2001 From: NameLessGO Date: Sun, 26 May 2024 21:39:19 +0000 Subject: [PATCH 086/192] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (13 of 13 strings) Translation: Conversations/Android App (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-conversations/pt_BR/ --- src/conversations/res/values-pt-rBR/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/conversations/res/values-pt-rBR/strings.xml b/src/conversations/res/values-pt-rBR/strings.xml index 210d814e165b9718dcae9ce84b0c44d59efaa73a..7e2b18910d5029cca757f6654326a7a60d2c5849 100644 --- a/src/conversations/res/values-pt-rBR/strings.xml +++ b/src/conversations/res/values-pt-rBR/strings.xml @@ -4,8 +4,8 @@ Usar o conversations.im Criar uma nova conta Você já possui uma conta XMPP? Esse pode ser o seu caso caso já esteja usando um outro cliente XMPP ou tenha usado o Conversations antes. Caso contrário, você pode criar uma nova conta XMPP agora.\nDica: alguns provedores de e-mail também fornecem contas XMPP. - O XMPP é uma rede de mensageria instantânea independente de provedor. Você pode usar esse cliente com qualquer servidor XMPP que você escolher. -\nEntretanto, para sua conveniência, nós simplificamos o processo de criação de uma conta em conversations.im, um provedor especialmente configurado para se usar com o Conversations. + XMPP é uma rede de mensagens instantâneas independente de provedor. Você pode usar este aplicativo com qualquer servidor XMPP de sua escolha. +\nNo entanto, para sua comodidade, facilitamos criar uma conta em conversas.im; um provedor especificamente adequado para uso com Conversations. Você foi convidado para %1$s. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nAo escolher %1$s como um provedor você conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo. Você foi convidado para %1$s. Um nome de usuário já foi escolhido para você. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nVocê conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo. Seu convite do servidor From f19110e4d24244c89d9ae2f4cfff9e605ba976cf Mon Sep 17 00:00:00 2001 From: alextecplayz Date: Sun, 26 May 2024 14:31:38 +0000 Subject: [PATCH 087/192] Translated using Weblate (Romanian) Currently translated at 100.0% (9 of 9 strings) Translation: Conversations/Android App (Quicksy) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-quicksy/ro/ --- src/quicksy/res/values-ro-rRO/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quicksy/res/values-ro-rRO/strings.xml b/src/quicksy/res/values-ro-rRO/strings.xml index 7dd79ce4ab0fcf271c9a6200d9813b3804298c1c..a2f4fbe04153e9591b7ec98f04da1c23c5b6e00d 100644 --- a/src/quicksy/res/values-ro-rRO/strings.xml +++ b/src/quicksy/res/values-ro-rRO/strings.xml @@ -4,9 +4,9 @@ Trimițând datele despre erori ajutați la continuarea dezvoltării aplicației Quicksy Contactele vă sunt anunțate atunci când folosiți Quicksy Pentru a continua să primiți notificări, chiar și când ecranul este oprit, trebuie să adăugați Quicksy în lista de aplicații protejate. - Poză profil Quicksy + Poză de profil Quicksy Quicksy nu este disponibilă în țara dumneavoastră. Nu s-a putut verifica identitatea serverului. Eroare de securitate necunoscută. A expirat timpul de așteptare conexiune server. - + \ No newline at end of file From 627bfae4ab8b9236dda945cd9797604113891b42 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 May 2024 16:31:59 +0200 Subject: [PATCH 088/192] use new xml api for bind 2 --- .../eu/siacs/conversations/AppSettings.java | 24 ++++++++ .../services/XmppConnectionService.java | 7 ++- .../conversations/utils/AccountUtils.java | 12 ++-- .../conversations/xmpp/XmppConnection.java | 28 +++++---- .../android/xmpp/model/bind2/Bind.java | 4 ++ .../android/xmpp/model/bind2/Tag.java | 17 ++++++ .../android/xmpp/model/sasl2/Device.java | 17 ++++++ .../android/xmpp/model/sasl2/Software.java | 17 ++++++ .../android/xmpp/model/sasl2/UserAgent.java | 25 ++++++++ .../services/QuickConversationsService.java | 60 ++++++++----------- 10 files changed, 160 insertions(+), 51 deletions(-) create mode 100644 src/main/java/im/conversations/android/xmpp/model/bind2/Tag.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Device.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Software.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/UserAgent.java diff --git a/src/main/java/eu/siacs/conversations/AppSettings.java b/src/main/java/eu/siacs/conversations/AppSettings.java index 0916d1e1a7d178bce439bfbc1b66334acc4f5c2e..4d39315244a607410df11496ec2ae8cab672a23a 100644 --- a/src/main/java/eu/siacs/conversations/AppSettings.java +++ b/src/main/java/eu/siacs/conversations/AppSettings.java @@ -10,6 +10,8 @@ import androidx.preference.PreferenceManager; import com.google.common.base.Strings; +import java.security.SecureRandom; + public class AppSettings { public static final String KEEP_FOREGROUND_SERVICE = "enable_foreground_service"; @@ -45,6 +47,7 @@ public class AppSettings { public static final String LARGE_FONT = "large_font"; private static final String ACCEPT_INVITES_FROM_STRANGERS = "accept_invites_from_strangers"; + private static final String INSTALLATION_ID = "im.conversations.android.install_id"; private final Context context; @@ -140,4 +143,25 @@ public class AppSettings { public boolean isRequireChannelBinding() { return getBooleanPreference(REQUIRE_CHANNEL_BINDING, R.bool.require_channel_binding); } + + public synchronized long getInstallationId() { + final var sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + final long existing = sharedPreferences.getLong(INSTALLATION_ID, 0); + if (existing != 0) { + return existing; + } + final var secureRandom = new SecureRandom(); + final var installationId = secureRandom.nextLong(); + sharedPreferences.edit().putLong(INSTALLATION_ID, installationId).apply(); + return installationId; + } + + public synchronized void resetInstallationId() { + final var secureRandom = new SecureRandom(); + final var installationId = secureRandom.nextLong(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putLong(INSTALLATION_ID, installationId) + .apply(); + } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 0cf6fbe248756554170a15b4ad7d37facf659ebe..b7935c3b5362027c51f4251a716df9c25a96d71a 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -229,6 +229,8 @@ public class XmppConnectionService extends Service { public DatabaseBackend databaseBackend; private final ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor("ContactMerger"); private long mLastActivity = 0; + + private final AppSettings appSettings = new AppSettings(this); private final FileBackend fileBackend = new FileBackend(this); private MemorizingTrustManager mMemorizingTrustManager; private final NotificationService mNotificationService = new NotificationService(this); @@ -483,6 +485,10 @@ public class XmppConnectionService extends Service { } } + public AppSettings getAppSettings() { + return this.appSettings; + } + public FileBackend getFileBackend() { return this.fileBackend; } @@ -4633,7 +4639,6 @@ public class XmppConnectionService extends Service { public void updateMemorizingTrustManager() { final MemorizingTrustManager trustManager; - final var appSettings = new AppSettings(this); if (appSettings.isTrustSystemCAStore()) { trustManager = new MemorizingTrustManager(getApplicationContext()); } else { diff --git a/src/main/java/eu/siacs/conversations/utils/AccountUtils.java b/src/main/java/eu/siacs/conversations/utils/AccountUtils.java index fe70241f90faf1d4117b02d5c0a3b8819d024f3c..4b2c2957f62aaa1e1a2e6e814312a4bb7b136843 100644 --- a/src/main/java/eu/siacs/conversations/utils/AccountUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/AccountUtils.java @@ -38,23 +38,27 @@ public class AccountUtils { return false; } - public static String publicDeviceId(final Account account) { + public static String publicDeviceId(final Account account, final long installationId) { final UUID uuid; try { uuid = UUID.fromString(account.getUuid()); } catch (final IllegalArgumentException e) { return account.getUuid(); } + return createUuid4(uuid.getMostSignificantBits(), installationId).toString(); + } + + public static UUID createUuid4(long mostSigBits, long leastSigBits) { final byte[] bytes = Bytes.concat( - Longs.toByteArray(uuid.getLeastSignificantBits()), - Longs.toByteArray(uuid.getLeastSignificantBits())); + Longs.toByteArray(mostSigBits), + Longs.toByteArray(leastSigBits)); bytes[6] &= 0x0f; /* clear version */ bytes[6] |= 0x40; /* set to version 4 */ bytes[8] &= 0x3f; /* clear variant */ bytes[8] |= 0x80; /* set to IETF variant */ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); - return new UUID(byteBuffer.getLong(), byteBuffer.getLong()).toString(); + return new UUID(byteBuffer.getLong(), byteBuffer.getLong()); } public static List getEnabledAccounts(final XmppConnectionService service) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index a3826626678d9a2061dfe33ce7acad9d686a9274..9a51f9e51550624e70e07c2e20232be3140513d1 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -23,6 +23,7 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import eu.siacs.conversations.AppSettings; +import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.XmppDomainVerifier; @@ -78,6 +79,7 @@ import im.conversations.android.xmpp.model.sasl.Response; import im.conversations.android.xmpp.model.sasl.Success; import im.conversations.android.xmpp.model.sasl2.Authenticate; import im.conversations.android.xmpp.model.sasl2.Authentication; +import im.conversations.android.xmpp.model.sasl2.UserAgent; import im.conversations.android.xmpp.model.sm.Ack; import im.conversations.android.xmpp.model.sm.Enable; import im.conversations.android.xmpp.model.sm.Enabled; @@ -195,7 +197,7 @@ public class XmppConnection implements Runnable { public XmppConnection(final Account account, final XmppConnectionService service) { this.account = account; this.mXmppConnectionService = service; - this.appSettings = new AppSettings(mXmppConnectionService.getApplicationContext()); + this.appSettings = mXmppConnectionService.getAppSettings(); this.presenceListener = new PresenceParser(service, account); this.unregisteredIqListener = new IqParser(service, account); this.messageListener = new MessageParser(service, account); @@ -1664,15 +1666,15 @@ public class XmppConnection implements Runnable { if (!Strings.isNullOrEmpty(firstMessage)) { authenticate.addChild("initial-response").setContent(firstMessage); } - final Element userAgent = authenticate.addChild("user-agent"); - userAgent.setAttribute("id", AccountUtils.publicDeviceId(account)); - userAgent - .addChild("software") - .setContent(mXmppConnectionService.getString(R.string.app_name)); + final var userAgent = + authenticate.addExtension( + new UserAgent( + AccountUtils.publicDeviceId( + account, appSettings.getInstallationId()))); + userAgent.setSoftware( + String.format("%s %s", BuildConfig.APP_NAME, BuildConfig.VERSION_NAME)); if (!PhoneHelper.isEmulator()) { - userAgent - .addChild("device") - .setContent(String.format("%s %s", Build.MANUFACTURER, Build.MODEL)); + userAgent.setDevice(String.format("%s %s", Build.MANUFACTURER, Build.MODEL)); } // do not include bind if 'inlineStreamManagement' is missing and we have a streamId // (because we would rather just do a normal SM/resume) @@ -1698,7 +1700,7 @@ public class XmppConnection implements Runnable { private Bind generateBindRequest(final Collection bindFeatures) { Log.d(Config.LOGTAG, "inline bind features: " + bindFeatures); final var bind = new Bind(); - bind.addChild("tag").setContent(mXmppConnectionService.getString(R.string.app_name)); + bind.setTag(BuildConfig.APP_NAME); if (bindFeatures.contains(Namespace.CARBONS)) { bind.addExtension(new im.conversations.android.xmpp.model.carbons.Enable()); } @@ -2292,6 +2294,10 @@ public class XmppConnection implements Runnable { return; } if (streamError.hasChild("conflict")) { + final var loginInfo = this.loginInfo; + if (loginInfo != null && loginInfo.saslVersion == SaslMechanism.Version.SASL_2) { + this.appSettings.resetInstallationId(); + } account.setResource(createNewResource()); Log.d( Config.LOGTAG, @@ -2299,7 +2305,7 @@ public class XmppConnection implements Runnable { + ": switching resource due to conflict (" + account.getResource() + ")"); - throw new IOException(); + throw new IOException("Closed stream due to resource conflict"); } else if (streamError.hasChild("host-unknown")) { throw new StateChangingException(Account.State.HOST_UNKNOWN); } else if (streamError.hasChild("policy-violation")) { diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java index bff72a06b5f595aa0f3d184c0b3cabad96317c46..3af144105420ffbbd222dd5f8c4423b0aaaf07d5 100644 --- a/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java @@ -21,4 +21,8 @@ public class Bind extends Extension { final var inline = getInline(); return inline == null ? Collections.emptyList() : inline.getExtensions(Feature.class); } + + public void setTag(final String tag) { + this.addExtension(new Tag(tag)); + } } diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Tag.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Tag.java new file mode 100644 index 0000000000000000000000000000000000000000..5fac1d9e378ba68012ac299d8ac98713fba9f985 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Tag.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.bind2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Tag extends Extension { + + public Tag() { + super(Tag.class); + } + + public Tag(final String tag) { + this(); + setContent(tag); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Device.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Device.java new file mode 100644 index 0000000000000000000000000000000000000000..2594f5874b09baaadd100486d8e3b492db949f41 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Device.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.sasl2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Device extends Extension { + + public Device() { + super(Device.class); + } + + public Device(final String device) { + this(); + this.setContent(device); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Software.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Software.java new file mode 100644 index 0000000000000000000000000000000000000000..8685ed3ff63285ecba2e012f0294cf6016c41017 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Software.java @@ -0,0 +1,17 @@ +package im.conversations.android.xmpp.model.sasl2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Software extends Extension { + + public Software() { + super(Software.class); + } + + public Software(final String software) { + this(); + this.setContent(software); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/UserAgent.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/UserAgent.java new file mode 100644 index 0000000000000000000000000000000000000000..bb2a0c68cfae29d8fc02c2eed963b3fe82075b61 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/UserAgent.java @@ -0,0 +1,25 @@ +package im.conversations.android.xmpp.model.sasl2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class UserAgent extends Extension { + + public UserAgent() { + super(UserAgent.class); + } + + public UserAgent(final String userAgentId) { + this(); + this.setAttribute("id", userAgentId); + } + + public void setSoftware(final String software) { + this.addExtension(new Software(software)); + } + + public void setDevice(final String device) { + this.addExtension(new Device(device)); + } +} diff --git a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java index 8cf8fe20296d24b9763d8ee263f3796c9acef97c..d2af65f8346c4d0f70c5f4572d6330f08180a482 100644 --- a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java +++ b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java @@ -5,16 +5,36 @@ import static eu.siacs.conversations.utils.Random.SECURE_RANDOM; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.SystemClock; -import android.preference.PreferenceManager; import android.util.Log; import com.google.common.collect.ImmutableMap; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.android.PhoneNumberContact; +import eu.siacs.conversations.crypto.TrustManagers; +import eu.siacs.conversations.crypto.sasl.Plain; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Entry; +import eu.siacs.conversations.http.HttpConnectionManager; +import eu.siacs.conversations.utils.AccountUtils; +import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.PhoneNumberUtilWrapper; +import eu.siacs.conversations.utils.SerialSingleThreadExecutor; +import eu.siacs.conversations.utils.SmsRetrieverWrapper; +import eu.siacs.conversations.utils.TLSSocketFactory; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.Jid; + +import im.conversations.android.xmpp.model.stanza.Iq; + +import io.michaelrocks.libphonenumber.android.Phonenumber; + import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; @@ -38,7 +58,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.WeakHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -52,26 +71,6 @@ import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.android.PhoneNumberContact; -import eu.siacs.conversations.crypto.TrustManagers; -import eu.siacs.conversations.crypto.sasl.Plain; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Contact; -import eu.siacs.conversations.entities.Entry; -import eu.siacs.conversations.http.HttpConnectionManager; -import eu.siacs.conversations.utils.AccountUtils; -import eu.siacs.conversations.utils.CryptoHelper; -import eu.siacs.conversations.utils.PhoneNumberUtilWrapper; -import eu.siacs.conversations.utils.SerialSingleThreadExecutor; -import eu.siacs.conversations.utils.SmsRetrieverWrapper; -import eu.siacs.conversations.utils.TLSSocketFactory; -import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.Jid; -import im.conversations.android.xmpp.model.stanza.Iq; -import io.michaelrocks.libphonenumber.android.Phonenumber; - public class QuickConversationsService extends AbstractQuickConversationsService { @@ -88,8 +87,6 @@ public class QuickConversationsService extends AbstractQuickConversationsService private static final String BASE_URL = "https://" + API_DOMAIN; - private static final String INSTALLATION_ID = "eu.siacs.conversations.installation-id"; - private final Set mOnVerificationRequested = Collections.newSetFromMap(new WeakHashMap<>()); private final Set mOnVerification = Collections.newSetFromMap(new WeakHashMap<>()); @@ -308,16 +305,9 @@ public class QuickConversationsService extends AbstractQuickConversationsService } private String getInstallationId() { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(service); - String id = preferences.getString(INSTALLATION_ID, null); - if (id != null) { - return id; - } else { - id = UUID.randomUUID().toString(); - preferences.edit().putString(INSTALLATION_ID, id).apply(); - return id; - } - + final var appSettings = service.getAppSettings(); + final long installationId = appSettings.getInstallationId(); + return AccountUtils.createUuid4(installationId, installationId).toString(); } private int getApiErrorCode(final Exception e) { From 1e3930a8267a530bc25c01acb6cba3b0a5c966fc Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 May 2024 16:35:09 +0200 Subject: [PATCH 089/192] use proper theme colors for new key icon --- src/main/res/drawable/ic_new_releases_24dp.xml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/res/drawable/ic_new_releases_24dp.xml b/src/main/res/drawable/ic_new_releases_24dp.xml index dd9a4b95d7b299264b79cc66471c06c68f48b523..b0ffc3f4273ed4d81c23ad8c1c87c598d5906612 100644 --- a/src/main/res/drawable/ic_new_releases_24dp.xml +++ b/src/main/res/drawable/ic_new_releases_24dp.xml @@ -1,5 +1,10 @@ - - + + From 69fc07db631567a3ba5da735c5ec6301b8246719 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 May 2024 16:43:19 +0200 Subject: [PATCH 090/192] exclude early oppo devices from call integration --- .../eu/siacs/conversations/services/CallIntegration.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index 121c2cae6de367606d2e68cf636c1cb5e7952923..15bc3a9b28da00da58a03eebb4678530727db02c 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegration.java @@ -531,6 +531,12 @@ public class CallIntegration extends Connection { && Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { return false; } + // we are relatively sure that old Oppo devices are broken too. We get reports of 'number + // not sent' from Oppo R15x (Android 10) + if ("OPPO".equalsIgnoreCase(Build.MANUFACTURER) + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + return false; + } // we only know of one Umidigi device (BISON_GT2_5G) that doesn't work (audio is not being // routed properly) However with those devices being extremely rare it's impossible to gauge // how many might be effected and no Naomi Wu around to clarify with the company directly From 21c5da33080d2fe2440813cfffcfceafce56a6c4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 May 2024 16:53:42 +0200 Subject: [PATCH 091/192] catch rare audio source exception (when permissions are lacking) in recorder --- .../java/eu/siacs/conversations/ui/RecordingActivity.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java index 64446e930b1bdcae45700b84612855e29797e563..231a8600d239851db4c35d447cb628b929e70d8d 100644 --- a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java @@ -105,7 +105,12 @@ public class RecordingActivity extends BaseActivity implements View.OnClickListe private boolean startRecording() { mRecorder = new MediaRecorder(); - mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + try { + mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + } catch (final RuntimeException e) { + Log.e(Config.LOGTAG,"could not set audio source", e); + return false; + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { mRecorder.setPrivacySensitive(true); } From bcf5e1916a9d8a235e9b44085340fa6592b90dda Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 May 2024 17:02:18 +0200 Subject: [PATCH 092/192] version bump to 2.16.3 + changelog --- CHANGELOG.md | 5 +++++ build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/4211404.txt | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/4211404.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index cdcc3b23fd44d3d34f5c226947745f01b75d8bb0..80e79abe53a4d8060f9bc07eaaeb55ed830e03ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Version 2.16.3 + +* exclude older Oppo devices from call integration +* various bug fixes + ### Version 2.16.2 * Run Backup as foreground service to prevent process being stopped after 10 minutes diff --git a/build.gradle b/build.gradle index 5443d33a91d61d48b45ea3040e69310edf09b02f..04da16f852675eec4954300370554db7ea4a42ed 100644 --- a/build.gradle +++ b/build.gradle @@ -104,8 +104,8 @@ android { defaultConfig { minSdkVersion 23 targetSdkVersion 34 - versionCode 42113 - versionName "2.16.2" + versionCode 42114 + versionName "2.16.3" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/4211404.txt b/fastlane/metadata/android/en-US/changelogs/4211404.txt new file mode 100644 index 0000000000000000000000000000000000000000..8c3bfc7ebdb9def198cd160feced925061bb10c9 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* exclude older Oppo devices from call integration +* various bug fixes From f0ec9006f0257fcfa1998cec6e8dd2cb12e1abec Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 2 Jun 2024 17:01:17 +0200 Subject: [PATCH 093/192] fix rare NPE race condition --- .../conversations/xmpp/XmppConnection.java | 42 +++++++++---------- .../xmpp/model/AuthenticationRequest.java | 13 ++++++ .../android/xmpp/model/sasl/Auth.java | 9 +++- .../xmpp/model/sasl2/Authenticate.java | 10 ++++- 4 files changed, 49 insertions(+), 25 deletions(-) create mode 100644 src/main/java/im/conversations/android/xmpp/model/AuthenticationRequest.java diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 9a51f9e51550624e70e07c2e20232be3140513d1..38194d5cd9674932a2ad0dce1992c1fe3fa63093 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -63,6 +63,7 @@ import eu.siacs.conversations.xmpp.bind.Bind2; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived; +import im.conversations.android.xmpp.model.AuthenticationRequest; import im.conversations.android.xmpp.model.AuthenticationStreamFeature; import im.conversations.android.xmpp.model.StreamElement; import im.conversations.android.xmpp.model.bind2.Bind; @@ -1514,14 +1515,15 @@ public class XmppConnection implements Runnable { final String firstMessage = saslMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)); final boolean usingFast = SaslMechanism.hashedToken(saslMechanism); - final StreamElement authenticate; + final AuthenticationRequest authenticate; + final LoginInfo loginInfo; if (version == SaslMechanism.Version.SASL) { authenticate = new Auth(); if (!Strings.isNullOrEmpty(firstMessage)) { authenticate.setContent(firstMessage); } quickStartAvailable = false; - this.loginInfo = new LoginInfo(saslMechanism, version, Collections.emptyList()); + loginInfo = new LoginInfo(saslMechanism, version, Collections.emptyList()); } else if (version == SaslMechanism.Version.SASL_2) { final Authentication authentication = (Authentication) authElement; final var inline = authentication.getInline(); @@ -1552,7 +1554,7 @@ public class XmppConnection implements Runnable { return; } } - this.loginInfo = new LoginInfo(saslMechanism, version, bindFeatures); + loginInfo = new LoginInfo(saslMechanism, version, bindFeatures); this.hashTokenRequest = hashTokenRequest; authenticate = generateAuthenticationRequest( @@ -1560,7 +1562,7 @@ public class XmppConnection implements Runnable { } else { throw new AssertionError("Missing implementation for " + version); } - + this.loginInfo = loginInfo; if (account.setOption(Account.OPTION_QUICKSTART_AVAILABLE, quickStartAvailable)) { mXmppConnectionService.databaseBackend.updateAccount(account); } @@ -1571,8 +1573,8 @@ public class XmppConnection implements Runnable { + ": Authenticating with " + version + "/" - + LoginInfo.mechanism(this.loginInfo).getMechanism()); - authenticate.setAttribute("mechanism", LoginInfo.mechanism(this.loginInfo).getMechanism()); + + LoginInfo.mechanism(loginInfo).getMechanism()); + authenticate.setMechanism(LoginInfo.mechanism(loginInfo)); synchronized (this.mStanzaQueue) { this.stanzasSentBeforeAuthentication = this.stanzasSent; tagWriter.writeElement(authenticate); @@ -1650,13 +1652,13 @@ public class XmppConnection implements Runnable { } } - private Authenticate generateAuthenticationRequest( + private AuthenticationRequest generateAuthenticationRequest( final String firstMessage, final boolean usingFast) { return generateAuthenticationRequest( firstMessage, usingFast, null, Bind2.QUICKSTART_FEATURES, true); } - private Authenticate generateAuthenticationRequest( + private AuthenticationRequest generateAuthenticationRequest( final String firstMessage, final boolean usingFast, final HashedToken.Mechanism hashedTokenRequest, @@ -1909,13 +1911,15 @@ public class XmppConnection implements Runnable { } clearIqCallbacks(); if (account.getJid().isBareJid()) { - account.setResource(this.createNewResource()); + account.setResource(createNewResource()); } else { fixResource(mXmppConnectionService, account); } final Iq iq = new Iq(Iq.Type.SET); final String resource = - Config.USE_RANDOM_RESOURCE_ON_EVERY_BIND ? nextRandomId() : account.getResource(); + Config.USE_RANDOM_RESOURCE_ON_EVERY_BIND + ? CryptoHelper.random(9) + : account.getResource(); iq.addExtension(new im.conversations.android.xmpp.model.bind.Bind()).setResource(resource); this.sendUnmodifiedIqPacket( iq, @@ -2381,11 +2385,11 @@ public class XmppConnection implements Runnable { SaslMechanism.Version.SASL_2, Bind2.QUICKSTART_FEATURES); final boolean usingFast = quickStartMechanism instanceof HashedToken; - final StreamElement authenticate = + final AuthenticationRequest authenticate = generateAuthenticationRequest( quickStartMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)), usingFast); - authenticate.setAttribute("mechanism", quickStartMechanism.getMechanism()); + authenticate.setMechanism(quickStartMechanism); sendStartStream(true, false); synchronized (this.mStanzaQueue) { this.stanzasSentBeforeAuthentication = this.stanzasSent; @@ -2416,16 +2420,8 @@ public class XmppConnection implements Runnable { tagWriter.writeTag(stream, flush); } - private String createNewResource() { - return mXmppConnectionService.getString(R.string.app_name) + '.' + nextRandomId(true); - } - - private String nextRandomId() { - return nextRandomId(false); - } - - private String nextRandomId(final boolean s) { - return CryptoHelper.random(s ? 3 : 9); + private static String createNewResource() { + return String.format("%s.%s", BuildConfig.APP_NAME, CryptoHelper.random(3)); } public String sendIqPacket(final Iq packet, final Consumer callback) { @@ -2437,7 +2433,7 @@ public class XmppConnection implements Runnable { final Iq packet, final Consumer callback, boolean force) { // TODO if callback != null verify that type is get or set if (packet.getId() == null) { - packet.setAttribute("id", nextRandomId()); + packet.setId(CryptoHelper.random(9)); } if (callback != null) { synchronized (this.packetCallbacks) { diff --git a/src/main/java/im/conversations/android/xmpp/model/AuthenticationRequest.java b/src/main/java/im/conversations/android/xmpp/model/AuthenticationRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..b2122ab863637c257b1b957b6e200c2f27aa5f5d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/AuthenticationRequest.java @@ -0,0 +1,13 @@ +package im.conversations.android.xmpp.model; + +import eu.siacs.conversations.crypto.sasl.SaslMechanism; + +public abstract class AuthenticationRequest extends StreamElement{ + + + protected AuthenticationRequest(Class clazz) { + super(clazz); + } + + public abstract void setMechanism(final SaslMechanism mechanism); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java index 668d10d80f8bcb56971968d41d500615d129a987..e9dd801f233ef6db23d3781b6724a6ab1e536c3a 100644 --- a/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java @@ -1,12 +1,19 @@ package im.conversations.android.xmpp.model.sasl; +import eu.siacs.conversations.crypto.sasl.SaslMechanism; import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.AuthenticationRequest; import im.conversations.android.xmpp.model.StreamElement; @XmlElement -public class Auth extends StreamElement { +public class Auth extends AuthenticationRequest { public Auth() { super(Auth.class); } + + @Override + public void setMechanism(final SaslMechanism mechanism) { + this.setAttribute("mechanism", mechanism.getMechanism()); + } } diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java index 7869bf010600d2525add357171a4f57601977db7..c709a779dd4614b9ec0f5e5dc2e6adf549004819 100644 --- a/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java @@ -1,12 +1,20 @@ package im.conversations.android.xmpp.model.sasl2; +import eu.siacs.conversations.crypto.sasl.SaslMechanism; +import eu.siacs.conversations.xmpp.XmppConnection; import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.AuthenticationRequest; import im.conversations.android.xmpp.model.StreamElement; @XmlElement -public class Authenticate extends StreamElement { +public class Authenticate extends AuthenticationRequest { public Authenticate() { super(Authenticate.class); } + + @Override + public void setMechanism(final SaslMechanism mechanism) { + this.setAttribute("mechanism", mechanism.getMechanism()); + } } From 4e09d5c44137033c64a56189b2931d9130e0f0dd Mon Sep 17 00:00:00 2001 From: codimp Date: Sun, 2 Jun 2024 15:29:05 +0000 Subject: [PATCH 094/192] Translated using Weblate (French) Currently translated at 100.0% (1020 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/fr/ --- src/main/res/values-fr/strings.xml | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index eb03186aa95fc6b20e6876b11e475abbac807b0c..f2b15b3ad733704e6399cd2d402954414b0c030c 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -5,7 +5,7 @@ Gérer le compte Détails du contact Détails du groupe - Détails du canal + Détails du salon Ajouter un compte Modifier le nom Ajouter au carnet d\'adresses @@ -14,8 +14,8 @@ Débloquer le contact Bloquer le domaine Débloquer le domaine - Bloquer le participant - Débloquer le participant + Bloquer le·a participant·e + Débloquer le·a participant·e Gestion des comptes Paramètres Choisir un contact @@ -35,11 +35,11 @@ Message chiffré avec OpenPGP Cet identifiant est déjà utilisé Identifiant non valide - Administrateur + Administrateur·ice Propriétaire - Modérateur - Participant - Visiteur + Modérateur·ice + Participant·e + Visiteur·ice Voulez-vous supprimer %s de votre liste de contacts ? Les conversations associées à ce contact ne seront pas supprimées. Voulez-vous bloquer %s pour l\'empêcher de vous envoyer des messages ? Voulez-vous débloquer %s et lui permettre de vous envoyer des messages ? @@ -95,15 +95,15 @@ Envoyer en clair Échec du déchiffrement. Avez-vous la bonne clé privée ? OpenKeychain - OpenKeychain pour chiffrer et déchiffrer les messages et pour gérer vos clés publiques.\n\nOpenKeychain est sous licence GPLv3 et est disponible sur F-Droid et Google Play.\n\n(Veuillez redémarrer %1$s après l\'installation de l\'application.)]]> + %1$s utilise <b>OpenKeychain</b> pour chiffrer et déchiffrer les messages et pour gérer vos clés publiques.<br><br>OpenKeychain est sous licence GPLv3 et est disponible sur F-Droid et Google Play.<br><br><small>(Veuillez redémarrer %1$s après l\'installation de l\'application.)</small> Redémarrer Installer Veuillez installer OpenKeychain Proposition… Patientez… - Aucune clé OpenPGP trouvée. + Aucune clé OpenPGP trouvée Impossible de chiffrer vos messages car votre contact n\'a pas communiqué sa clé publique.\n\nDemandez-lui de configurer OpenPGP. - Aucune clé OpenPGP n\'a été trouvée. + Aucune clé OpenPGP n\'a été trouvée Impossible de chiffrer votre message car vos contacts ne communiquent pas leur clé publique.\n\nDemandez-leur de configurer OpenPGP. Général Accepter les fichiers @@ -137,7 +137,7 @@ Demander les màj de disponibilité Choisir une image Prendre une photo - Accepter par avance les demandes de publication. + Accepter par avance les demandes de publication Le fichier choisi n\'est pas une image Impossible de convertir l\'image Impossible de trouver le fichier @@ -171,7 +171,7 @@ OMEMO Supprimer Désactiver temporairement - Publier un avatar + Publier une image de profil Publier la clé publique OpenPGP Supprimer la clé publique OpenPGP Êtes-vous sûr de vouloir supprimer votre clé publique OpenPGP de votre annonce de présence ?\nVos contacts ne pourront plus vous envoyer de message chiffrés avec OpenPGP. @@ -181,10 +181,10 @@ Enregistrer un son Adresse XMPP Bloquer l\'adresse XMPP - nom@exemple.com + nom@example.com Mot de passe Ce n\'est pas une adresse XMPP valide - Plus de mémoire disponible. L\'image est trop volumineuse. + Plus de mémoire disponible. L\'image est trop volumineuse Voulez-vous ajouter %s à votre carnet d\'adresses ? Infos sur le serveur XEP-0313 : MAM @@ -208,7 +208,7 @@ en ligne hier en ligne il y a %d jours Message chiffré. Veuillez installer OpenKeychain pour le déchiffrer. - Nouveaux messages chiffrés avec OpenPGP détectés. + Nouveaux messages chiffrés avec OpenPGP détectés ID de clé OpenPGP Empreinte OMEMO v\\Empreinte OMEMO @@ -228,12 +228,12 @@ Sélectionner Le contact existe déjà Rejoindre - canal@conference.example.com/surnom - canal@conference.example.com + salon@conference.example.com/surnom + salon@conference.example.com Enregistrer comme favori Supprimer le favori Détruire le groupe - Détruire le canal + Détruire le salon Voulez-vous vraiment détruire ce groupe ?\n\nAvertissement : le groupe sera complètement supprimé du serveur. Êtes-vous sûr de vouloir détruire ce canal public \? \n @@ -1062,7 +1062,7 @@ Couleurs systèmes (Material You) La découverte de canaux utilise un service de tierce partie appelé <a href=https://search.jabber.network>search.jabber.network</a>.<br><br> Utiliser cette fonctionnalité va transmettre votre adresse IP et votre recherche à ce service. Voir leur <a href=https://search.jabber.network/privacy>Politique de confidentialité</a> pour plus d\'informations. Archiver la conversation - Archiver cette conversation + Supprimer cette conversation ensuite Envoyer un message chiffré Conversation archivée Se rendre à la conversation From 17a65af9c85ab6c26a518f2998d7ee71f7b685b1 Mon Sep 17 00:00:00 2001 From: codimp Date: Sat, 1 Jun 2024 16:41:36 +0000 Subject: [PATCH 095/192] Translated using Weblate (French) Currently translated at 100.0% (13 of 13 strings) Translation: Conversations/Android App (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-conversations/fr/ --- src/conversations/res/values-fr/strings.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/conversations/res/values-fr/strings.xml b/src/conversations/res/values-fr/strings.xml index 68de25b54251a43abc609d88a7bb38fec262176c..730443ae6eaa2ae8422ec7da8a89d417db5bb4bd 100644 --- a/src/conversations/res/values-fr/strings.xml +++ b/src/conversations/res/values-fr/strings.xml @@ -3,7 +3,8 @@ Choisissez votre fournisseur XMPP Utiliser conversations.im Créer un nouveau compte - Avez-vous déjà un compte XMPP ? Cela peut être le cas si vous utilisez déjà un autre client XMPP ou si vous avez déjà utilisé Conversations auparavant. Sinon, vous pouvez créer un nouveau compte XMPP dès maintenant.\nRemarque : Certains fournisseurs de messagerie proposent également des comptes XMPP. + Avez-vous déjà un compte XMPP ? Cela peut être le cas si vous utilisez déjà un autre client XMPP ou si vous avez déjà utilisé Conversations auparavant. Sinon, vous pouvez créer un nouveau compte XMPP dès maintenant. +\nRemarque : Certains fournisseurs mail proposent également des comptes XMPP. XMPP est un réseau de messagerie instantanée indépendant du fournisseur. Vous pouvez utiliser cette application avec n’importe quel serveur XMPP de votre choix. \nToutefois, pour votre commodité, nous avons facilité la création d’un compte sur conversations.im ; un fournisseur spécialement conçu pour Conversations. Vous avez été invité à %1$s. Nous allons vous guider à travers le processus de création d’un compte.\nEn choisissant %1$s comme fournisseur, vous pourrez communiquer avec les utilisateurs des autres fournisseurs en leur donnant votre adresse XMPP complète. @@ -11,7 +12,7 @@ Votre invitation au serveur Code de provisionnement mal formaté Appuyez sur le bouton partager pour envoyer à votre contact une invitation pour %1$s. - Si vos contacts sont à proximité, ils peuvent aussi scanner le code ci-dessous pour accepter votre invitation. + Si votre contact se trouve près de vous, il peut aussi scanner le code ci-dessous pour accepter votre invitation. Rejoignez %1$s et discutez avec moi : %2$s Partager une invitation avec … \ No newline at end of file From dc859e8b5fbb96ee852becc6e8bb4fdde2c135bc Mon Sep 17 00:00:00 2001 From: codimp Date: Sat, 1 Jun 2024 16:06:00 +0000 Subject: [PATCH 096/192] Translated using Weblate (French) Currently translated at 100.0% (2 of 2 strings) Translation: Conversations/App Store Metadata (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata-conversations/fr/ --- .../android/fr-FR/full_description.txt | 21 ++++++++++--------- .../android/fr-FR/short_description.txt | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/conversations/fastlane/metadata/android/fr-FR/full_description.txt b/src/conversations/fastlane/metadata/android/fr-FR/full_description.txt index 653bdec1053be7fd5ae5efa4a9f60085f43be614..12cf31963abc0687e336ead7cf835a78ecb9ba88 100644 --- a/src/conversations/fastlane/metadata/android/fr-FR/full_description.txt +++ b/src/conversations/fastlane/metadata/android/fr-FR/full_description.txt @@ -1,4 +1,4 @@ -Facile à utiliser, fiable, respectueux de votre batterie. Prend en charge les images, les conversations de groupe et le chiffrement de bout-en-bout. +Facile à utiliser, fiable, respectueux de votre batterie. Prend en charge les images, les conversations de groupe et le chiffrement de bout en bout. Principes de conception : @@ -8,6 +8,7 @@ Principes de conception : * Nécessiter le moins de permissions possible Fonctionnalités : + * Chiffrement de bout-en-bout avec au choix, OMEMO ou OpenPGP * Envoi et réception d'images * Appels audio et vidéo chiffrés (DTLS-SRTP) @@ -27,12 +28,12 @@ Conversations fonctionne avec n'importe quel serveur XMPP. Cependant XMPP est un Ces XEP sont actuellement : -* XEP-0065: SOCKS5 Bytestreams (ou mod_proxy65). Sera utilisé pour transférer des fichiers si les deux correspondants sont derrière un pare-feu (NAT). -* XEP-0163: Personal Eventing Protocol pour les avatars -* XEP-0191: Blocking Command vous permet de mettre des spammeurs sur liste noire ou bloquer des contacts sans les retirer de vos contacts. -* XEP-0198: Stream Management permet à XMPP de survivre à des petites pannes de réseau et aux changements de la connexion TCP sous-jacente. -* XEP-0280: Message Carbons qui synchronise automatiquement les messages que vous envoyez à votre client de bureau et vous permet ainsi de passer sans heurt de votre client mobile à votre client de bureau et inversement dans une conversation. -* XEP-0237: Roster Versioning principalement pour économiser de la bande passante sur les connexions mobiles de mauvaise qualité. -* XEP-0313: Message Archive Management synchronise l'historique des messages avec le serveur. Retrouvez des messages qui ont été envoyés pendant que Conversations était hors ligne. -* XEP-0352: Client State Indication fait savoir au serveur si Conversations est ou n'est pas en arrière-plan. Permet au serveur d'économiser de la bande passante en différant des paquets non importants. -* XEP-0363: HTTP File Upload vous permet de partager des fichiers dans les conférences et avec des contacts hors-ligne. Nécessite un composant supplémentaire sur votre serveur. +* XEP-0065 : SOCKS5 Bytestreams (ou mod_proxy65). Sera utilisé pour transférer des fichiers si les deux correspondants sont derrière un pare-feu (NAT). +* XEP-0163 : Personal Eventing Protocol pour les avatars +* XEP-0191 : Blocking Command vous permet de mettre des spammeurs sur liste noire ou bloquer des contacts sans les retirer de vos contacts. +* XEP-0198 : Stream Management permet à XMPP de survivre à des petites pannes de réseau et aux changements de la connexion TCP sous-jacente. +* XEP-0280 : Message Carbons qui synchronise automatiquement les messages que vous envoyez à votre client de bureau et vous permet ainsi de passer sans heurt de votre client mobile à votre client de bureau et inversement dans une conversation. +* XEP-0237 : Roster Versioning principalement pour économiser de la bande passante sur les connexions mobiles de mauvaise qualité. +* XEP-0313 : Message Archive Management synchronise l'historique des messages avec le serveur. Retrouvez des messages qui ont été envoyés pendant que Conversations était hors ligne. +* XEP-0352 : Client State Indication fait savoir au serveur si Conversations est ou n'est pas en arrière-plan. Permet au serveur d'économiser de la bande passante en différant des paquets non importants. +* XEP-0363 : HTTP File Upload vous permet de partager des fichiers dans les conférences et avec des contacts hors-ligne. Nécessite un composant supplémentaire sur votre serveur. diff --git a/src/conversations/fastlane/metadata/android/fr-FR/short_description.txt b/src/conversations/fastlane/metadata/android/fr-FR/short_description.txt index b4ae66d63e11132d66b0742a0475b1c578f0b277..072c6ed1a866a42e0c34c3d4730bd9af5a63d4a3 100644 --- a/src/conversations/fastlane/metadata/android/fr-FR/short_description.txt +++ b/src/conversations/fastlane/metadata/android/fr-FR/short_description.txt @@ -1 +1 @@ -Messagerie instantanée XMPP chiffrée, facile à utiliser avec votre appareil mobile +Messagerie instantanée XMPP chiffrée et facile avec votre appareil mobile From ec49979e275337da7586e77d85976c26b2747d75 Mon Sep 17 00:00:00 2001 From: codimp Date: Sat, 1 Jun 2024 16:21:56 +0000 Subject: [PATCH 097/192] Translated using Weblate (French) Currently translated at 100.0% (2 of 2 strings) Translation: Conversations/App Store Metadata (Quicksy) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata-quicksy/fr/ --- .../metadata/android/fr-FR/full_description.txt | 14 ++++++++++++++ .../metadata/android/fr-FR/short_description.txt | 1 + 2 files changed, 15 insertions(+) create mode 100644 src/quicksy/fastlane/metadata/android/fr-FR/full_description.txt create mode 100644 src/quicksy/fastlane/metadata/android/fr-FR/short_description.txt diff --git a/src/quicksy/fastlane/metadata/android/fr-FR/full_description.txt b/src/quicksy/fastlane/metadata/android/fr-FR/full_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..c3ba436ff7b81423624f4305ae87944b7c7124ca --- /dev/null +++ b/src/quicksy/fastlane/metadata/android/fr-FR/full_description.txt @@ -0,0 +1,14 @@ +Quicksy est un spin-off du client populaire Jabber/XMPP Conversations avec découverte automatique des contacts. + +Vous vous inscrivez avec votre numéro de téléphone et Quicksy va automatiquement — en se basant sur les numéros de votre carnet de contacts — vous suggérer des contacts. + +Sous le capot Quicksy est un client Jabber à part entière qui vous permet de communiquer avec n'importe quel utilisateur·ice sur n'importe quel serveur public fédéré. De même, les utilisateur·ices de Quicksy peuvent être contactés de l'extérieur simplement en ajoutant +numéro-de-téléphone@quicksy.im à votre liste de contacts. + +Outre la synchronisation des contacts, l'interface utilisateur·ice est délibérément aussi proche que possible de Conversations. Cela permet aux utilisateur·ices de migrer éventuellement de Quicksy vers Conversations sans avoir à réapprendre le fonctionnement de l'application. + +Les contacts suggérés inclus d'autres utilisateur·ices Quicksy et des utilisateur·ices Jabber/XMPP classiques qui ont entré leur identifiant Jabber dans le répertoire Quicksy (https://quicksy.im/#get-listed). + +NOTE : Pour ajouter (https://quicksy.im/enter/) votre identifiant Jabber dans le répertoire +Quicksy des frais d'inscription uniques sont requis. + +Lisez la politique de confidentialité (https://quicksy.im/#privacy) pour plus d'information. diff --git a/src/quicksy/fastlane/metadata/android/fr-FR/short_description.txt b/src/quicksy/fastlane/metadata/android/fr-FR/short_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..8cd359341f41b3de294289a119ef2cb24bd94e30 --- /dev/null +++ b/src/quicksy/fastlane/metadata/android/fr-FR/short_description.txt @@ -0,0 +1 @@ +Entrée et découverte facile de Jabber / XMPP From 17423e270eb21fe10aa32fd8cabd66d8a2e49653 Mon Sep 17 00:00:00 2001 From: codimp Date: Sat, 1 Jun 2024 16:52:41 +0000 Subject: [PATCH 098/192] Translated using Weblate (French) Currently translated at 1.4% (1 of 67 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/fr/ --- fastlane/metadata/android/fr-FR/changelogs/349.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/fr-FR/changelogs/349.txt diff --git a/fastlane/metadata/android/fr-FR/changelogs/349.txt b/fastlane/metadata/android/fr-FR/changelogs/349.txt new file mode 100644 index 0000000000000000000000000000000000000000..fb657c547ab70b21eda75966ac9ae9ff3029a72e --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/349.txt @@ -0,0 +1,4 @@ +* Introduction d'un paramètre expert pour faire la découverte de salons sur le serveur local au lieu de search.jabber.network +* Active les coches de délivrance par défaut et supprimer le paramètre +* Active ‘Le bouton Envoyer indique l'état’ par défaut et supprimer le paramètre +* Déplacer les paramètres du service de sauvegarde et de premier plan vers l'écran principal From feebe197a4743afedbaee9a851969b92e2b65d12 Mon Sep 17 00:00:00 2001 From: codimp Date: Sun, 2 Jun 2024 23:00:29 +0000 Subject: [PATCH 099/192] Translated using Weblate (French) Currently translated at 100.0% (1020 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/fr/ --- src/main/res/values-fr/strings.xml | 178 ++++++++++++++--------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index f2b15b3ad733704e6399cd2d402954414b0c030c..0e9394621931509426854afae952386c3248d55f 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -235,11 +235,11 @@ Détruire le groupe Détruire le salon Voulez-vous vraiment détruire ce groupe ?\n\nAvertissement : le groupe sera complètement supprimé du serveur. - Êtes-vous sûr de vouloir détruire ce canal public \? -\n -\nAttention : Le canal sera totalement supprimé du serveur. + Êtes-vous sûr de vouloir détruire ce salon public ? +\n +\nAttention : Le salon sera totalement supprimé du serveur. Impossible de détruire le groupe - Impossible de détruire le canal + Impossible de détruire le salon Modifier le sujet du groupe Sujet Rejoindre le groupe… @@ -251,13 +251,13 @@ %1$s+%2$d autres ont tout lu jusqu\'ici Tout le monde a lu jusqu\'ici Publier - Appuyer sur l\'avatar pour choisir une image depuis la galerie + Appuyer sur l\'image de profil pour choisir une image depuis la galerie Mise à jour… Le serveur a rejeté votre publication Impossible de convertir votre image - Impossible de stocker l\'avatar sur le disque + Impossible de stocker l\'image de profil sur le disque (Un appui long réinitialise le paramètre) - Votre serveur ne supporte pas la publication d\'avatars + Votre serveur ne supporte pas la publication d\'image de profil chuchoté pour %s Envoyer un message privé à %s @@ -280,13 +280,13 @@ Correction des messages Permet à vos contacts d\'éditer leurs messages rétroactivement Paramètres avancés - À utiliser avec précaution. + À utiliser avec précaution À propos de %s Heures tranquilles Heure de début Heure de fin Activer les heures tranquilles - Les notifications seront muettes pendant les heures tranquilles. + Les notifications seront muettes pendant les heures tranquilles Autres Empreinte OMEMO copiée dans le presse-papier Vous êtes bannis de ce groupe @@ -298,7 +298,7 @@ avec le compte %s hébergé sur %s Vérification de %s sur l\'hôte HTTP - Vous n\'êtes pas connecté. Essayez plus tard. + Vous n\'êtes pas connecté. Essayez plus tard Vérification de la taille de %s Vérification de la taille de %1$s sur %2$s Options du message @@ -310,7 +310,7 @@ URL copiée dans le presse-papier Adresse XMPP copiée dans le presse-papiers Message d\'erreur copié dans le presse-papier - adresse internet + adresse web Scanner le QR code Montrer le QR code Afficher la liste des contacts bloqués @@ -318,7 +318,7 @@ Confirmer Réessayer Service au premier plan - Évite que le système ne ferme votre connexion. + Évite que le système ne ferme votre connexion Créer une sauvegarde La sauvegarde sera stockée dans %s Création des fichiers de sauvegarde @@ -344,13 +344,13 @@ Aucune application disponible pour ouvrir le lien Aucune application disponible pour afficher le contact Tags dynamiques - Afficher des tags en lecture seule en dessous des contacts. + Afficher des tags en lecture seule en dessous des contacts Activer les notifications Serveur de groupe non trouvé Impossible de créer le groupe - Avatar du compte + Image de profil du compte Copier l\'empreinte OMEMO dans le presse-papier - Régénérer l\'empreinte OMEMO + Régénérer la clé OMEMO Supprimer les appareils Êtes-vous sûr de vouloir supprimer les autres appareils de l\'annonce OMEMO ? Ils s\'annonceront de nouveau à leur prochaine connexion, mais ils peuvent ne pas recevoir les messages envoyés entre temps. Aucune clé utilisable n\'est disponible pour ce contact. \nImpossible d\'obtenir de nouvelles clés depuis le serveur. Pourrait-il y avoir un problème avec le serveur de votre contact ? @@ -372,26 +372,26 @@ Hors ligne Banni Membre - Mode expert + Mode avancé Accorder des privilèges aux membres Révoquer les privilèges des membres - Accorder des privilèges d\'administrateur - Révoquer des privilèges d\'administrateur + Accorder des privilèges d\'administrateur·ice + Révoquer des privilèges d\'administrateur·ice Accorder des privilèges de propriétaire Révoquer les privilèges du propriétaire Supprimer du groupe - Supprimer du canal + Supprimer du salon Impossible de changer l\'affiliation de %s Bannir du groupe - Bannir du canal - Vous essayez de supprimer %s d\'un canal public. La seule façon de le faire est de bannir cet utilisateur pour toujours. + Bannir du salon + Vous essayez de supprimer %s d\'un salon public. La seule façon de le faire est de bannir cet·te utilisateur·ice pour toujours. Bannir maintenant Impossible de changer le rôle de %s - Configuration du groupe - Configuration du canal public + Configuration du groupe privé + Configuration du salon public Privé, membres uniquement Rendre les adresses XMPP visibles à tout le monde - Rendre le canal modéré + Rendre le salon modéré Vous ne participez pas Options du groupe modifiées ! Impossible de modifier les options du groupe @@ -404,14 +404,14 @@ Touche Entrée pour envoyer Utilisez la touche Entrée pour envoyer un message. Vous pourrez toujours utiliser la combinaison Ctrl+Entrée pour envoyer un message, même si cette option est désactivée. Afficher la touche Entrée - Remplacer la touche Émoticônes par la touche Entrée. + Remplacer la touche Émoticônes par la touche Entrée audio vidéo image document PDF Application Android Contact - L\'avatar a été publié ! + L\'image de profil a été publiée ! %s en cours d\'envoi En train de proposer un(e) %s Cacher contacts hors-ligne @@ -426,11 +426,11 @@ Aucune application trouvée pour afficher le lieu Position Quitter le groupe privé - Quitte le canal public + Quitte le salon public Ne pas utiliser les CAs système - Tous les certificats doivent être approuvés manuellement. + Tous les certificats doivent être approuvés manuellement Retirer les certificats - Supprimer les certificats approuvés manuellement. + Supprimer les certificats approuvés manuellement Aucun certificat approuvé manuellement Retirer les certificats Supprimer la sélection @@ -485,7 +485,7 @@ Votre appareil ne supporte pas la sélection de certificats client ! Connexion Connexion via Tor - Rediriger toutes les connexions vers le réseau Tor. Nécessite Orbot. + Rediriger toutes les connexions vers le réseau Tor. Nécessite Orbot Nom d\'hôte Port Adresse du serveur (ou .onion) @@ -524,18 +524,18 @@ Ce champ est requis Corriger le message Envoyer le message corrigé - Vous avez déjà fait confiance à l\'empreinte de cette personne pour accorder votre confiance. En sélectionnant « Terminé », vous confirmez simplement que %s fait partie de ce groupe. + Vous avez déjà fait confiance à l\'empreinte de cette personne. En sélectionnant « Terminé », vous confirmez simplement que %s fait partie de ce groupe. Vous avez désactivé ce compte Erreur de sécurité : accès invalide au fichier ! Aucune application disponible pour partager l\'URI Partager l\'URI avec… Accepter et continuer Nous vous guiderons tout au long du processus de création d\'un compte sur conversations.im. -\nLorsque vous sélectionnerez conversations.im en tant que fournisseur, vous pourrez communiquer avec les utilisateurs d\'autres fournisseurs en leur fournissant votre adresse XMPP complète. +\nLorsque vous sélectionnerez conversations.im en tant que fournisseur, vous pourrez communiquer avec les utilisateur·ices d\'autres fournisseurs en leur fournissant votre adresse XMPP complète. Votre adresse XMPP complète sera : %s Créer un compte Utiliser votre propre fournisseur - Choisissez votre nom d\'utilisateur + Choisissez votre nom d\'utilisateur·ice Gérer l\'état de disponibilité manuellement Définir votre disponibilité lors de l\'édition de votre statut. Message de statut @@ -548,7 +548,7 @@ Les optimisations de batterie ne peuvent pas être désactivées sur votre appareil Échec de l\'inscription : Réessayer plus tard Échec de l\'inscription : le mot de passe n\'est pas assez fort - Choisir les participants + Choisir les participant·es Création d\'un groupe… Inviter à nouveau Désactiver @@ -568,7 +568,7 @@ Ordinateur Smartphone Tablette - Navigateur Internet + Navigateur web Console Paiement requis Autoriser à accéder à internet @@ -583,7 +583,7 @@ Effacer les identités OMEMO Régénérer vos clés OMEMO. Tous vos contacts devront vous vérifier à nouveau. À n\'utiliser qu\'en dernier recours. Supprimer les clés sélectionnées - Vous devez être connecté pour publier votre avatar. + Vous devez être connecté pour publier votre image de profil. Afficher le message d\'erreur Message d\'erreur Économiseur de données activé @@ -645,16 +645,16 @@ %d mois %d mois - Suppression messages auto + Suppression automatique des messages Efface automatiquement de cet appareil les messages plus anciens que l\'intervalle choisi. Chiffrement du message en cours Aucun message récupéré, en raison de la configuration de rétention de l\'appareil. Compression de la vidéo en cours Contact bloqué. - Notifications d\'inconnus - Notifier les messages et appels reçus d\'inconnus. - Message d\'un inconnu reçu - Bloquer l\'inconnu + Notifications d\'inconnu·es + Notifier les messages et appels reçus d\'inconnu·es. + Message d\'un·e inconnu·e reçu + Bloquer l\'inconnu·e Bloquer le domaine entier En ligne actuellement Nouvelle tentative de déchiffrement @@ -686,7 +686,7 @@ La lecture d\'un QR code nécessite l\'accès à l\'appareil photo Faire défiler l\'écran jusqu\'en bas Faire défiler l\'écran jusqu\'en bas après avoir envoyé un message - Modifier le message de l\'état + Modifier le message d\'état Modifier le message de statut Désactiver le chiffrement %1$s n\'est pas capable d\'envoyer un message chiffré à %2$s. Ceci peut être lié au fait que votre contact utilise un serveur obsolète ou un client qui ne gère par OMEMO. @@ -707,7 +707,7 @@ Le message n\'était pas chiffré pour cet appareil. Échec de déchiffrement du message OMEMO. annuler - Le partage de positionnement est désactivé. + Le partage de positionnement est désactivé Figer la position Débloquer la position Copier la position @@ -724,14 +724,14 @@ Voir la conversation Plugin de partage de localisation Utilisez le plugin Share Location à la place de la carte intégrée - Copier l\'adresse internet + Copier l\'adresse web Copier l\'adresse XMPP Partage de fichier HTTP pour S3 Recherche directe Lors de l\'ajout de conversations, afficher le clavier et placer le curseur sur le champ de recherche - Avatar du groupe - Le serveur ne prend pas en charge les avatars pour les groupes - Seul le propriétaire peut changer l\'avatar d\'un groupe + Image de profil du groupe + Le serveur ne prend pas en charge les images de profil pour les groupes + Seul le propriétaire peut changer l\'image de profil d\'un groupe Nom du contact Surnom Nom @@ -757,7 +757,7 @@ Importance, son, vibration Compression vidéo Voir les média - Participants + Participant·es Navigateur de média Fichier omis en raison d\'une violation de la sécurité. Qualité des vidéos @@ -799,10 +799,10 @@ Impossible d\'établir une connexion sécurisée. Impossible de trouver le serveur. Une erreur est survenue pendant le traitement de votre requête. - Entrée utilisateur incorrecte + Entrée utilisateur·ice incorrecte Temporairement indisponible. Réessayez plus tard. Pas de connexion réseau. - Veuillez réessayer dans%s + Veuillez réessayer dans %s Vous êtes à taux limité Trop de tentatives Vous utilisez une version obsolète de cette application. @@ -815,8 +815,8 @@ Rejeter la demande Installer Orbot Démarrer Orbot - Aucune application de marché installée. - Ce canal rendra votre adresse XMPP publique + Aucun magasin d\'applications installée. + Ce salon rendra votre adresse XMPP publique e-book Original (non compressé) Ouvrir avec… @@ -825,48 +825,48 @@ Restaurer la sauvegarde Restaurer Entrez votre mot de passe pour que le compte %s restaure la sauvegarde. - N\'utilisez pas la fonctionnalité de sauvegarde de la restauration pour tenter de cloner (exécuter simultanément) une installation. La restauration d’une sauvegarde ne concerne que les migrations ou en cas de perte du périphérique d’origine. + N\'utilisez pas la fonctionnalité de sauvegarde de la restauration pour tenter de cloner (exécuter simultanément) une installation. La restauration d’une sauvegarde ne concerne que les migrations ou en cas de perte de l\'appareil d’origine. Impossible de restaurer la sauvegarde. Impossible de déchiffrer la sauvegarde. Le mot de passe est-il correct ? Sauvegarde & restauration Entrez l\'adresse XMPP Créer un groupe - Rejoindre le canal public + Rejoindre le salon public Créer un groupe privé - Créer un canal public - Nom du canal + Créer un salon public + Nom du salon Adresse XMPP - Veuillez donner un nom au canal + Veuillez donner un nom au salon Veuillez renseigner une adresse XMPP Ceci est une adresse XMPP. Veuillez renseigner un nom. - Création d\'un canal public… - Ce canal existe déjà - Vous avez rejoint un canal existant - Impossible de sauvegarder la configuration du canal + Création d\'un salon public… + Ce salon existe déjà + Vous avez rejoint un salon existant + Impossible de sauvegarder la configuration du salon Autoriser quiconque à éditer le sujet Permettre à quiconque d\'inviter d\'autres personnes N\'importe qui peut éditer le sujet. Les propriétaires peuvent éditer le sujet. - Les administrateurs peuvent modifier le sujet. + Les administrateur·ices peuvent modifier le sujet. Les propriétaires peuvent inviter d\'autres personnes. N\'importe qui peut inviter d\'autres personnes. - Les adresses XMPP sont visibles par les administrateurs. + Les adresses XMPP sont visibles par les administrateur·ices. Les adresses XMPP sont visibles par tous. - Ce canal public n\'a pas de participants. Invitez vos contacts ou utilisez le bouton de partage pour distribuer son adresse XMPP. - Ce groupe privé n\'a aucun participant. + Ce salon public n\'a pas de participant·es. Invitez vos contacts ou utilisez le bouton de partage pour distribuer son adresse XMPP. + Ce groupe privé n\'a aucun·e participant·e. Gérer les privilèges - Rechercher des participants + Rechercher des participant·es Fichier trop volumineux Joindre - Découverte des canaux - Recherche des canaux + Découverte des salons + Recherche des salons Violation possible de la confidentialité ! J\'ai déjà un compte Ajouter un compte existant Enregistrer un nouveau compte Ceci ressemble à une adresse de domaine Ajouter quand même - Ceci ressemble à une adresse de canal + Ceci ressemble à une adresse de salon Partager les fichiers de sauvegardes Sauvegarder les conversations Événement @@ -875,13 +875,13 @@ Ce compte a déjà été configuré Veuillez saisir le mot de passe pour ce compte Impossible de réaliser cette action - Rejoindre le canal public… + Rejoindre le salon public… L\'application de partage n\'a pas accordé la permission d\'accéder à ce fichier. - + Salons et groupes de discussion jabber.network Serveur local - La plupart des utilisateurs devraient choisir « jabber.network » pour de meilleures suggestions provenant de l’écosystème public entier de XMPP. - Méthode de découverte des canaux + La plupart des utilisateur·ices devraient choisir « jabber.network » pour de meilleures suggestions provenant de l’écosystème public entier de XMPP. + Méthode de découverte des salons Sauvegarde À propos Veuillez activer votre compte @@ -926,8 +926,8 @@ Impossible de corriger le message Toutes les conversations Cette conversation - Votre avatar - Avatar pour %s + Votre image de profil + Image de profil pour %s Chiffré avec OMEMO Chiffré avec OpenPGP Non chiffré @@ -949,7 +949,7 @@ Échec lors de la livraison Plus d\'options Aucune application trouvée - Inviter à Conversations + Inviter sur Conversations Impossible de lire l\'invitation Le serveur ne prend pas en charge la génération d\'invitations Aucun compte actif ne prend en charge cette fonctionalité @@ -993,7 +993,7 @@ Synchroniser les favoris Activer \"Rejoindre automatiquement\" en entrant ou sortant d\'un groupe et réagir aux modifications apportées par d\'autres clients. graphique vectoriel - Appel entrant (%s) - %s + Appel entrant (%s) · %s Appel sortant · %s %1$d appel manqué de %2$s @@ -1006,7 +1006,7 @@ Impossible de supprimer le compte du serveur Document texte Échec temporaire de l\'authentification - Supprimer l\'avatar + Supprimer l\'image de profil Vous essayez d\'importer un format de fichier de sauvegarde obsolète Livre audio Distributeur UnifiedPush @@ -1014,7 +1014,7 @@ Signaler un spam Politique de confidentialité Quicksy vous demande votre consentement pour utiliser vos données - Signaler un spam et bloquer son auteur + Signaler un spam et bloquer son auteur·ice Déconnecté S\'identifier Vous vous êtes déconnecté⸱e de ce compte @@ -1032,11 +1032,11 @@ Thème clair/sombre Autoriser les captures d\'écran Chiffrement de bout en bout - Afficher le contenu de l\'application dans le sélecteur d\'applications et autorise les captures d\'écran + Afficher le contenu de l\'application dans le sélecteur d\'applications et autoriser les captures d\'écran Autorités de certification Faire confiance aux certificats de l\'autorité de certification du système - Oblige la liaison du canal - La liaison du canal peut permettre de détecter les attaques de l\'homme du milieu + Oblige la liaison du salon + La liaison du salon peut permettre de détecter les attaques de l\'homme du milieu Clavier Créer une sauvegarde ponctuelle Créer une sauvegarde récurrente @@ -1049,7 +1049,7 @@ Rejoignez la Conversation Chiffrement de bout en bout, confiance aveugle avant vérification, détection des attaques de l\'homme du milieu Système d\'exploitation - Opération pas encore supportée + Opération non supportée Notifications d\'utilisation En agissant en tant que distributeur UnifiedPush, la connexion XMPP persistante, fiable et économique en batterie sera utilisée pour communiquer avec d\'autres applications compatibles comme Tusky, Ltt.rs, FluffyChat et d\'autres encore. Notifications en plein écran @@ -1060,18 +1060,18 @@ Afficher un fond différent pour distinguer les messages envoyés et reçus Couleurs dynamiques Couleurs systèmes (Material You) - La découverte de canaux utilise un service de tierce partie appelé <a href=https://search.jabber.network>search.jabber.network</a>.<br><br> Utiliser cette fonctionnalité va transmettre votre adresse IP et votre recherche à ce service. Voir leur <a href=https://search.jabber.network/privacy>Politique de confidentialité</a> pour plus d\'informations. + La découverte de salons utilise un service tier appelé <a href=https://search.jabber.network>search.jabber.network</a>.<br><br> Utiliser cette fonctionnalité va transmettre votre adresse IP et votre recherche à ce service. Voir leur <a href=https://search.jabber.network/privacy>Politique de confidentialité</a> pour plus d\'informations. Archiver la conversation Supprimer cette conversation ensuite Envoyer un message chiffré Conversation archivée Se rendre à la conversation Votre contact utilise un appareil qui n\'est pas encore vérifié. Scannez son QR code pour effectuer une vérification et éviter une attaque de l\'homme du milieu. - Discuter + Lancer une discussion Aucun certificat client sélectionné ! Thème, couleurs, captures d\'écran, saisie Sécurité - Relai de notifications pour les applications de tierce partie compatibles avec UnifiedPush + Relai de notifications pour les applications tierces compatibles avec UnifiedPush Notifications Taille de fichier, compression des images, qualité des vidéos Période sans notifications, sonnerie, vibration, inconnus @@ -1082,12 +1082,12 @@ Connection au serveur Notifications d\'écriture, dernière connexion, disponibilité Nom d\'hôte et port, Tor - Nom d\'hôte et port, Tor et découverte des canaux + Nom d\'hôte et port, Tor et découverte des salons Application Interaction Sur l\'appareil - Invitations d\'inconnus - Accepter les invitations aux conversations de groupes provenant d\'inconnus + Invitations d\'inconnu·es + Accepter les invitations aux conversations de groupes provenant d\'inconnu·es Grande police Augmenter la taille de la police dans les bulles de message Autoriser les messages privés From 35049b6379c4583b9b867c5b72583156d08d514b Mon Sep 17 00:00:00 2001 From: nautilusx Date: Mon, 3 Jun 2024 16:57:55 +0000 Subject: [PATCH 100/192] Translated using Weblate (German) Currently translated at 100.0% (68 of 68 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/de/ --- fastlane/metadata/android/de-DE/changelogs/4211404.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/de-DE/changelogs/4211404.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/4211404.txt b/fastlane/metadata/android/de-DE/changelogs/4211404.txt new file mode 100644 index 0000000000000000000000000000000000000000..ecd13e4023f3c4ddd57d09e6355fef9579b7797e --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* Ältere Oppo-Geräte von der Anrufintegration ausgenommen +* Verschiedene Fehlerbehebungen From e1aa2d3d6d0bd580e68e1d65e6a3b39ceff3c957 Mon Sep 17 00:00:00 2001 From: rex07 Date: Mon, 3 Jun 2024 19:47:20 +0000 Subject: [PATCH 101/192] Translated using Weblate (Arabic) Currently translated at 100.0% (9 of 9 strings) Translation: Conversations/Android App (Quicksy) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-quicksy/ar/ --- src/quicksy/res/values-ar/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/quicksy/res/values-ar/strings.xml b/src/quicksy/res/values-ar/strings.xml index ddf1d878886f507bc4a6ff2f689edd64c7f7122b..cf44a653b2b27dfbd971ce8a51de059800c5ffdb 100644 --- a/src/quicksy/res/values-ar/strings.xml +++ b/src/quicksy/res/values-ar/strings.xml @@ -1,10 +1,10 @@ - مدى الوقت الذي يظل فيه Quicksy هادئًا بعد رؤية نشاط على جهاز آخر - عبر إرسال الأخطاء انت تقوم بالمساعدة في تطوير برمجة Quicksy + طول الفترة الزمنية التي يظل فيها Quicksy هادئًا بعد رؤية النشاط على جهاز آخر + بإرسال تتبعات المكدس، فإنك تساعد في التطوير المستمر لـ Quicksy إجعل كلّ جهات إتصالك تعلم أنك تستعمل كويكسي للمواصلة في إستقبال التنبيهات، حتى والشاشة مغلقة، يجب عليك أن تضيف Quicksy إلى قائمة التطبيقات المحميّة. - صورة حساب Quicksy + صورة الملف الشخصي بسرعة Quicksy إن كويكسي Quicksy غير متوفر في بلدكم. لا يمكن التأكد من خادم الهويّة. خطأ أمني مجهول. From f67aeadf10ec4264b2b311c93ac7db3f6591ac80 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Mon, 3 Jun 2024 19:52:04 +0000 Subject: [PATCH 102/192] Translated using Weblate (Ukrainian) Currently translated at 100.0% (68 of 68 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/uk/ --- fastlane/metadata/android/uk/changelogs/4211404.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/4211404.txt diff --git a/fastlane/metadata/android/uk/changelogs/4211404.txt b/fastlane/metadata/android/uk/changelogs/4211404.txt new file mode 100644 index 0000000000000000000000000000000000000000..ff1007cbfdabb52cee8a35187858db1707cf6da1 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* Виключення старих пристроїв Oppo з інтеграції викликів +* Виправлення різноманітних помилок From a901341f813e54b84f37eda36d9087ba5ff32b2b Mon Sep 17 00:00:00 2001 From: ariasuni Date: Mon, 3 Jun 2024 23:05:53 +0000 Subject: [PATCH 103/192] Translated using Weblate (French) Currently translated at 100.0% (1020 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/fr/ --- src/main/res/values-fr/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 0e9394621931509426854afae952386c3248d55f..6747989e624383156e328874844fb18258489f0d 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -21,7 +21,7 @@ Choisir un contact Choisir les contacts Partager via le compte - Bloquer la liste + Comptes bloqués À l\'instant Il y a 1 minute Il y a %d minutes @@ -313,7 +313,7 @@ adresse web Scanner le QR code Montrer le QR code - Afficher la liste des contacts bloqués + Afficher la liste des comptes bloqués Détails du compte Confirmer Réessayer From d2c36895265836f08b4e410fa3e8b054c1bab659 Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Tue, 4 Jun 2024 02:52:26 +0000 Subject: [PATCH 104/192] Translated using Weblate (Polish) Currently translated at 17.6% (12 of 68 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/pl/ --- fastlane/metadata/android/pl-PL/changelogs/4211404.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/pl-PL/changelogs/4211404.txt diff --git a/fastlane/metadata/android/pl-PL/changelogs/4211404.txt b/fastlane/metadata/android/pl-PL/changelogs/4211404.txt new file mode 100644 index 0000000000000000000000000000000000000000..916a4c5fe2c5fa2f15c08efab5319b3caac4a3c8 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* Wyłączenie starszych urządzeń Umidigi z integracji rozmów +* Różne poprawki błędów From b80361f1be43a230c7975a8810b79e60eafda539 Mon Sep 17 00:00:00 2001 From: ghose Date: Tue, 4 Jun 2024 07:18:37 +0000 Subject: [PATCH 105/192] Translated using Weblate (Galician) Currently translated at 58.8% (40 of 68 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/gl/ --- fastlane/metadata/android/gl-ES/changelogs/4211404.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/gl-ES/changelogs/4211404.txt diff --git a/fastlane/metadata/android/gl-ES/changelogs/4211404.txt b/fastlane/metadata/android/gl-ES/changelogs/4211404.txt new file mode 100644 index 0000000000000000000000000000000000000000..bae283ec9337d8033076e363dd03519ac65f330e --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* excluír da integración de chamadas aos dispositivos Oppo antigos +* arranxos varios From a8cd904ee44536862c4cf5116f1406a886973976 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Tue, 4 Jun 2024 00:20:39 +0000 Subject: [PATCH 106/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (68 of 68 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/zh_Hans/ --- fastlane/metadata/android/zh-CN/changelogs/4211404.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/zh-CN/changelogs/4211404.txt diff --git a/fastlane/metadata/android/zh-CN/changelogs/4211404.txt b/fastlane/metadata/android/zh-CN/changelogs/4211404.txt new file mode 100644 index 0000000000000000000000000000000000000000..aa59d1f2eb5ffcc3a9b218fa7640b8f92ad4eb3f --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* 从呼叫集成中排除较旧的 OPPO 设备 +* 各种错误修复 From 7160028ecf77722caa546dac355d322a0167cdc3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 4 Jun 2024 12:51:35 +0200 Subject: [PATCH 107/192] bump dependencies --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 04da16f852675eec4954300370554db7ea4a42ed..c1a22a73332ec8cdd447c58d424b0f1b6bfdc777 100644 --- a/build.gradle +++ b/build.gradle @@ -45,10 +45,10 @@ dependencies { exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' } conversationsPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2") - quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.0.2' + quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.1.0' implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1' implementation("com.github.CanHub:Android-Image-Cropper:2.0.0") - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.exifinterface:exifinterface:1.3.7' implementation 'androidx.cardview:cardview:1.0.0' implementation "androidx.preference:preference:1.2.1" From a1746a8e0660b252aa939d54b2940e8ee052fc66 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 4 Jun 2024 16:50:43 +0200 Subject: [PATCH 108/192] fix regression in handling own omemo devices --- .../conversations/crypto/axolotl/AxolotlService.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index b1ecda3c23f20a8ce309e8b29c64299531d9d132..ceb7ab48e82a51677488d48269b47248a67155fa 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -392,11 +392,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } Iq packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid()); mXmppConnectionService.sendIqPacket(account, packet, response -> { - if (packet.getType() == Iq.Type.TIMEOUT) { + if (response.getType() == Iq.Type.TIMEOUT) { Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids."); } else { //TODO consider calling registerDevices only after item-not-found to account for broken PEPs - final Element item = IqParser.getItem(packet); + final Element item = IqParser.getItem(response); final Set deviceIds = IqParser.deviceIds(item); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds); registerDevices(account.getJid().asBareJid(), deviceIds); @@ -744,7 +744,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } final SettableFuture future = SettableFuture.create(); final Iq packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId()); - mXmppConnectionService.sendIqPacket(account, packet, (response) -> { + mXmppConnectionService.sendIqPacket(account, packet, response -> { Pair verification = IqParser.verification(response); if (verification != null) { try { @@ -850,7 +850,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } } if (packet != null) { - mXmppConnectionService.sendIqPacket(account, packet, (response) -> { + mXmppConnectionService.sendIqPacket(account, packet, response -> { if (response.getType() == Iq.Type.RESULT) { fetchDeviceListStatus.put(jid, true); final Element item = IqParser.getItem(response); @@ -914,7 +914,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { final Jid jid = Jid.of(address.getName()); final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid()); final Iq bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId()); - mXmppConnectionService.sendIqPacket(account, bundlesPacket, (packet) -> { + mXmppConnectionService.sendIqPacket(account, bundlesPacket, packet -> { if (packet.getType() == Iq.Type.TIMEOUT) { fetchStatusMap.put(address, FetchStatus.TIMEOUT); sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout")); From 6275d0c536301e167b90fe1d0305534503c316c3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 4 Jun 2024 16:54:43 +0200 Subject: [PATCH 109/192] version bump to 2.16.4 + changelog --- CHANGELOG.md | 4 ++++ build.gradle | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80e79abe53a4d8060f9bc07eaaeb55ed830e03ea..5a1ab749dce83fb5d534e6405294b15cb6cef2be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### Version 2.16.4 + +* Fix minor regression introduced in 2.16.4 + ### Version 2.16.3 * exclude older Oppo devices from call integration diff --git a/build.gradle b/build.gradle index c1a22a73332ec8cdd447c58d424b0f1b6bfdc777..7168b748620b1a05c44284daa98b266a3a5dad39 100644 --- a/build.gradle +++ b/build.gradle @@ -104,8 +104,8 @@ android { defaultConfig { minSdkVersion 23 targetSdkVersion 34 - versionCode 42114 - versionName "2.16.3" + versionCode 42115 + versionName "2.16.4" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId From cb41b7182f9977cc129f3d9ce5e10e3d0a4086ce Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 5 Jun 2024 10:00:08 +0200 Subject: [PATCH 110/192] work with invalid nicks in bookmarks --- .../eu/siacs/conversations/entities/Bookmark.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java index 76c314a3e9d2e34418a3598711dd1788b81586bd..eb8f3328dd64c8d9f8240f011f69320b2f1d94f9 100644 --- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -159,8 +159,15 @@ public class Bookmark extends Element implements ListItem { } public Jid getFullJid() { - final String nick = getNick(); - return jid == null || nick == null || nick.trim().isEmpty() ? jid : jid.withResource(nick); + 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 From 961fdcb1c1569ed39babda894b08da029fa035f0 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 5 Jun 2024 10:00:27 +0200 Subject: [PATCH 111/192] null check inline bind features --- src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 38194d5cd9674932a2ad0dce1992c1fe3fa63093..1b5269b274d4e842182425ddb555146f35382e36 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -843,7 +843,8 @@ public class XmppConnection implements Runnable { account.getJid().asBareJid() + ": successfully enabled carbons (via Bind 2.0)"); features.carbonsEnabled = true; - } else if (loginInfo.inlineBindFeatures.contains(Namespace.CARBONS)) { + } else if (currentLoginInfo.inlineBindFeatures != null + && currentLoginInfo.inlineBindFeatures.contains(Namespace.CARBONS)) { negotiatedCarbons = true; Log.d( Config.LOGTAG, From d4b9b36077d49363a95847aaede18a8bd2500eaf Mon Sep 17 00:00:00 2001 From: random_r Date: Thu, 6 Jun 2024 10:18:40 +0000 Subject: [PATCH 112/192] Translated using Weblate (Italian) Currently translated at 100.0% (68 of 68 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/it/ --- fastlane/metadata/android/it-IT/changelogs/4211404.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/4211404.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/4211404.txt b/fastlane/metadata/android/it-IT/changelogs/4211404.txt new file mode 100644 index 0000000000000000000000000000000000000000..8f20152238235b044888b219380015a10bd00449 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* esclusione di dispositivi Oppo obsoleti dall'integrazione delle chiamate +* correzione di vari errori From 6150329df7a6222ee0d6fc002fad755cc3824389 Mon Sep 17 00:00:00 2001 From: 0ko <0ko@users.noreply.translate.codeberg.org> Date: Sun, 9 Jun 2024 04:53:30 +0000 Subject: [PATCH 113/192] Translated using Weblate (Russian) Currently translated at 97.0% (990 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/ru/ --- src/main/res/values-ru/strings.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index e3808038011e3442c4ed578da1a8267864a592ac..6691ddb5fedd6e3889ed7278a4a27e0817011c3c 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -1088,4 +1088,12 @@ Принимать приглашения в групповые беседы от незнакомцев Операция не поддерживается Полноэкранные уведомления + Повторяющиеся рез. копии + Разрешить приложению показывать экраны входящих звонков при заблокированном экране. + Conversations, будучи распределителем UnifiedPush, будет использовать стойкое, надёжное и малопотребляющее подключение XMPP для пробуждения других совместимых с UnifiedPush приложений, таких как Tusky, Ltt.rs, FluffyChat и др. + + Требовать привязку канала + Привязка канала может обнаружить некоторые атаки посредником + Соединение с сервером + Приглашения от незнакомцев \ No newline at end of file From 066d5b5d349994f8603dfb67dfcf9294945673ec Mon Sep 17 00:00:00 2001 From: Codeberg Translate Date: Mon, 10 Jun 2024 05:18:24 +0000 Subject: [PATCH 114/192] Update translation files Updated by "Remove blank strings" hook in Weblate. Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/ --- src/main/res/values-ru/strings.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 6691ddb5fedd6e3889ed7278a4a27e0817011c3c..910490c16e06d9305c7ac95a0b2f1916b019c3d2 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -1091,7 +1091,6 @@ Повторяющиеся рез. копии Разрешить приложению показывать экраны входящих звонков при заблокированном экране. Conversations, будучи распределителем UnifiedPush, будет использовать стойкое, надёжное и малопотребляющее подключение XMPP для пробуждения других совместимых с UnifiedPush приложений, таких как Tusky, Ltt.rs, FluffyChat и др. - Требовать привязку канала Привязка канала может обнаружить некоторые атаки посредником Соединение с сервером From 5eed02a477d522bc0d1eeeaac9b5638eaf6d453d Mon Sep 17 00:00:00 2001 From: ghose Date: Tue, 11 Jun 2024 19:29:52 +0000 Subject: [PATCH 115/192] Translated using Weblate (Galician) Currently translated at 100.0% (1020 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/gl/ --- src/main/res/values-gl/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index 657f6c8fd9975bba3e4d87f5028067e983f175dd..64cf92af05935b1509c91bf3004b3ec0d742a6f8 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -936,7 +936,7 @@ Volver á chamada activa Non se puido activar a cámara Fixar enriba - Desafixar de enriba + Non fixar enriba Ruta GPX No se pode correxir a mensaxe Todos os chats From 6aa4e08b7578c9b2cc8af75d1378d319692f0474 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Wed, 12 Jun 2024 13:40:18 +0000 Subject: [PATCH 116/192] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1020 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/uk/ --- src/main/res/values-uk/strings.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index de997fcb0f6c7dac85a4642b4a7054bfb28fd2cd..33e3cb4477923a5fc13798847401c8a64297880a 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -366,7 +366,7 @@ Увімкнути всі облікові записи Вимкнути всі облікові записи Здійснити дію з - Учасник + Без ролі Поза мережею Вигнанець Учасник @@ -379,7 +379,7 @@ Відкликати права власника Вилучити з групи Прибрати з каналу - Неможливо змінити статус участі %s + Неможливо змінити роль %s Заборонити доступ до групи Вилучити з каналу Ви намагаєтеся вилучити %s з публічного каналу. Єдиним способом для цього є заблокувати користувача назавжди. @@ -464,10 +464,10 @@ xmpp.example.com Вхід із сертифікатом Не вдалося розпізнати сертифікат - Налаштування збереження - Налаштування збереження на стороні сервера - Отримання налаштувань збереження. Будь ласка, зачекайте… - Не вдалося отримати налаштування збереження + Налаштування архівації + Налаштування архівації на стороні сервера + Отримання налаштувань архівації. Будь ласка, зачекайте… + Не вдалося отримати налаштування архівації Потрібно розв\'язати головоломку Уведіть текст із зображення вище Ланцюжок сертифікатів не довірений @@ -725,7 +725,7 @@ Ім\'я контакту Прізвисько Ім\'я - Назва не є обов\'язковою + Ім\'я вказувати необов\'язково Назва групи Цю групу закрито Неможливо зберегти запис @@ -878,8 +878,8 @@ Вхідний відеовиклик З\'єднання З\'єднано - Приймаю виклик - Завершую виклик + Приймання виклику + Завершення виклику Відповісти Відхилити Пошук пристроїв From 3f80a45997ad82dec9097a6556997a74f8a9ca01 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 17 Jun 2024 09:03:07 +0200 Subject: [PATCH 117/192] fix race condition when granting camera permission after request to switch to video --- .../eu/siacs/conversations/ui/RtpSessionActivity.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 1bb11c97391ece66e24a5d2bfc5683ba94dc8d34..1e68fae2e9017961fa0c203f76151a26ad85e99c 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -314,10 +314,15 @@ public class RtpSessionActivity extends XmppActivity private void acceptContentAdd() { try { - requireRtpConnection() - .acceptContentAdd(requireRtpConnection().getPendingContentAddition().summary); + final ContentAddition pendingContentAddition = + requireRtpConnection().getPendingContentAddition(); + if (pendingContentAddition == null) { + Log.d(Config.LOGTAG, "content offer was gone after granting permission"); + return; + } + requireRtpConnection().acceptContentAdd(pendingContentAddition.summary); } catch (final IllegalStateException e) { - Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show(); + Toast.makeText(this, Strings.nullToEmpty(e.getMessage()), Toast.LENGTH_SHORT).show(); } } From 94fc1b1a20ea8045c6a41374fae2a286949a9b77 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 27 Jun 2024 10:32:14 +0200 Subject: [PATCH 118/192] catch rare cases when ringtone activity is not found --- .../fragment/settings/NotificationsSettingsFragment.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/NotificationsSettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/NotificationsSettingsFragment.java index b354ebaff32a1cf06fde3b3da9d5b23ba83283f8..e0d2ced3c6e3b3cc5193d2ecc7b0720217ddfee9 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/NotificationsSettingsFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/NotificationsSettingsFragment.java @@ -175,7 +175,12 @@ public class NotificationsSettingsFragment extends XmppPreferenceFragment { uri = appSettings().getRingtone(); } Log.i(Config.LOGTAG, "current ringtone: " + uri); - this.pickRingtoneLauncher.launch(uri); + try { + this.pickRingtoneLauncher.launch(uri); + } catch (final ActivityNotFoundException e) { + Toast.makeText(requireActivity(), R.string.no_application_found, Toast.LENGTH_LONG) + .show(); + } } private AppSettings appSettings() { From 70abccee367512659257b4222f9e041fd8e4a961 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 27 Jun 2024 10:32:46 +0200 Subject: [PATCH 119/192] unify tone generator invocations --- .../services/CallIntegration.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegration.java b/src/main/java/eu/siacs/conversations/services/CallIntegration.java index 15bc3a9b28da00da58a03eebb4678530727db02c..adf03d0b7279d097ac76a54a9fb8dbecfa98b652 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegration.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegration.java @@ -392,9 +392,7 @@ public class CallIntegration extends Connection { public void success() { Log.d(Config.LOGTAG, "CallIntegration.success()"); - final var toneGenerator = - new ToneGenerator(AudioManager.STREAM_VOICE_CALL, DEFAULT_TONE_VOLUME); - toneGenerator.startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375); + startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375); this.destroyWithDelay(new DisconnectCause(DisconnectCause.LOCAL, null), 375); } @@ -409,9 +407,7 @@ public class CallIntegration extends Connection { public void error() { Log.d(Config.LOGTAG, "CallIntegration.error()"); - final var toneGenerator = - new ToneGenerator(AudioManager.STREAM_VOICE_CALL, DEFAULT_TONE_VOLUME); - toneGenerator.startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375); + startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375); this.destroyWithDelay(new DisconnectCause(DisconnectCause.ERROR, null), 375); } @@ -428,8 +424,7 @@ public class CallIntegration extends Connection { public void busy() { Log.d(Config.LOGTAG, "CallIntegration.busy()"); - final var toneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 80); - toneGenerator.startTone(ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500); + startTone(80, ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500); this.destroyWithDelay(new DisconnectCause(DisconnectCause.BUSY, null), 2500); } @@ -457,6 +452,17 @@ public class CallIntegration extends Connection { Log.d(Config.LOGTAG, "destroyed!"); } + private void startTone(final int volume, final int toneType, final int durationMs) { + final ToneGenerator toneGenerator; + try { + toneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, volume); + } catch (final RuntimeException e) { + Log.e(Config.LOGTAG, "could not initialize tone generator", e); + return; + } + toneGenerator.startTone(toneType, durationMs); + } + public static Uri address(final Jid contact) { return Uri.parse(String.format("xmpp:%s", contact.toEscapedString())); } From 7f5da72a5f41c6c4214530e0578638194d1a0354 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 27 Jun 2024 16:33:11 +0200 Subject: [PATCH 120/192] show archive icon for tar and 7z --- .../siacs/conversations/ui/adapter/MediaAdapter.java | 10 +++++++++- .../java/eu/siacs/conversations/utils/MimeUtils.java | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) 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 7b3a35ab3a70a12d1b708bc6322d99c27fb05974..0a84e53ca863328e2838f405e95bfb6ab8a3ed2e 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java @@ -44,6 +44,14 @@ public class MediaAdapter extends RecyclerView.Adapter ARCHIVE_MIMES = + Arrays.asList( + "application/x-7z-compressed", + "application/zip", + "application/rar", + "application/x-gtar", + "application/x-tar"); public static final List CODE_MIMES = Arrays.asList("text/html", "text/xml"); private final ArrayList attachments = new ArrayList<>(); @@ -94,7 +102,7 @@ public class MediaAdapter extends RecyclerView.Adapter Date: Thu, 27 Jun 2024 16:33:39 +0200 Subject: [PATCH 121/192] check that activity is still running when showing archiving dialog --- .../java/eu/siacs/conversations/ui/EditAccountActivity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 401995b9e0af713a2e2f94383d628300ada29fd2..aed19cf6ff59c8c003def262faf0a176fdc6247e 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -1389,7 +1389,9 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat prefs.setAttribute("default", defaults.get(choice.get())); xmppConnectionService.pushMamPreferences(mAccount, prefs); }); - builder.create().show(); + if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { + builder.create().show(); + } }); } From 4eee26e7d381f59a4402db30abbc640efeaf3980 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 2 Jul 2024 13:17:51 +0200 Subject: [PATCH 122/192] version bump to 2.16.5 + changelog --- CHANGELOG.md | 6 +++++- build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/4211604.txt | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/4211604.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a1ab749dce83fb5d534e6405294b15cb6cef2be..0ac556d368d700a25205316b925b0bdc67e98f2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ # Changelog +### Version 2.16.5 + +* Minor bug fixes + ### Version 2.16.4 -* Fix minor regression introduced in 2.16.4 +* Fix minor regression introduced in 2.16.3 ### Version 2.16.3 diff --git a/build.gradle b/build.gradle index 7168b748620b1a05c44284daa98b266a3a5dad39..2086c8c0274c66104e400be65c6693ecf2cd5b46 100644 --- a/build.gradle +++ b/build.gradle @@ -104,8 +104,8 @@ android { defaultConfig { minSdkVersion 23 targetSdkVersion 34 - versionCode 42115 - versionName "2.16.4" + versionCode 42116 + versionName "2.16.5" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/4211604.txt b/fastlane/metadata/android/en-US/changelogs/4211604.txt new file mode 100644 index 0000000000000000000000000000000000000000..3afc657f9176c2c66773ae36055c1eabcbe5b4e3 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4211604.txt @@ -0,0 +1 @@ +* Minor bug fixes From a7eb7baad2f562d416d1ea573579f00577a65f77 Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Wed, 3 Jul 2024 10:57:01 +0000 Subject: [PATCH 123/192] Translated using Weblate (Spanish) Currently translated at 100.0% (68 of 68 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/es/ --- fastlane/metadata/android/es-ES/changelogs/4211204.txt | 2 ++ fastlane/metadata/android/es-ES/changelogs/4211304.txt | 1 + fastlane/metadata/android/es-ES/changelogs/4211404.txt | 2 ++ 3 files changed, 5 insertions(+) create mode 100644 fastlane/metadata/android/es-ES/changelogs/4211204.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/4211304.txt create mode 100644 fastlane/metadata/android/es-ES/changelogs/4211404.txt diff --git a/fastlane/metadata/android/es-ES/changelogs/4211204.txt b/fastlane/metadata/android/es-ES/changelogs/4211204.txt new file mode 100644 index 0000000000000000000000000000000000000000..f75aa4665f8eb48bb84f4a7d84693ffe8497f415 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/4211204.txt @@ -0,0 +1,2 @@ +* La llamada ya no se silencia cuando se cambia el dispositivo de salida +* Dispositivos Umidigi excluidos de la integración de llamadas diff --git a/fastlane/metadata/android/es-ES/changelogs/4211304.txt b/fastlane/metadata/android/es-ES/changelogs/4211304.txt new file mode 100644 index 0000000000000000000000000000000000000000..60217885a4a490941c3a19890bad0c639aa8f59e --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/4211304.txt @@ -0,0 +1 @@ +* Ejecute la copia de seguridad como servicio en primer plano para que el proceso no se detenga después de 10 minutos diff --git a/fastlane/metadata/android/es-ES/changelogs/4211404.txt b/fastlane/metadata/android/es-ES/changelogs/4211404.txt new file mode 100644 index 0000000000000000000000000000000000000000..71fdf6502ac0cddeabfdf22452013dddf8179abe --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* excluir dispositivos Oppo antiguos de la integración de llamadas +* varios arreglos From 2943f4363e2ab50ae8e1d7e31717eccbd97e4d3c Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Fri, 5 Jul 2024 07:06:14 +0000 Subject: [PATCH 124/192] Translated using Weblate (Spanish) Currently translated at 100.0% (2 of 2 strings) Translation: Conversations/App Store Metadata (Quicksy) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata-quicksy/es/ --- .../fastlane/metadata/android/es-ES/short_description.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quicksy/fastlane/metadata/android/es-ES/short_description.txt b/src/quicksy/fastlane/metadata/android/es-ES/short_description.txt index e988bbf912ec17fa1d7d57b59c39b05c3f1e92b4..7e7ef394200c6e83bad6fd0dc731f53470570c82 100644 --- a/src/quicksy/fastlane/metadata/android/es-ES/short_description.txt +++ b/src/quicksy/fastlane/metadata/android/es-ES/short_description.txt @@ -1 +1 @@ -Jabber/XMPP fácil de ingresar y fácil de descubrir +Jabber/XMPP Fácil de usar y encuentra tus contactos From 487d51eaf26b3d5992b2c77e75e137bc6e29bb82 Mon Sep 17 00:00:00 2001 From: Stephan-P Date: Mon, 8 Jul 2024 06:25:10 +0000 Subject: [PATCH 125/192] Translated using Weblate (Dutch) Currently translated at 78.7% (803 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/nl/ --- src/main/res/values-nl/strings.xml | 104 +++++++++++++++++++++++------ 1 file changed, 85 insertions(+), 19 deletions(-) diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index ca3090319bdeb0fd2233a98ed0132c46d0100d36..2b0a8ea0abed8af6b4f3b387feccee86617b6198 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -39,7 +39,7 @@ Moderator Deelnemer Bezoeker - Wil je %s uit je contactenlijst verwijderen? De gesprekken met deze contactpersoon zullen niet worden verwijderd. + Wil je %s uit je contactenlijst verwijderen? De gesprekken met deze contactpersoon worden niet verwijderd. Wil je alle berichten van %s blokkeren? Wil je %s deblokkeren en er weer berichten van kunnen ontvangen? Alle contacten van %s blokkeren? @@ -66,8 +66,8 @@ Door crashrapportages via uw XMPP account te sturen help je de ontwikkeling van %1$s. Nu versturen Niet opnieuw vragen - Verbinding maken met account mislukt - Verbinden met meerdere accounts mislukt + Verbinding met account mislukt + Verbinding met meerdere accounts mislukt Tik hier op om accounts te beheren Bestand bijvoegen Wil je dit ontbrekende contact toevoegen aan je contactenlijst? @@ -125,7 +125,7 @@ Foto nemen Op voorhand toestemming verlenen voor abonneren Het bestand dat je gekozen hebt is geen afbeelding - Kon de afbeelding niet converteren + Kan afbeeldingsbestand niet converteren Bestand niet gevonden Algemene I/O-fout. Misschien is er geen opslagruimte meer beschikbaar? Onbekend @@ -232,8 +232,8 @@ Tik op avatar om een foto uit de galerij te kiezen Publiceren… De server weigerde de publicatie van je afbeelding - Kon de afbeelding niet converteren - Fout bij opslaan van avatar + Kan de afbeelding niet converteren + Kan avatar niet op schijf opslaan (Of hou lang ingedrukt om de oorspronkelijke terug te zetten) Je server ondersteunt de publicatie van avatars niet gefluisterd @@ -251,7 +251,7 @@ Nu aanvragen Negeren Beveiliging - Berichtcorrectie toestaan + Berichtcorrectie Sta je contacten toe hun berichten na het versturen te verbeteren Instellingen voor experts Wees voorzichtig met deze instellingen @@ -284,8 +284,8 @@ XMPP-adres gekopieerd naar klembord Foutmelding gekopieerd naar klembord webadres - 2D-streepjescode scannen - 2D-streepjescode tonen + QR-code scannen + QR-code weergeven Geblokkeerde contacten weergeven Accountgegevens Bevestigen @@ -324,7 +324,7 @@ Geen verdere geschiedenis op server Bijwerken… Wachtwoord gewijzigd! - Kon wachtwoord niet wijzigen + Kan wachtwoord niet wijzigen Wachtwoord wijzigen Huidig wachtwoord Nieuw wachtwoord @@ -345,11 +345,11 @@ Eigenaarsprivileges intrekken Verwijderen uit groepsgesprek Verwijderen uit kanaal - Kon aansluiting niet wijzigen + Kan de aansluiting van %s niet wijzigen Verbannen uit groepsgesprek Verbannen uit kanaal Nu verbannen - Kon rol van %s niet wijzigen + Kan rol van %s niet wijzigen Instellingen voor privégroep Instellingen voor openbaar kanaal Privé, enkel leden @@ -357,7 +357,7 @@ Kanaal modereren Je neemt geen deel Gespreksopties aangepast! - Kon gespreksopties niet aanpassen + Kan groepschatopties niet wijzigen Nooit Voor onbepaalde duur Sluimeren @@ -411,8 +411,8 @@ Dit is geen geldige gebruikersnaam Downloaden mislukt: server niet gevonden Downloaden mislukt: bestand niet gevonden - Downloaden mislukt: kon geen verbinding maken met host - Download mislukt: kon bestand niet schrijven + Downloaden mislukt: kan geen verbinding maken met host + Download mislukt: kan bestand niet schrijven Tor-netwerk niet beschikbaar Bindingsfout Gebroken @@ -581,7 +581,7 @@ Onbekend certificaat aanvaarden? Het servercertificaat is niet ondertekend door een gekende certificaatautoriteit. Verkeerde servernaam aanvaarden? - De server kon niet authenticeren als ‘%s’. Het certificaat is enkel geldig voor: + Kan de server niet verifiëren als ‘%s’. Het certificaat is alleen geldig voor: Wil je toch verbinding maken? Certificaatgegevens: Eenmalig @@ -765,7 +765,7 @@ Je microfoon is niet beschikbaar Je kunt slechts één gesprek tegelijk voeren. Terug naar lopend gesprek - Kon camera niet wisselen + Kan camera niet wisselen Bovenaan vastzetten Bovenaan losmaken Kon bericht niet corrigeren @@ -782,8 +782,8 @@ Bekijk %1$d deelnemers - Een bericht kon niet worden afgeleverd - Sommige berichten konden niet worden afgeleverd + Een bericht kan niet worden afgeleverd + Sommige berichten kunnen niet worden afgeleverd Mislukte afleveringen Meer opties @@ -800,4 +800,70 @@ Chat archiveren Kan niet verbinden met OpenKeychain Chat starten + Kan back-up niet herstellen. + Kan back-up niet decoderen. Is het wachtwoord correct? + Incompatibele client + Uitgelogd + Stuur crashrapporten + Kan certificaat niet verwerken + Kan archiveringsvoorkeuren niet ophalen + Kan oproep niet verbinden + Wil je de bladwijzer voor %s verwijderen en de gesprekken archiveren? + Waarschuwing: Dit verzenden zonder updates van wederzijdse aanwezigheid kan onverwachte problemen veroorzaken. +\n +\nGa naar “Contactgegevens ” om je aanwezigheidsabonnementen te verifiëren. + Bladwijzers synchroniseren + Stel de vlag “autojoin ” in bij het invoeren of verlaten van een MUC en reageer op wijzigingen die door andere clients zijn aangebracht. + Klaar om bestand te delen + Bestand verwijderd + Kan geen tijdelijk bestand aanmaken + Delen met… + Chat daarna verwijderen + Wil je de bladwijzer voor %s verwijderen? + %1$s gebruikt <b>OpenKeychain</b> om berichten te versleutelen en te decoderen en het beheer van je openbare sleutels. <br>< br>Het is gelicentieerd onder GPLv3+ en beschikbaar op F-Droid en Google Play<br><br><small>(Start %1$s hierna opnieuw.)</small> + Kan het bericht niet versleutelen omdat de contactpersoon hun openbare sleutel niet aankondigt. +\n +\nVraag de contactpersoon om OpenPGP in te stellen. + Nieuwe chat + Kan geen groepschat aanmaken + Kan coderingssleutels niet ophalen + Weet je zeker dat je je account wilt verwijderen? Als je je account verwijdert, wordt je hele chatgeschiedenis gewist + Kan deze actie niet uitvoeren + Wil je alle berichten in deze chat verwijderen? +\n +\nWaarschuwing: Dit heeft geen invloed op berichten die zijn opgeslagen op andere apparaten of servers. + Kan opname niet starten + Kan opname niet opslaan + Kan kanaalconfiguratie niet opslaan + Kan het bericht niet versleutelen omdat de contactpersonen hun openbare sleutel niet aankondigen. +\n +\nVraag de hen om OpenPGP in te stellen. + Kan account niet bijwerken + Kan apparaatlijst niet ophalen + Kan geen verbinding maken met de server. + Kan account niet van server verwijderen + kan het bestand niet delen + Er zijn geen bruikbare sleutels beschikbaar voor deze contactpersoon. +\nKan geen nieuwe sleutels van de server halen. Misschien is er iets mis met de server van de contactpersoon? + De app die je gebruikte om deze afbeelding te selecteren, bood niet genoeg rechten om het bestand te lezen. +\n +\nGebruik een andere bestandsbeheerder om een afbeelding te kiezen. + Vraag eerst aanwezigheidsupdates aan bij je contactpersoon. +\n +\nDit wordt gebruikt om te bepalen welke chat-app jouw contactpersoon gebruikt. + Verstuur versleuteld bericht + Kennisgevingsgeluid + Kennisgevingsgeluid voor nieuwe berichten + Ringtone voor inkomende oproepen + De tijdsduurmeldingen worden gedempt na het detecteren van activiteit op een van je andere apparaten. + App-inhoud verbergen in de app-schakelaar en schermopnames blokkeren + De app die je gebruikte om dit bestand te delen, bood onvoldoende rechten. + Aanmelding wordt niet ondersteund door server + XEP-0215: External Service Discovery + Versleuteld bericht. Installeer OpenKeychain om het te decoderen. + Groepsberichten + Je hebt deze groepschat verlaten om technische redenen + gehost op %s + Kan server niet vinden. + Kan geen veilige verbinding tot stand brengen. \ No newline at end of file From ac49954b350a213247819f5232153cd3f3d0fd2e Mon Sep 17 00:00:00 2001 From: nautilusx Date: Mon, 8 Jul 2024 18:32:45 +0000 Subject: [PATCH 126/192] Translated using Weblate (German) Currently translated at 100.0% (69 of 69 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/4211604.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/de-DE/changelogs/4211604.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/4211604.txt b/fastlane/metadata/android/de-DE/changelogs/4211604.txt new file mode 100644 index 0000000000000000000000000000000000000000..a00810e4f6e08b6d44a747d22e22fe4cd4ea8f83 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4211604.txt @@ -0,0 +1 @@ +* Kleinere Fehlerbehebungen From 634ec3e00b3d298567ca4f7377e47ac45a3bba67 Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Mon, 8 Jul 2024 05:15:14 +0000 Subject: [PATCH 127/192] Translated using Weblate (Polish) Currently translated at 18.8% (13 of 69 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/4211604.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/pl-PL/changelogs/4211604.txt diff --git a/fastlane/metadata/android/pl-PL/changelogs/4211604.txt b/fastlane/metadata/android/pl-PL/changelogs/4211604.txt new file mode 100644 index 0000000000000000000000000000000000000000..2f9a6e84501c3df605cbf3d649485b071f507f42 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/4211604.txt @@ -0,0 +1 @@ +* Mniejsze poprawki błędów From 93c53455ed22486166751b095bc09a6f8fe2a479 Mon Sep 17 00:00:00 2001 From: Besnik_b Date: Sun, 7 Jul 2024 21:59:00 +0000 Subject: [PATCH 128/192] Translated using Weblate (Albanian) Currently translated at 100.0% (69 of 69 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/sq/ --- fastlane/metadata/android/sq/changelogs/4211404.txt | 2 ++ fastlane/metadata/android/sq/changelogs/4211604.txt | 1 + 2 files changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/sq/changelogs/4211404.txt create mode 100644 fastlane/metadata/android/sq/changelogs/4211604.txt diff --git a/fastlane/metadata/android/sq/changelogs/4211404.txt b/fastlane/metadata/android/sq/changelogs/4211404.txt new file mode 100644 index 0000000000000000000000000000000000000000..6baac3ca45d268356aea58c85ab153595bea33e3 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/4211404.txt @@ -0,0 +1,2 @@ +* përjashtim pajisjesh të vjetra Oppo prej integrim thirrjesh +* ndreqje të metash të ndryshme diff --git a/fastlane/metadata/android/sq/changelogs/4211604.txt b/fastlane/metadata/android/sq/changelogs/4211604.txt new file mode 100644 index 0000000000000000000000000000000000000000..fda6c4ac95a94d1737c92aeb9c2fe69cab7fdc8a --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/4211604.txt @@ -0,0 +1 @@ +* Ndreqje të metash të vockla From d270ec88f8f8e7e444c762f4768700f99067fbff Mon Sep 17 00:00:00 2001 From: ghose Date: Mon, 8 Jul 2024 05:57:46 +0000 Subject: [PATCH 129/192] Translated using Weblate (Galician) Currently translated at 59.4% (41 of 69 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/4211604.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/gl-ES/changelogs/4211604.txt diff --git a/fastlane/metadata/android/gl-ES/changelogs/4211604.txt b/fastlane/metadata/android/gl-ES/changelogs/4211604.txt new file mode 100644 index 0000000000000000000000000000000000000000..ef27ca90434811b561de96e3e74bae86693aa06e --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/4211604.txt @@ -0,0 +1 @@ +* Arranxo de problemas menores From 63cbdfcfcf9ecb096ee7299be2b98b674d385471 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Mon, 8 Jul 2024 05:57:39 +0000 Subject: [PATCH 130/192] Translated using Weblate (Ukrainian) Currently translated at 100.0% (69 of 69 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/uk/ --- fastlane/metadata/android/uk/changelogs/4211604.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/uk/changelogs/4211604.txt diff --git a/fastlane/metadata/android/uk/changelogs/4211604.txt b/fastlane/metadata/android/uk/changelogs/4211604.txt new file mode 100644 index 0000000000000000000000000000000000000000..5055677c66cce09d060052a9941d17aecf3e6621 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/4211604.txt @@ -0,0 +1 @@ +* Незначні виправлення помилок From 3618d38b5bfdb5cae46783de017f7bdf76bac9b4 Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Mon, 8 Jul 2024 19:31:07 +0000 Subject: [PATCH 131/192] Translated using Weblate (Spanish) Currently translated at 100.0% (69 of 69 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/4211604.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/es-ES/changelogs/4211604.txt diff --git a/fastlane/metadata/android/es-ES/changelogs/4211604.txt b/fastlane/metadata/android/es-ES/changelogs/4211604.txt new file mode 100644 index 0000000000000000000000000000000000000000..288dce67f8be1df87c8ad3c10e93a1cfa6237d7a --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/4211604.txt @@ -0,0 +1 @@ +* Correcciones de errores menores From 638639642af7ff13e4c15d509dafb37772fe493f Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Mon, 8 Jul 2024 01:06:18 +0000 Subject: [PATCH 132/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (69 of 69 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/4211604.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/zh-CN/changelogs/4211604.txt diff --git a/fastlane/metadata/android/zh-CN/changelogs/4211604.txt b/fastlane/metadata/android/zh-CN/changelogs/4211604.txt new file mode 100644 index 0000000000000000000000000000000000000000..9d049011e09872903f007ac9248a9b5ab7f131e1 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/4211604.txt @@ -0,0 +1 @@ +* 小错误修复 From 6953f51f5e98f0915151eb8ceeeb139b7a01a5dc Mon Sep 17 00:00:00 2001 From: Stephan-P Date: Mon, 8 Jul 2024 04:56:37 +0000 Subject: [PATCH 133/192] Translated using Weblate (Dutch) Currently translated at 100.0% (2 of 2 strings) Translation: Conversations/App Store Metadata (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata-conversations/nl/ --- .../android/nl-NL/full_description.txt | 39 +++++++++++++++++++ .../android/nl-NL/short_description.txt | 1 + 2 files changed, 40 insertions(+) create mode 100644 src/conversations/fastlane/metadata/android/nl-NL/full_description.txt create mode 100644 src/conversations/fastlane/metadata/android/nl-NL/short_description.txt diff --git a/src/conversations/fastlane/metadata/android/nl-NL/full_description.txt b/src/conversations/fastlane/metadata/android/nl-NL/full_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..3d12812f9cb3e361709c6a42382032b4e8752877 --- /dev/null +++ b/src/conversations/fastlane/metadata/android/nl-NL/full_description.txt @@ -0,0 +1,39 @@ +Eenvoudig te gebruiken, betrouwbaar, batterijvriendelijk. Met ingebouwde ondersteuning voor afbeeldingen, groepschats en e2e-codering. + +Ontwerpprincipes: + +* Fraai van uiterlijk en gemakkelijk te gebruiken zonder opoffering van de veiligheid of privacy +* Gebaseerd op bestaande, gevestigde protocollen +* Vereist geen Google-account of specifiek Google Cloud Messaging (GCM) +* Verlangt een minimum aan rechten + +Functies: + +* End-to-end-codering met OMEMO of OpenPGP +* Afbeeldingen verzenden en ontvangen +* Versleutelde audio- en videogesprekken (DTLS-SRTP) +* Intuïtieve gebruikersinterface volgens de richtlijnen van Android Design +* Afbeeldingen / avatars voor jouw contacten +* Synchronisatie met desktopclient +* Conferenties (met ondersteuning voor bladwijzers) +* Adresboekintegratie +* Meerdere accounts / uniform Postvak In +* Zeer lage impact op de levensduur van de batterij + +Conversations maakt het heel gemakkelijk om een account aan te maken op de gratis conversations.im-server. Conversations werkt daarbij ook met elke andere XMPP-server. Veel XMPP-servers worden beheerd door vrijwilligers en zijn gratis. + +XMPP-functies: + +Conversations werkt met elke bestaande XMPP-server. XMPP is echter een uitbreidbaar protocol. Deze extensies zijn ook gestandaardiseerd in zogenaamde XEP's. Conversations ondersteunt een aantal daarvan om de algehele gebruikerservaring te verbeteren. De kans bestaat dat jouw huidige XMPP-server deze extensies niet ondersteunt. Om het meeste uit gesprekken te halen, kun je overwegen om over te schakelen naar een XMPP-server die dat wel doet of - nog beter - je eigen XMPP-server voor jou en je vrienden te gebruiken. + +Deze XEP's zijn - vanaf nu: + +* XEP-0065: SOCKS5 Bytestreams (or mod_proxy65). Wordt gebruikt om bestanden over te dragen als beide partijen achter een firewall zitten (NAT). +* XEP-0163: Personal Eventing Protocol for avatars +* XEP-0191: Blocking command laat je spammers op de zwarte lijst zetten of contacten blokkeren zonder ze uit je selectie te verwijderen. +* XEP-0198: Stream Management stelt XMPP in staat om kleine netwerkuitval en veranderingen van de onderliggende TCP-verbinding te overleven. +* XEP-0280: Message Carbons synchroniseert automatisch de berichten die je naar je desktopclient verzendt en stelt je zo in staat om, binnen één gesprek, naadloos over te schakelen van je mobiele client naar je desktopclient en terug. +* XEP-0237: Roster Versioning om bandbreedte te besparen op slechte mobiele verbindingen +* XEP-0313: Message Archive Management synchronisatie van berichtgeschiedenis met de server. Verzamelt berichten die zijn verzonden terwijl Conversations offline was. +* XEP-0352: Client State Indication laat de server weten of Conversations al dan niet op de achtergrond actief is. Hiermee kan de server bandbreedte besparen door onbelangrijke pakketten achter te houden. +* XEP-0363: HTTP File Upload stelt je in staat om bestanden te delen tijdens conferenties en met offline contacten. Vereist een extra component op je server. diff --git a/src/conversations/fastlane/metadata/android/nl-NL/short_description.txt b/src/conversations/fastlane/metadata/android/nl-NL/short_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..76a5046f52a53a514a4c0198684bf85bd741a21c --- /dev/null +++ b/src/conversations/fastlane/metadata/android/nl-NL/short_description.txt @@ -0,0 +1 @@ +Versleutelde, gebruiksvriendelijke XMPP messenger voor je mobiele apparaat From 6c68337f88e171d621c86fe6d9ebbcffb5d0a1f9 Mon Sep 17 00:00:00 2001 From: Stephan-P Date: Tue, 9 Jul 2024 20:31:24 +0000 Subject: [PATCH 134/192] Translated using Weblate (Dutch) Currently translated at 81.4% (831 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/nl/ --- src/main/res/values-nl/strings.xml | 31 ++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index 2b0a8ea0abed8af6b4f3b387feccee86617b6198..fe5e74f73d1b7aaa42cf5942f42e89e8a9ad924b 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -418,8 +418,8 @@ Gebroken Aanwezigheid Trillen behandelen als stille modus - Uitgebreide verbindingsinstellingen - Toon hostnaam- en poortinstellingen bij instellen van een account + Hostname & poort + Uitgebreide verbindingsinstellingen weergeven bij het instellen van een account xmpp.voorbeeld.be Archiefvoorkeuren Voorkeuren voor archief aan serverzijde @@ -866,4 +866,31 @@ gehost op %s Kan server niet vinden. Kan geen veilige verbinding tot stand brengen. + Knop “Verzenden” vervangen door snelle actie + %1$s heeft de groepschat verlaten + Groepschats doorzoeken + CAPTCHA vereist + Onvertrouwde certificaatketen + Geen app om koppeling te openen + Geen app om contact te bekijken + vector-afbeelding + multimediabestand + Audioboek + Download mislukt: Ongeldig bestand + Afwezig wanneer het apparaat is vergrendeld + Afwezig melden als het apparaat vergrendeld is + Bezet melden als het apparaat in stille modus is + Tekst gedeeld met %s + Er zijn geen bruikbare sleutels beschikbaar voor dit contact. +\nZorg ervoor dat jullie allebei een aanwezigheidsabonnement hebben. + Je probeert %s te verwijderen van een publiek kanaal. De enige manier om dat te doen, is door die gebruiker voor altijd te verbannen. + Bestand gedeeld met %s + Afbeelding gedeeld met %s + Afbeeldingen gedeeld met %s + Server- of .onion-adres + Gebruik de Enter-toets om een bericht te verzenden. Je kunt altijd Ctrl + Enter gebruiken om een bericht te verzenden, zelfs als deze optie is uitgeschakeld. + Bezet melden als het apparaat op trillen staat + Weet je zeker dat je alle andere apparaten uit de OMEMO-aankondiging wilt verwijderen? De volgende keer dat je apparaten verbinding maken, zullen ze zichzelf opnieuw aankondigen, maar ontvangen ze mogelijk geen berichten die ondertussen zijn verzonden. + De server is niet verantwoordelijk voor dit domein + Chat gearchiveerd \ No newline at end of file From 4f33c99c2f4475634222e6bbc2e1f9c50dd3dcb3 Mon Sep 17 00:00:00 2001 From: Stephan-P Date: Wed, 10 Jul 2024 05:01:53 +0000 Subject: [PATCH 135/192] Translated using Weblate (Dutch) Currently translated at 88.0% (898 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/nl/ --- src/main/res/values-nl/strings.xml | 74 +++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index fe5e74f73d1b7aaa42cf5942f42e89e8a9ad924b..8970360db083a95892149b07de2a9ed619ce554b 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -443,7 +443,7 @@ %d berichten Laad meer berichten - Synchroniseer met contacten + Integratie met contactenlijst Melding bij alle berichten Melding enkel wanneer aangesproken Meldingen uitgeschakeld @@ -516,7 +516,7 @@ Delen als HTTP-link Blindelings vertrouwen vóór verificatie Onvertrouwd - Ongeldige 2D-streepjescode + Ongeldige QR-code Cache wissen Privéopslag wissen Privéopslag waar bestanden worden bijgehouden wissen (de bestanden kunnen opnieuw gedownload worden van de server) @@ -598,7 +598,7 @@ Ontwerp: OMEMO-versleuteling OMEMO zal altijd gebruikt worden voor één-op-één- privégroepsgesprekken. - OMEMO zal standaard gebruikt worden voor nieuwe gesprekken. + OMEMO wordt standaard gebruikt voor nieuwe gesprekken. OMEMO zal uitdrukkelijk ingeschakeld moeten worden voor nieuwe gesprekken. Snelkoppeling aanmaken Standaard aan @@ -625,7 +625,7 @@ XMPP-adres kopiëren Bestanden delen via HTTP voor S3 Rechtstreeks zoeken - Open het toetsenbord op het scherm ‘Gesprek starten’ en plaats de cursor in het zoekveld + Open op het scherm \'Nieuwe chat\' het toetsenbord en plaats de cursor in het zoekveld Gespreksafbeelding Host ondersteunt geen gespreksafbeeldingen Enkel de eigenaar kan de gespreksafbeelding wijzigen @@ -668,11 +668,11 @@ %s verifiëren %s.]]> We hebben je nóg een sms gestuurd met 6-cijferige code. - Voer de 6-cijferige code hieronder in. + Voer hieronder de 6-cijferige code in. Sms opnieuw versturen Sms opnieuw versturen (%s) Even geduld (%s) - terug + Terug Mogelijke pincode is automatisch van het klembord geplakt. Voer je 6-cijferige code in. Weet je zeker dat je de registratieprocedure wilt stopzetten? @@ -893,4 +893,66 @@ Weet je zeker dat je alle andere apparaten uit de OMEMO-aankondiging wilt verwijderen? De volgende keer dat je apparaten verbinding maken, zullen ze zichzelf opnieuw aankondigen, maar ontvangen ze mogelijk geen berichten die ondertussen zijn verzonden. De server is niet verantwoordelijk voor dit domein Chat gearchiveerd + Geen toestemming om te bellen + Je hebt de vingerafdruk van deze persoon al vertrouwd. Door \"Gereed\" te selecteren, bevestig je alleen dat %s deel uitmaakt van deze groepschat. + Doe mee aan het gesprek + Welkom bij Quicksy! + Akkoord en doorgaan + Quicksy vraagt toestemming om je gegevens te gebruiken + Bezet in stille modus + Verleen %1$s toegang tot externe opslag + Verleen %1$s toegang tot de camera + Tip: Gebruik \'Bestand kiezen\' in plaats van \'Afbeelding kiezen\' om afzonderlijke afbeeldingen ongecomprimeerd te verzenden, ongeacht deze instelling. + Jouw apparaat maakt gebruik van zware batterij-optimalisaties voor %1$s, wat kan leiden tot vertraagde meldingen of zelfs berichtverlies. +\nHet wordt aanbevolen om deze uit te schakelen. + Jouw apparaat maakt gebruik van zware batterij-optimalisaties voor %1$s, wat kan leiden tot vertraagde meldingen of zelfs berichtverlies. +\n +\nJe wordt nu gevraagd om deze uit te schakelen. + Je bent afgemeld bij dit account + Beveiligingsfout: Ongeldige bestandstoegang! + Er is geen app om URI te delen + Laat je contacten zien wanneer je de app voor het laatst hebt gebruikt + Licht + Donker + Toestemming verlenen om internet te gebruiken + Meld dit XMPP-adres voor spammen. + Je OMEMO-sleutels opnieuw genereren. Al je contacten zullen je opnieuw moeten verifiëren. Gebruik dit alleen als laatste redmiddel. + Jouw besturingssysteem verhindert %1$s om toegang te krijgen tot internet op de achtergrond. Om meldingen van nieuwe berichten te ontvangen, moet je %1$s onbeperkte toegang toestaan wanneer \"Databesparing\" is ingeschakeld. +\n%1$s zal zich nog steeds inspannen om gegevens op te slaan waar mogelijk. + Jouw apparaat biedt geen ondersteuning voor het uitschakelen van gegevensbesparing voor %1$s. + Je hebt alle OMEMO-sleutels die je bezit geverifieerd + Vertrouw nieuwe apparaten van niet-geverifieerde contacten, maar bevestig nieuwe apparaten onmiddellijk handmatig voor geverifieerde contacten. + Cachemap opschonen (gebruikt door camera-app) + Bijbehorende chats gearchiveerd. + Meldingen van berichten en oproepen van vreemden. + Kleurrijke chatbubbels + Duidelijke achtergrondkleuren voor verzonden en ontvangen berichten + Dynamische kleuren + Systeemkleuren (Material You) + Lopende oproepen + Gemiste oproepen + Doorgaan + Geen app om website te openen + Let op-meldingen weergeven + Er is een handleiding opgezet voor het aanmaken van een account op conversations.im. +\nWanneer je conversations.im als provider kiest, kun je communiceren met gebruikers van andere providers door hen jouw volledige XMPP-adres te geven. + %1$s verwerkt jouw contactenlijst lokaal, op jouw apparaat, om u de namen en profielfoto\'s te tonen voor overeenkomende contacten op XMPP. +\n +\nGegevens uit de contacten lijst blijven te allen tijde op jouw apparaat! + De barcode bevat geen vingerafdrukken voor deze chat. + Weet je zeker dat je de verificatie van dit apparaat wilt verwijderen? +\nDit apparaat en berichten ervan worden gemarkeerd als \'Niet vertrouwd\'. + %1$s kan geen versleutelde berichten verzenden naar %2$s. Dit kan te wijten zijn aan het feit dat jouw contact een verouderde server of client gebruikt die OMEMO niet aankan. + Laatst gezien + Opnieuw verbinding maken op een andere host + Blindelings vertrouwde OMEMO-sleutels, wat betekent dat ze iemand anders kunnen zijn of dat iemand kan hebben ingetikt. + Je staat op het punt om de OMEMO-sleutels van je eigen account te verifiëren. Dit is alleen veilig als je deze link hebt gevolgd van een betrouwbare bron waar alleen jij deze link had kunnen publiceren. + Inkomende oproepen + Probeer geen back-ups terug te zetten die je niet zelf hebt aangemaakt! + Verleen %1$s toegang tot de microfoon + Deze meldingscategorie wordt gebruikt om een permanente melding weer te geven die aangeeft dat %1$s actief is. + Oproepen + Mislukte leveringen + Instellingen voor berichtmeldingen + Meldingsinstellingen voor inkomende oproepen \ No newline at end of file From 7aa4eebee507a17707d0f8563198809b7ea9d1b6 Mon Sep 17 00:00:00 2001 From: Stephan-P Date: Fri, 12 Jul 2024 09:17:16 +0000 Subject: [PATCH 136/192] Translated using Weblate (Dutch) Currently translated at 94.9% (968 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/nl/ --- src/main/res/values-nl/strings.xml | 77 ++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index 8970360db083a95892149b07de2a9ed619ce554b..e680579cb836f35ae1e88bb9d0ad994a2e73fcd3 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -955,4 +955,81 @@ Mislukte leveringen Instellingen voor berichtmeldingen Meldingsinstellingen voor inkomende oproepen + Geen (gedeactiveerd) + Account van server verwijderen + Melding verbergen + Lopend videogesprek + Oproep opnieuw verbinden + Inkomende oproep (%s) · %s + Uitgaande oproep + Uitgaande oproep · %s + Back-up + Apparaten ontdekken + Gaat over + Storing met app + Probleem met verificatie + Ophangen + Lopend gesprek + Persoon is niet beschikbaar + Verbinding verbroken + Ingetrokken oproep + Uitgaande oproep (%s) · %s + Gemiste oproep · %s + Gemiste oproep + Audiogesprek + Hulp + Tijdelijke authenticatiefout + Geen XMPP-adres gevonden + Avatar verwijderen + Oproepen zijn uitgeschakeld bij gebruik van Tor + Overschakelen naar video + Verzoek om overschakeling naar video afwijzen + UnifiedPush Distributor + XMPP-account + Het account waarmee pushberichten worden ontvangen. + Push-server + Weigeren + + %1$d gemiste oproep van %2$s + %1$d gemiste oproepen van %2$s + + Oproepen + Schakel een account in + Inkomende video-oproep + Overschakelen naar videogesprek? + Extra sporen toevoegen? + Verbinden + Verbonden + Opnieuw verbinden + Overschakelen naar chat + Afsluiten + Oproep aannemen + Oproep beëindigen + Antwoorden + Afwijzen + GPX-route + Inloggen + Uitloggen + + %1$d gemiste oproep van %2$d contactpersoon + %1$d gemiste oproepen van %2$d contactpersonen + + De meeste gebruikers moeten ‘jabber.network’ kiezen voor betere suggesties uit het hele openbare XMPP-ecosysteem. + Tor uitschakelen voor oproepen + Video-oproep opnieuw verbinden + Server biedt geen ondersteuning voor het aanmaken van uitnodigingen + Je probeert een verouderd back-upbestandsformaat te importeren + De app voor delen heeft geen toestemming gegeven voor toegang tot dit bestand. + Groepschats en kanalen + jabber.network + Kanaal-ontdekkingsmethode + Inkomende oproep + Inkomende oproep + + %d gemiste oproep + %d gemiste oproepen + + Versleuteld met OpenPGP + Antwoordbericht opnemen + Contactpersoon toevoegen, groepschat aanmaken of erbij aansluiten, of kanalen ontdekken \ No newline at end of file From 33211dbcae3833500f1ae1b5d8d935adfe68f8e2 Mon Sep 17 00:00:00 2001 From: Stephan-P Date: Thu, 11 Jul 2024 09:02:31 +0000 Subject: [PATCH 137/192] Translated using Weblate (Dutch) Currently translated at 100.0% (9 of 9 strings) Translation: Conversations/Android App (Quicksy) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-quicksy/nl/ --- src/quicksy/res/values-nl/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quicksy/res/values-nl/strings.xml b/src/quicksy/res/values-nl/strings.xml index eec11a1f90b41c109cf89000a6b69c44811afe9e..80b7a48e46e27cb0dc2b62c411b6e87a7cc891f1 100644 --- a/src/quicksy/res/values-nl/strings.xml +++ b/src/quicksy/res/values-nl/strings.xml @@ -4,9 +4,9 @@ Door crashrapportages te versturen help je de ontwikkeling van Quicksy Laat al je contactpersonen weten wanneer je Quicksy gebruikt Om meldingen te blijven ontvangen, zelfs wanneer het scherm uit staat, moet je Quicksy toevoegen aan de lijst met beschermde apps. - Quicksy-profielafbeelding + Quicksy profielafbeelding Quicksy is niet beschikbaar in je land. Kan serveridentiteit niet verifiëren. Onbekende beveiligingsfout. Time-out bij verbinden met server. - + \ No newline at end of file From 12c64c3bf86388fe95eb86d3124a83763a086d0c Mon Sep 17 00:00:00 2001 From: Stephan-P Date: Fri, 12 Jul 2024 10:03:59 +0000 Subject: [PATCH 138/192] Translated using Weblate (Dutch) Currently translated at 100.0% (1020 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/nl/ --- src/main/res/values-nl/strings.xml | 86 ++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 16 deletions(-) diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index e680579cb836f35ae1e88bb9d0ad994a2e73fcd3..0e49fcdce6e6dd121b94ec21508c2e55111d690a 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -79,7 +79,9 @@ Geschiedenis wissen Gespreksgeschiedenis wissen Bestand verwijderen - Weet je zeker dat je dit bestand wil verwijderen?\n\nWaarschuwing: Dit zal kopieën van dit bericht die opgeslagen zijn op andere apparaten of servers niet verwijderen. + Weet je zeker dat je dit bestand wil verwijderen? +\n +\nWaarschuwing: Dit zal kopieën van dit bericht die opgeslagen zijn op andere apparaten of servers niet verwijderen. Apparaat kiezen Verstuur onversleuteld bericht Verstuur bericht @@ -92,7 +94,7 @@ Herstarten Installeren Gelieve OpenKeychain te installeren - bezig met aanbieden… + Aanbieden… wachten… Geen OpenPGP-sleutel gevonden Geen OpenPGP-sleutels gevonden @@ -155,8 +157,8 @@ Avatar publiceren OpenPGP-publieke sleutel publiceren OpenPGP-publieke sleutel verwijderen - Weet je zeker dat je je publieke OpenPGP-sleutel uit je aankondiging van aanwezigheid wilt verwijderen\? -\nJe contacten zullen je geen versleutelde OpenPGP-berichten meer kunnen sturen. + Weet je zeker dat je jouw publieke OpenPGP-sleutel uit je aankondiging van aanwezigheid wilt verwijderen? +\nJouw contacten zullen je geen versleutelde OpenPGP-berichten meer kunnen sturen. OpenPGP-publieke sleutel gepubliceerd. Account inschakelen Stem opnemen @@ -294,10 +296,10 @@ Belet het besturingssysteem je verbinding te onderbreken Back-up creëren Back-upbestanden worden opgeslagen in %s - Bezig met creëren van back-upbestanden... + Back-upbestanden aanmaken Je back-up is opgeslagen De back-upbestanden zijn opgeslagen in %s - Bezig met herstellen van back-up... + Back-up terugzetten Je back-up is hersteld Vergeet niet om de account in te schakelen. Bestand kiezen @@ -374,8 +376,8 @@ Android-applicatie Contact Avatar is gepubliceerd! - Bezig met versturen van %s - Bezig met aanbieden van %s + Versturen %s + Aanbieden %s Offline contacten verbergen %s is aan het typen… %s is gestopt met typen @@ -510,7 +512,7 @@ Vingerafdruk kopiëren Geverifieerde vingerafdrukken Gebruik de camera om de streepjescode van een contact te scannen - De sleutels worden opgehaald. Even geduld. + Wacht tot de sleutels zijn opgehaald Delen als streepjescode Delen als XMPP-URI Delen als HTTP-link @@ -548,7 +550,7 @@ %d maand - %d maand + %d maanden Automatisch berichten verwijderen Verwijder automatisch berichten van dit apparaat ouder dan de ingestelde tijdsperiode. @@ -678,8 +680,8 @@ Weet je zeker dat je de registratieprocedure wilt stopzetten? Ja Nee - Bezig met verifiëren… - Bezig met aanvragen van sms… + Verifiëren… + SMS aanvragen… De ingevoerde code is onjuist. De toegestuurde code is verlopen. Onbekende netwerkfout. @@ -706,7 +708,7 @@ e-boek Origineel (zonder compressie) Openen met… - Conversations-profielafbeelding + Conversations profielafbeelding Kies een account Back-up herstellen Herstellen @@ -723,7 +725,7 @@ Voer een naam in voor het kanaal Voer een XMPP-adres in Dit is een XMPP-adres. Voer een naam in. - Bezig met creëren van openbaar kanaal... + Openbaar kanaal aanmaken… Dit kanaal bestaat al Je hebt deelgenomen aan een bestaand kanaal Iedereen mag het onderwerp aanpassen @@ -795,7 +797,7 @@ Kan video niet schakelen. Onversleuteld document Accountregistraties zijn niet ondersteund - Door crashrapportages te versturen help je de ontwikkeling + Door foutrapportages te versturen help je de ontwikkeling Inloggen met certificaat Chat archiveren Kan niet verbinden met OpenKeychain @@ -1022,7 +1024,7 @@ De app voor delen heeft geen toestemming gegeven voor toegang tot dit bestand. Groepschats en kanalen jabber.network - Kanaal-ontdekkingsmethode + Channel discovery-methode Inkomende oproep Inkomende oproep @@ -1032,4 +1034,56 @@ Versleuteld met OpenPGP Antwoordbericht opnemen Contactpersoon toevoegen, groepschat aanmaken of erbij aansluiten, of kanalen ontdekken + Respijtperiode, Beltoon, Trilling, Vreemden + Verzenden + Ontvangen + Automatisch downloaden + Uiterlijk + Licht/donker thema + Schermopnames toestaan + App-inhoud weergeven in app-schakelaar en schermopnames toestaan + End-to-end-versleuteling + Certificaatautoriteiten + CA-certificaten van het systeem vertrouwen + E2E-versleuteling, Blind vertrouwen voor verificatie, MITM-detectie + Eenmalige back-up aanmaken + Herhaalde back-up + Eenmalig aanmaken, Herhaald inplannen + Meldingen op volledig scherm + Contactlijstintegratie is niet beschikbaar + Notification relay for UnifiedPush compatible third party apps + Spam rapporteren + Privéberichten toestaan + Accepteer uitnodigingen om chats van vreemden te groeperen + Uitnodigingen van vreemden + Groot lettertype + Vergroot de lettergrootte in berichtbubbels + Je gebruikt niet-geverifieerde apparaten. Scan de QR-code op jouw andere apparaten om verificatie uit te voeren en actieve MITM-aanvallen te belemmeren. + Toestaan dat deze app inkomende oproepmeldingen weergeeft op het volledige scherm wanneer het apparaat is vergrendeld. + Jouw contactpersoon maakt gebruik van niet-geverifieerde apparaten. Scan hun QR-code om verificatie uit te voeren en actieve MITM-aanvallen te belemmeren. + Oproepintegratie niet beschikbaar! + In de rol van een UnifiedPush Distributor wordt de permanente, betrouwbare en batterijvriendelijke XMPP-verbinding gebruikt om andere UnifiedPush-compatibele apps als Tusky, Ltt.rs, FluffyChat etc. uit de slaapstand te halen. + Bestandsgrootte, Beeldcompressie, Videokwaliteit + Hostnaam & poort, Tor, Channel Discovery + Channel Discovery gebruikt een externe dienst <a href=https://search.jabber.network>search.jabber.network</a>.<br><br>Bij het gebruik van deze functie, worden jouw IP-adres en zoektermen naar deze dienst verzonden. Lees de <a href=https://search.jabber.network/privacy>Privacy Policy</a> voor meer informatie. + Spam rapporteren en spammer blokkeren + Privacybeleid + Geen clientcertificaat geselecteerd! + Interface + Beveiliging + Meldingen + Thema, Kleuren, Schermopnames, Invoer + Chat archiveren & verwijderen + Kanaalbinding vereisen + Kanaalbinding kan sommige machine-in-the-middle-aanvallen detecteren + Server-verbinding + Besturingssysteem + Typemeldingen, Laatst gezien, Beschikbaarheid + Hostnaam & poort, Tor + Toetsenbord + Meldingen van betrokkenheid + Toepassing + Interactie + Op apparaat + Niet-ondersteunde actie \ No newline at end of file From f4dbf1ab6de027b1e2c2e53858e7324349205fb9 Mon Sep 17 00:00:00 2001 From: Stephan-P Date: Sat, 13 Jul 2024 04:04:24 +0000 Subject: [PATCH 139/192] Translated using Weblate (Dutch) Currently translated at 100.0% (1020 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/nl/ --- src/main/res/values-nl/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index 0e49fcdce6e6dd121b94ec21508c2e55111d690a..81d180457cf5b55135eed0361a22da3f690548f8 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -1034,7 +1034,7 @@ Versleuteld met OpenPGP Antwoordbericht opnemen Contactpersoon toevoegen, groepschat aanmaken of erbij aansluiten, of kanalen ontdekken - Respijtperiode, Beltoon, Trilling, Vreemden + Uitstelperiode, Beltoon, Trilling, Vreemden Verzenden Ontvangen Automatisch downloaden From e9fedc26dfacaa9ab9d5682c4df5c9b86fc492d6 Mon Sep 17 00:00:00 2001 From: random_r Date: Sat, 13 Jul 2024 09:20:14 +0000 Subject: [PATCH 140/192] Translated using Weblate (Italian) Currently translated at 100.0% (69 of 69 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/it/ --- fastlane/metadata/android/it-IT/changelogs/4211604.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/4211604.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/4211604.txt b/fastlane/metadata/android/it-IT/changelogs/4211604.txt new file mode 100644 index 0000000000000000000000000000000000000000..f084c214e2c391573c00e7e12b6c04fa01f484b3 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/4211604.txt @@ -0,0 +1 @@ +* Correzioni minori di errori From 07600c57ea62ce2fe1a43f4ff391af458fa4b053 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 15 Jul 2024 10:06:40 +0200 Subject: [PATCH 141/192] catch rare race conditions when pressing switch to earpiece --- .../conversations/ui/RtpSessionActivity.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 1e68fae2e9017961fa0c203f76151a26ad85e99c..06cf4e5140654a82ab66a3b78372414139034f78 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -1284,13 +1284,21 @@ public class RtpSessionActivity extends XmppActivity } private void switchToEarpiece(final View view) { - requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.EARPIECE); - acquireProximityWakeLock(); + try { + requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.EARPIECE); + acquireProximityWakeLock(); + } catch (final IllegalStateException e) { + Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show(); + } } private void switchToSpeaker(final View view) { - requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.SPEAKER_PHONE); - releaseProximityWakeLock(); + try { + requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.SPEAKER_PHONE); + releaseProximityWakeLock(); + } catch (final IllegalStateException e) { + Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show(); + } } private void retry(final View view) { From 65fd84346714e21773b2bd46e732b42000da4e12 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 15 Jul 2024 10:08:47 +0200 Subject: [PATCH 142/192] catch establishing call when last call is shutting down --- .../CallIntegrationConnectionService.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java b/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java index 95f1f6785270a41bd074cf99caedac0ccd077826..a225dcbee935a5e2782eb6a6873b4ee156939c2b 100644 --- a/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/CallIntegrationConnectionService.java @@ -35,6 +35,7 @@ import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.ui.RtpSessionActivity; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection; +import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; @@ -125,9 +126,17 @@ public class CallIntegrationConnectionService extends ConnectionService { // actually attempted // sendJingleFinishMessage(service, contact, Reason.CONNECTIVITY_ERROR); } else { - final var proposal = - service.getJingleConnectionManager() - .proposeJingleRtpSession(account, with, media); + final JingleConnectionManager.RtpSessionProposal proposal; + try { + proposal = + service.getJingleConnectionManager() + .proposeJingleRtpSession(account, with, media); + } catch (final IllegalStateException e) { + return Connection.createFailedConnection( + new DisconnectCause( + DisconnectCause.ERROR, + "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 return Connection.createFailedConnection( From 7ef02063435c38db75f2eb9459e3971bd3ee3c5a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 15 Jul 2024 13:53:21 +0200 Subject: [PATCH 143/192] modernize auto download values --- .../conversations/ui/TimePreference.java | 98 ------------------- .../settings/AttachmentsSettingsFragment.java | 18 +++- .../settings/BackupSettingsFragment.java | 14 +-- .../settings/SecuritySettingsFragment.java | 20 ++-- .../settings/XmppPreferenceFragment.java | 33 +++++-- src/main/res/values/arrays.xml | 14 +-- src/main/res/xml/preferences_attachments.xml | 2 - 7 files changed, 57 insertions(+), 142 deletions(-) delete mode 100644 src/main/java/eu/siacs/conversations/ui/TimePreference.java diff --git a/src/main/java/eu/siacs/conversations/ui/TimePreference.java b/src/main/java/eu/siacs/conversations/ui/TimePreference.java deleted file mode 100644 index a7e53ca383af2169a4294e2e467f723c047f4e6a..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/ui/TimePreference.java +++ /dev/null @@ -1,98 +0,0 @@ -package eu.siacs.conversations.ui; - -import android.content.Context; -import android.content.res.TypedArray; -import android.preference.DialogPreference; -import android.preference.Preference; -import android.util.AttributeSet; -import android.view.View; -import android.widget.TimePicker; - -import java.text.DateFormat; -import java.util.Calendar; -import java.util.Date; - - -public class TimePreference extends DialogPreference implements Preference.OnPreferenceChangeListener { - private TimePicker picker = null; - public final static long DEFAULT_VALUE = 0; - - public TimePreference(final Context context, final AttributeSet attrs) { - super(context, attrs, 0); - this.setOnPreferenceChangeListener(this); - } - - protected void setTime(final long time) { - persistLong(time); - notifyDependencyChange(shouldDisableDependents()); - notifyChanged(); - updateSummary(time); - } - - private void updateSummary(final long time) { - final DateFormat dateFormat = android.text.format.DateFormat.getTimeFormat(getContext()); - final Date date = minutesToCalender(time).getTime(); - setSummary(dateFormat.format(date.getTime())); - } - - @Override - protected View onCreateDialogView() { - picker = new TimePicker(getContext()); - picker.setIs24HourView(android.text.format.DateFormat.is24HourFormat(getContext())); - return picker; - } - - @SuppressWarnings("NullableProblems") - @Override - protected void onBindDialogView(final View v) { - super.onBindDialogView(v); - long time = getPersistedLong(DEFAULT_VALUE); - - picker.setCurrentHour((int) ((time % (24 * 60)) / 60)); - picker.setCurrentMinute((int) ((time % (24 * 60)) % 60)); - } - - @Override - protected void onDialogClosed(final boolean positiveResult) { - super.onDialogClosed(positiveResult); - - if (positiveResult) { - setTime(picker.getCurrentHour() * 60 + picker.getCurrentMinute()); - } - } - - private static Calendar minutesToCalender(long time) { - final Calendar c = Calendar.getInstance(); - c.set(Calendar.HOUR_OF_DAY, (int) ((time % (24 * 60)) / 60)); - c.set(Calendar.MINUTE, (int) ((time % (24 * 60)) % 60)); - return c; - } - - public static long minutesToTimestamp(long time) { - return minutesToCalender(time).getTimeInMillis(); - } - - @Override - protected Object onGetDefaultValue(final TypedArray a, final int index) { - return a.getInteger(index, 0); - } - - @Override - protected void onSetInitialValue(final boolean restorePersistedValue, final Object defaultValue) { - long time; - if (defaultValue instanceof Long) { - time = restorePersistedValue ? getPersistedLong((Long) defaultValue) : (Long) defaultValue; - } else { - time = restorePersistedValue ? getPersistedLong(DEFAULT_VALUE) : DEFAULT_VALUE; - } - - setTime(time); - updateSummary(time); - } - - @Override - public boolean onPreferenceChange(final Preference preference, final Object newValue) { - ((TimePreference) preference).updateSummary((Long)newValue); - return true; - } -} diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/AttachmentsSettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/AttachmentsSettingsFragment.java index 6b05c18c5b6630f3fe04ac3af15ed62f3a0f5ce0..32bef556d3a22991879c12f758843b6ec8d1c1e0 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/AttachmentsSettingsFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/AttachmentsSettingsFragment.java @@ -3,15 +3,31 @@ package eu.siacs.conversations.ui.fragment.settings; import android.os.Bundle; import androidx.annotation.Nullable; +import androidx.preference.ListPreference; import androidx.preference.PreferenceFragmentCompat; import eu.siacs.conversations.R; +import eu.siacs.conversations.utils.UIHelper; -public class AttachmentsSettingsFragment extends PreferenceFragmentCompat { +public class AttachmentsSettingsFragment extends XmppPreferenceFragment { @Override public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { setPreferencesFromResource(R.xml.preferences_attachments, rootKey); + final ListPreference autoAcceptFileSize = findPreference("auto_accept_file_size"); + if (autoAcceptFileSize == null) { + throw new IllegalStateException("The preference resource file is missing preferences"); + } + setValues( + autoAcceptFileSize, + R.array.file_size_values, + value -> { + if (value <= 0) { + return getString(R.string.never); + } else { + return UIHelper.filesizeToString(value); + } + }); } @Override diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/BackupSettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/BackupSettingsFragment.java index f78577656d619357219dd94068dd67d968946464..739598a69009561a7f6a6bec4076fc6530d31c32 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/BackupSettingsFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/BackupSettingsFragment.java @@ -71,16 +71,10 @@ public class BackupSettingsFragment extends XmppPreferenceFragment { R.string.pref_create_backup_summary, FileBackend.getBackupDirectory(requireContext()).getAbsolutePath())); createOneOffBackup.setOnPreferenceClickListener(this::onBackupPreferenceClicked); - final int[] choices = getResources().getIntArray(R.array.recurring_backup_values); - final CharSequence[] entries = new CharSequence[choices.length]; - final CharSequence[] entryValues = new CharSequence[choices.length]; - for (int i = 0; i < choices.length; ++i) { - entryValues[i] = String.valueOf(choices[i]); - entries[i] = timeframeValueToName(requireContext(), choices[i]); - } - recurringBackup.setEntries(entries); - recurringBackup.setEntryValues(entryValues); - recurringBackup.setSummaryProvider(new TimeframeSummaryProvider()); + setValues( + recurringBackup, + R.array.recurring_backup_values, + value -> timeframeValueToName(requireContext(), value)); } @Override diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/SecuritySettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/SecuritySettingsFragment.java index 0ccf2679e17d3069093148d73ba31af3dba127fc..2fa1bc3bee2e624d66a63af5991e8729e2d0dec4 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/SecuritySettingsFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/SecuritySettingsFragment.java @@ -4,6 +4,7 @@ import android.content.DialogInterface; import android.os.Bundle; import android.widget.Toast; +import androidx.annotation.ArrayRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; @@ -11,7 +12,9 @@ import androidx.preference.ListPreference; import androidx.preference.Preference; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.common.base.Function; import com.google.common.base.Strings; +import com.google.common.primitives.Ints; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.R; @@ -21,6 +24,7 @@ import eu.siacs.conversations.services.MemorizingTrustManager; import java.security.KeyStoreException; import java.util.ArrayList; import java.util.Collections; +import java.util.concurrent.Callable; public class SecuritySettingsFragment extends XmppPreferenceFragment { @@ -36,19 +40,12 @@ public class SecuritySettingsFragment extends XmppPreferenceFragment { throw new IllegalStateException("The preference resource file is missing preferences"); } omemo.setSummaryProvider(new OmemoSummaryProvider()); - final int[] choices = getResources().getIntArray(R.array.automatic_message_deletion_values); - final CharSequence[] entries = new CharSequence[choices.length]; - final CharSequence[] entryValues = new CharSequence[choices.length]; - for (int i = 0; i < choices.length; ++i) { - entryValues[i] = String.valueOf(choices[i]); - entries[i] = timeframeValueToName(requireContext(), choices[i]); - } - automaticMessageDeletion.setEntries(entries); - automaticMessageDeletion.setEntryValues(entryValues); - automaticMessageDeletion.setSummaryProvider(new TimeframeSummaryProvider()); + setValues( + automaticMessageDeletion, + R.array.automatic_message_deletion_values, + value -> timeframeValueToName(requireContext(), value)); } - @Override protected void onSharedPreferenceChanged(@NonNull String key) { super.onSharedPreferenceChanged(key); @@ -151,7 +148,6 @@ public class SecuritySettingsFragment extends XmppPreferenceFragment { .show(); } - private static class OmemoSummaryProvider implements Preference.SummaryProvider { diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/XmppPreferenceFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/XmppPreferenceFragment.java index 76364526d187044915c099dd81dac15a04188ba4..43efde3872cddcc0cd927c95eaa11871d5ebbebb 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/XmppPreferenceFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/XmppPreferenceFragment.java @@ -4,12 +4,14 @@ import android.content.Context; import android.content.SharedPreferences; import android.util.Log; +import androidx.annotation.ArrayRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; +import com.google.common.base.Function; import com.google.common.base.Strings; import com.google.common.primitives.Ints; @@ -101,14 +103,29 @@ public abstract class XmppPreferenceFragment extends PreferenceFragmentCompat { } } - protected static class TimeframeSummaryProvider - implements Preference.SummaryProvider { - - @Nullable - @Override - public CharSequence provideSummary(@NonNull ListPreference preference) { - final Integer value = Ints.tryParse(Strings.nullToEmpty(preference.getValue())); - return timeframeValueToName(preference.getContext(), value == null ? 0 : value); + protected void setValues( + final ListPreference listPreference, + @ArrayRes int resId, + final Function valueToName) { + final int[] choices = getResources().getIntArray(resId); + final CharSequence[] entries = new CharSequence[choices.length]; + final CharSequence[] entryValues = new CharSequence[choices.length]; + for (int i = 0; i < choices.length; ++i) { + final int value = choices[i]; + entryValues[i] = String.valueOf(choices[i]); + entries[i] = valueToName.apply(value); } + listPreference.setEntries(entries); + listPreference.setEntryValues(entryValues); + listPreference.setSummaryProvider( + new Preference.SummaryProvider() { + @Nullable + @Override + public CharSequence provideSummary(@NonNull ListPreference preference) { + final Integer value = + Ints.tryParse(Strings.nullToEmpty(preference.getValue())); + return valueToName.apply(value == null ? 0 : value); + } + }); } } diff --git a/src/main/res/values/arrays.xml b/src/main/res/values/arrays.xml index 1c00e9e737efd498898eee6e120dd19477d260d7..47fbfb81f361574db8ef11b3b4a2643744eea490 100644 --- a/src/main/res/values/arrays.xml +++ b/src/main/res/values/arrays.xml @@ -1,22 +1,14 @@ - - @string/never - 256 KiB - 512 KiB - 1 MiB - 5 MiB - 10 MiB - - + 0 - 262144 524288 1048576 5242880 10485760 - + 52428800 + 1800 diff --git a/src/main/res/xml/preferences_attachments.xml b/src/main/res/xml/preferences_attachments.xml index 7577fb2eaf99c8417ea94f73665f96425cf5c599..23c741b3a3a7092a6e1c112e1b15ba225cd078d1 100644 --- a/src/main/res/xml/preferences_attachments.xml +++ b/src/main/res/xml/preferences_attachments.xml @@ -22,8 +22,6 @@ Date: Tue, 16 Jul 2024 09:22:34 +0000 Subject: [PATCH 144/192] Translated using Weblate (Russian) Currently translated at 98.9% (1009 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/ru/ --- src/main/res/values-ru/strings.xml | 45 +++++++++++++++--------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 910490c16e06d9305c7ac95a0b2f1916b019c3d2..50827224d262e4ee01b84855629e707454a9dda7 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -41,7 +41,7 @@ Модератор Участник Посетитель - Вы хотите удалить %s из своего списка контактов? Беседы, связанные с этим контактом, будут сохранены. + Вы хотите удалить %s из своего списка контактов? Чат с этим контактом удалён не будет. Вы хотите заблокировать дальнейшие сообщения от %s? Вы хотите разблокировать пользователя %s? Заблокировать всех пользователей домена %s? @@ -79,7 +79,7 @@ Подготовка к передаче изображений Обмен файлами. Пожалуйста, подождите… Очистить историю - Очистить историю + Очистить историю чата Вы хотите удалить все сообщения в этой беседе? \n \nВнимание: Данная операция не повлияет на сообщения, хранящиеся на других устройствах или серверах. @@ -88,12 +88,12 @@ \n \nПредупреждение: Данная операция не удалит копии этого файла, хранящиеся на других устройствах или серверах. Выберите устройство - Нешифрованное сообщение + Отправить сообщение чистым текстом Сообщение Сообщение для %s v\\OMEMO-зашифр. сообщение Используется новое имя - Отправить в незашифрованном виде + Отправить чистый текст Расшифровка не удалась. Вероятно, что у вас нет надлежащего ключа. Установите OpenKeychain OpenKeychain для шифрования и дешифрования сообщений и управления открытыми ключами.

OpenKeychain распространяется под лицензией GPLv3+ и доступна для загрузки через F-Droid или Google Play.

(Потребуется перезапуск %1$s после установки.)]]>
@@ -165,7 +165,7 @@ Несовместимый сервер Ошибка потока Ошибка открытия потока - Без шифра + Чистый текст OTR OpenPGP OMEMO @@ -177,7 +177,7 @@ Вы действительно хотите удалить ваш OpenPGP публичный ключ из опубликованных?\nВаши собеседники не смогут больше отправлять вам зашифрованные OpenPGP сообщения. Публичный ключ OpenPGP опубликован. Включить аккаунт - Вы точно хотите удалить свою учётную запись? Это удалит все истории диалогов + Вы точно хотите удалить свою учётную запись? Удаление учётной записи сотрёт все истории диалогов Запись голоса XMPP-адрес Заблокировать XMPP-адрес @@ -558,7 +558,7 @@ Средний Длинный Оповещать других об использовании - Позволяет вашим контактам видеть, когда вы используете Conversations + Позволяет вашим контактам видеть, когда вы в последний раз использовали приложение Приватность Тема Выбрать цветовую палитру @@ -702,14 +702,14 @@ Не удалось получить список устройств Не удалось получить ключи шифрования Подсказка: в некоторых случаях это может исправлено добавлением друг друга в список контактов. - Вы уверены, что хотите выключить OMEMO-шифрование для этой беседы? + Вы уверены, что хотите выключить OMEMO-шифрование для этого чата? \nЭто позволит администратору сервера читать ваши сообщения, но также это может быть единственным способом связи с людьми, использующими устаревшие клиенты. Отключить сейчас Черновик: OMEMO-шифрование OMEMO будет всегда использоваться для одиночных бесед и закрытых конференций. - OMEMO будет использоваться по умолчанию для новых бесед. - OMEMO нужно будет явно включать для новых бесед. + OMEMO будет использоваться по умолчанию для новых чатов. + OMEMO нужно будет явно включать для новых чатов. Создать ярлык Включено по умолчанию Выключено по умолчанию @@ -730,14 +730,14 @@ Предоставить %1$s разрешение на использование микрофона Поиск сообщений GIF - Посмотреть беседу + Посмотреть чат Расширение для обмена информацией о местонахождении Используйте расширение для обмена информацией о местонахождении вместо встроенной карты Копировать веб-адрес Копировать XMPP-адрес Файлообмен по HTTP для S3 Быстрый поиск - На экране \"Начать беседу\" открывать клавиатуру и ставить курсор в поле поиска + На экране \"Новая беседа\" открывать клавиатуру и ставить курсор в поле поиска Аватар конференции Сервер не поддерживает наличие аватар у конференций Только владелец может менять аватар конференции @@ -788,20 +788,20 @@ Подтвердите %s %s.]]> Мы отправили вам еще одну SMS с кодом из 6 цифр. - Пожалуйста, введите код из 6 цифр ниже. + Пожалуйста, введите ПИН-код из 6 цифр ниже. Отправьте заново SMS Отправьте заново SMS (%s) Пожалуйста, подождите (%s) - назад - Автоматически вставлен возможный код из буфера обмена. - Пожалуйста, введите ваш код из 6 цифр. + Назад + Автоматически вставлен возможный ПИН-код из буфера обмена. + Пожалуйста, введите ваш ПИН-код из 6 цифр. Вы уверены, что хотите прервать процедуру регистрации? Да Нет Подтверждение… Запрос SMS… - Введенный вами код некорректен. - Отправленный вам код просрочен. + Введенный вами ПИН-код некорректен. + Отправленный вам ПИН-код просрочен. Неизвестная ошибка сети. Неизвестный ответ от сервера. Не удалось подключиться к серверу. @@ -929,8 +929,8 @@ Открепить GPX-трек Не удалось исправить сообщение - Все беседы - Эта беседа + Все чаты + Этот чат Ваш аватар Аватар для %s Зашифровано с помощью OMEMO @@ -1037,8 +1037,8 @@ Интеграция вызовов недоступна! Нет разрешения на телефонный звонок Вы хотите удалить закладку для «%s»? - Закрыть и удалить - Вы хотите удалить закладку для %s и закрыть беседу? + Удалить и Архивировать чат + Вы хотите удалить закладку для %s и архивировать чат? Отправить отчёты о вылетах Перейти к беседе Тема, цвета, снимки, ввод @@ -1095,4 +1095,5 @@ Привязка канала может обнаружить некоторые атаки посредником Соединение с сервером Приглашения от незнакомцев + Уведомления о взаимодействии \ No newline at end of file From 8ce02d4cc40d7f2fff5b4a24fd97171483cf39a8 Mon Sep 17 00:00:00 2001 From: 0ko <0ko@users.noreply.translate.codeberg.org> Date: Mon, 22 Jul 2024 18:16:27 +0000 Subject: [PATCH 145/192] Translated using Weblate (Russian) Currently translated at 98.9% (1009 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/ru/ --- src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 50827224d262e4ee01b84855629e707454a9dda7..e8821bbc10c2aacb03d2b875962dc2a8ab2a62b9 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -1037,7 +1037,7 @@ Интеграция вызовов недоступна! Нет разрешения на телефонный звонок Вы хотите удалить закладку для «%s»? - Удалить и Архивировать чат + Архивировать и удалить чат Вы хотите удалить закладку для %s и архивировать чат? Отправить отчёты о вылетах Перейти к беседе From 1cca8d3be0b14a257f2b7f8ea8b96990251678ad Mon Sep 17 00:00:00 2001 From: kazushi Date: Wed, 24 Jul 2024 06:58:23 +0000 Subject: [PATCH 146/192] Translated using Weblate (Japanese) Currently translated at 100.0% (13 of 13 strings) Translation: Conversations/Android App (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-conversations/ja/ --- src/conversations/res/values-ja/strings.xml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/conversations/res/values-ja/strings.xml b/src/conversations/res/values-ja/strings.xml index 851af0d4a0be9fa670c4e72575d413694ba3323e..adb2eb808d94e143e5cd3df3e77385c187d7e039 100644 --- a/src/conversations/res/values-ja/strings.xml +++ b/src/conversations/res/values-ja/strings.xml @@ -2,10 +2,11 @@ XMPP プロバイダーを選択してください conversations.im を利用する - 新規アカウントを作成 - XMPP アカウントをお持ちですか?既にほかの XMPP クライアントを利用しているか、 Conversations を利用したことがある場合はこちら。初めての方は、今すぐ新規 XMPP アカウントを作成できます。\nヒント: e メールのプロバイダーが XMPP アカウントも提供している場合があります。 - XMPP は、プロバイダーに依存しないインスタントメッセージのプロトコルです。 XMPP サーバーならどこでも、このアプリを使用することができます。 -\nよろしければ、 Conversations に最適化されたプロバイダー conversations.im で簡単にアカウントを作成することもできます。 + アカウントを新規作成 + XMPP アカウントをお持ちですか? 既に他の XMPP クライアントを利用しているか、 Conversations を利用したことがある場合はアカウントをお持ちです。初めての方は、今すぐ XMPP アカウントを新規作成できます。 +\nヒント: いくつかのメールプロバイダーは XMPP アカウントも提供しています。 + XMPP は、プロバイダーに依存しないインスタントメッセージのネットワークです。どの XMPP サーバーでもこのアプリを使用できます。 +\nConversations に最適化された conversations.im で簡単にアカウントを作成することもできます。 %1$s へ招待されました。アカウント作成手順をご案内します。 \n%1$s をプロバイダーに選択してほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。 %1$s へ招待されました。ユーザー名は既に選択されています。アカウント作成手順をご案内します。 \nほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。 サーバーの招待 From 43051695f8fd2fe6fc8141bb40454f2593186e89 Mon Sep 17 00:00:00 2001 From: kazushi Date: Wed, 24 Jul 2024 06:39:30 +0000 Subject: [PATCH 147/192] Translated using Weblate (Japanese) Currently translated at 50.0% (1 of 2 strings) Translation: Conversations/App Store Metadata (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata-conversations/ja/ --- .../fastlane/metadata/android/ja-JP/short_description.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversations/fastlane/metadata/android/ja-JP/short_description.txt b/src/conversations/fastlane/metadata/android/ja-JP/short_description.txt index ade292722c0c54d2a8808802df9b0ddf550e6fc8..b41413005aafe88b527886a4d1d5f863d5f8155a 100644 --- a/src/conversations/fastlane/metadata/android/ja-JP/short_description.txt +++ b/src/conversations/fastlane/metadata/android/ja-JP/short_description.txt @@ -1 +1 @@ -携帯端末で簡単に操作できるXMPP暗号化インスタント・メッセンジャー +暗号化対応、モバイル端末で簡単に使用できる XMPP インスタント・メッセンジャー From a1b16308a0639167f913c9887ab998ed68f1507b Mon Sep 17 00:00:00 2001 From: kazushi Date: Wed, 24 Jul 2024 13:09:08 +0000 Subject: [PATCH 148/192] Translated using Weblate (Japanese) Currently translated at 99.5% (1015 of 1020 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 | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index a419e9591ef855d5f44cdd3e3d4d3df090d810c7..adc465911a9295b6198bfa975a67da1eca2eae9a 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -9,28 +9,28 @@ アカウントを追加 名前を編集 アドレス帳に追加 - 名簿から削除 + 連絡先リストから削除 連絡先をブロック - 連絡先のブロックを解除 + 連絡先をブロック解除 ドメインをブロック - ドメインのブロックを解除 + ドメインをブロック解除 参加者をブロック - 参加者のブロックを解除 + 参加者をブロック解除 アカウントを管理 設定 連絡先を選択 連絡先を選択 アカウントで共有 - ブロック一覧 + ブロックリスト ちょうど今 - 1分前 - %d分前 + 1 分前 + %d 分前 - %d件の未読の会話 + 未読の会話 %d 件 送信中… メッセージを復号しています。しばらくお待ちください… - OpenPGPで暗号化されたメッセージ + OpenPGP で暗号化されたメッセージ ニックネームはすでに使用されています ニックネームが正しくありません 管理者 @@ -990,24 +990,24 @@ 連絡先は未検証のデバイスを使用しています。 QR コードをスキャンして検証を実行し、アクティブな MITM 攻撃を阻止してください。 未検証のデバイスを使用しています。他のデバイスで QR コードをスキャンして検証を実行し、アクティブな MITM 攻撃を阻止してください。 電話をかける権限がありません - %s のブックマークを削除して会話を保管しますか ? + %s のブックマークを削除して会話をアーカイブしますか ? %s のブックマークを削除しますか ? 通話の統合は利用できません。 連絡先は利用できません - 削除して会話を保管 + 削除して会話をアーカイブ クラッシュ報告を送信 - 対応する会話は保管されました。 + 対応する会話はアーカイブされました。 クライアント証明書が選択されていません。 ホスト名とポート番号、Tor このデバイスで 猶予期間、着信音、バイブレーション、見知らぬ人 端末間暗号化、検証前の無条件の信頼、MITM検出 - 会話を保管 + 会話をアーカイブ 新しい会話 - この会話を保管 + その後の会話を削除 システムの配色 (Material You) 暗号化メッセージを送信 - 会話は保管されました + 会話はアーカイブされました 多彩な会話の吹き出し 会話に切り替え インターフェース From 9c79432d07472b0b9b5e209a7f6b8e6ea0a7510e Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Thu, 25 Jul 2024 14:58:05 +0000 Subject: [PATCH 149/192] Translated using Weblate (Spanish) Currently translated at 100.0% (2 of 2 strings) Translation: Conversations/App Store Metadata (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata-conversations/es/ --- .../fastlane/metadata/android/es-ES/short_description.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversations/fastlane/metadata/android/es-ES/short_description.txt b/src/conversations/fastlane/metadata/android/es-ES/short_description.txt index 7ed04767246cd7fab459ea0ebc43836a01ddd32f..11f7274bdebebf58375141e69e3b5a2fa1c70449 100644 --- a/src/conversations/fastlane/metadata/android/es-ES/short_description.txt +++ b/src/conversations/fastlane/metadata/android/es-ES/short_description.txt @@ -1 +1 @@ -Mensajería instantánea XMPP cifrada y fácil de usar para tu dispositivo móvil +Mensajería instantánea XMPP cifrada y fácil de usar para tu teléfono inteligente From b40b8467d78d15011547c81d21ecabfe8a78437b Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Thu, 25 Jul 2024 14:56:14 +0000 Subject: [PATCH 150/192] Translated using Weblate (Spanish) Currently translated at 100.0% (2 of 2 strings) Translation: Conversations/App Store Metadata (Quicksy) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata-quicksy/es/ --- .../metadata/android/es-ES/full_description.txt | 16 ++++++++-------- .../metadata/android/es-ES/short_description.txt | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/quicksy/fastlane/metadata/android/es-ES/full_description.txt b/src/quicksy/fastlane/metadata/android/es-ES/full_description.txt index 7b75633e869fb601e192fa6e5d717c56347ce56e..b57fd6c1cbf47fa40776d93bf47663b51d410061 100644 --- a/src/quicksy/fastlane/metadata/android/es-ES/full_description.txt +++ b/src/quicksy/fastlane/metadata/android/es-ES/full_description.txt @@ -1,14 +1,14 @@ -Quicksy es un programa que se ejecuta en el popular cliente Jabber/XMPP, con descubrimiento de contactos automatizado. +Quicksy es una bifurcación del popular cliente Jabber/XMPP Conversations con busqueda automático de contactos. -Regístrese con su número de teléfono y Quicksy, según los números de teléfono de su agenda, sugerirá automáticamente contactos potenciales. +Te registras con tu número de teléfono y Quicksy automáticamente, basándose en los números de teléfono de tu agenda, te sugerirá posibles contactos. -En esencia, Quicksy es un cliente Jabber completo que le permite comunicarse con cualquier usuario en cualquier servidor público. De manera similar, se puede contactar a los usuarios de Quicksy desde el extranjero simplemente agregando +phonenumber@quicksy.im a su lista de contactos. +Quicksy es un cliente Jabber completo que te permite comunicarte con cualquier usuario en cualquier servidor público. Asimismo, puedes contactar a los usuarios de Quicksy desde el exterior simplemente agregando +phonenumber@quicksy.im a tu lista de contactos. -Elimine la sincronización de contactos; la interfaz de usuario se deja intencionalmente lo más cerca posible de Conversations. Esto permite a los usuarios migrar, si lo desean, de Quicksy a Conversations sin tener que volver a aprender cómo funciona la aplicación. +Aparte de la sincronización de contactos, la interfaz de usuario se parece lo más posible a Conversations. Esto permite que los usuarios puedan migrar de Quicksy a Conversations sin tener que volver a aprender cómo funciona la aplicación. -Los contactos sugeridos consisten en otros usuarios de Quicksy y usuarios habituales de Jabber/XMPP que han proporcionado su ID de Jabber a la Lista de Quicksy (https://quicksy.im/#get-listed). +Los contactos sugeridos consisten en otros usuarios de Quicksy y usuarios habituales de Jabber/XMPP que han ingresado su ID de Jabber en el Directorio de Quicksy (https://quicksy.im/#get-listed). -NOTA: Para proporcionar (https://quicksy.im/enter/) su ID de Jabber a la Lista -Quicksy requiere una tarifa de registro aplicable única. +NOTA: Para ingresar (https://quicksy.im/enter/) tu ID de Jabber en Quicksy +Directorio se requiere un único pago de registro. -Para más detalles, lea la Política de Privacidad (https://quicksy.im/#privacy). +Lee la Política de privacidad (https://quicksy.im/#privacy) para obtener más información. diff --git a/src/quicksy/fastlane/metadata/android/es-ES/short_description.txt b/src/quicksy/fastlane/metadata/android/es-ES/short_description.txt index 7e7ef394200c6e83bad6fd0dc731f53470570c82..91bf36ef1409fcbaf2312a0bfb02378d993efe34 100644 --- a/src/quicksy/fastlane/metadata/android/es-ES/short_description.txt +++ b/src/quicksy/fastlane/metadata/android/es-ES/short_description.txt @@ -1 +1 @@ -Jabber/XMPP Fácil de usar y encuentra tus contactos +Jabber/XMPP fácil de usar y encuentra tus contactos From 79fbc3bdd25eee7f7ccefe83e78f2d37002b639b Mon Sep 17 00:00:00 2001 From: davilopes Date: Fri, 26 Jul 2024 17:13:47 +0000 Subject: [PATCH 151/192] Translated using Weblate (Portuguese (Brazil)) Currently translated at 91.9% (938 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/pt_BR/ --- src/main/res/values-pt-rBR/strings.xml | 32 +++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index 98f8386f3faf9cc6374fb6d854b2f33cae3ee5dd..0e33068d49d13a7e0eb1fbab6a6654dca7e867e1 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -937,7 +937,7 @@ Chamada de vídeo Ajuda Seu microfone não está disponível - Você só pode ter uma chamada de cada vez + Você só pode fazer uma chamada por vez Retornar para a chamada em andamento Não foi possível trocar a câmera Fixar no topo @@ -992,4 +992,34 @@ Enviar mensagem criptografada Desconectado Gostaria de remover o favorito de %s? + Operação não suportada + Deletar e arquivar chat + Seu contato tem dispositivos não verificados. Escaneie o Código QR dele para fazer uma verificação e impedir ataques MITM. + Sair + Você está usando dispositivos não verificados. Escaneie o Código QR em outro dispositivo para fazer a verificação, e impedir ataques MITM. + Trocar para o chat + Interface + Tema, Cores, Capturas de tela, Entrada + Segurança + Encriptação E2E, Confiar cegamente antes da verificação, Detecção de MITM + Notificações + Tamanho do arquivo, Compressão de imagem, Qualidade de vídeo + Confiar nos certificados CA do sistema + Política de privacidade + A integração de lista de contatos não está disponível + Entrar + Modo claro/escuro + Permitir capturas de tela + Permitir mensagens privadas + Reportar spam + Reportar e bloquear o spammer + A integração de chamada não está disponível! + Iniciar chat + Nenhum certificado de cliente selecionado! + Enviando + Recebendo + Download automático + Aparência + Conexão com o servidor + Sistema Operacional \ No newline at end of file From 96223dc7171e4242a7b7f9f742d56c644192b7c4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 3 Aug 2024 09:56:49 +0200 Subject: [PATCH 152/192] use bundled letsencrypt for uri link header checker --- .../http/HttpConnectionManager.java | 55 ++++++++++++++----- .../services/ChannelDiscoveryService.java | 32 +---------- .../conversations/ui/UriHandlerActivity.java | 20 ++++--- src/main/res/layout/activity_uri_handler.xml | 6 +- 4 files changed, 58 insertions(+), 55 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java index 27f5c3fc7bdcbb8f78aa00c5b964c0a3efee4ae6..13126f7eb401cc2835671890b67a645708d3d3d5 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java @@ -2,9 +2,24 @@ package eu.siacs.conversations.http; import static eu.siacs.conversations.utils.Random.SECURE_RANDOM; +import android.content.Context; import android.os.Build; import android.util.Log; +import eu.siacs.conversations.BuildConfig; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.TrustManagers; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.AbstractConnectionManager; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.TLSSocketFactory; + +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.ResponseBody; + import org.apache.http.conn.ssl.StrictHostnameVerifier; import java.io.IOException; @@ -14,7 +29,9 @@ import java.net.InetSocketAddress; import java.net.Proxy; import java.net.UnknownHostException; import java.security.KeyManagementException; +import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -24,18 +41,6 @@ import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; -import eu.siacs.conversations.BuildConfig; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Message; -import eu.siacs.conversations.services.AbstractConnectionManager; -import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.utils.TLSSocketFactory; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.ResponseBody; - public class HttpConnectionManager extends AbstractConnectionManager { private final List downloadConnections = new ArrayList<>(); @@ -43,7 +48,7 @@ public class HttpConnectionManager extends AbstractConnectionManager { public static final Executor EXECUTOR = Executors.newFixedThreadPool(4); - public static final OkHttpClient OK_HTTP_CLIENT; + private static final OkHttpClient OK_HTTP_CLIENT; static { OK_HTTP_CLIENT = new OkHttpClient.Builder() @@ -173,4 +178,28 @@ public class HttpConnectionManager extends AbstractConnectionManager { } return body.byteStream(); } + + + public static OkHttpClient okHttpClient(final Context context) { + final OkHttpClient.Builder builder = HttpConnectionManager.OK_HTTP_CLIENT.newBuilder(); + try { + final X509TrustManager trustManager; + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) { + trustManager = TrustManagers.defaultWithBundledLetsEncrypt(context); + } else { + trustManager = TrustManagers.createDefaultTrustManager(); + } + final SSLSocketFactory socketFactory = + new TLSSocketFactory(new X509TrustManager[] {trustManager}, SECURE_RANDOM); + builder.sslSocketFactory(socketFactory, trustManager); + } catch (final IOException + | KeyManagementException + | NoSuchAlgorithmException + | KeyStoreException + | CertificateException e) { + Log.d(Config.LOGTAG, "not reconfiguring service to work with bundled LetsEncrypt"); + throw new RuntimeException(e); + } + return builder.build(); + } } diff --git a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java index b9d43fa51523e3c8fd74aec716a6325b8063a170..5a8c58ac6d1fa1acfa3282ed22458fef9a83bb18 100644 --- a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java +++ b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java @@ -1,8 +1,6 @@ package eu.siacs.conversations.services; -import static eu.siacs.conversations.utils.Random.SECURE_RANDOM; -import android.os.Build; import android.util.Log; import androidx.annotation.NonNull; @@ -12,17 +10,16 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import eu.siacs.conversations.Config; -import eu.siacs.conversations.crypto.TrustManagers; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Room; import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.http.services.MuclumbusService; import eu.siacs.conversations.parser.IqParser; -import eu.siacs.conversations.utils.TLSSocketFactory; 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; @@ -33,10 +30,6 @@ import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import java.io.IOException; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -46,9 +39,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.X509TrustManager; - public class ChannelDiscoveryService { private final XmppConnectionService service; @@ -67,25 +57,7 @@ public class ChannelDiscoveryService { this.muclumbusService = null; return; } - final OkHttpClient.Builder builder = HttpConnectionManager.OK_HTTP_CLIENT.newBuilder(); - try { - final X509TrustManager trustManager; - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) { - trustManager = TrustManagers.defaultWithBundledLetsEncrypt(service); - } else { - trustManager = TrustManagers.createDefaultTrustManager(); - } - final SSLSocketFactory socketFactory = - new TLSSocketFactory(new X509TrustManager[] {trustManager}, SECURE_RANDOM); - builder.sslSocketFactory(socketFactory, trustManager); - } catch (final IOException - | KeyManagementException - | NoSuchAlgorithmException - | KeyStoreException - | CertificateException e) { - Log.d(Config.LOGTAG, "not reconfiguring service to work with bundled LetsEncrypt"); - throw new RuntimeException(e); - } + final OkHttpClient.Builder builder = HttpConnectionManager.okHttpClient(service).newBuilder(); if (service.useTorToConnect()) { builder.proxy(HttpConnectionManager.getProxy()); } diff --git a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java index f90918f49fb8c69758d71f92c9b9fe803abe08d1..55bf8b3adacd481fbbbf5a5ca6688ecae6b01ddd 100644 --- a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java @@ -5,7 +5,6 @@ import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.View; @@ -57,9 +56,7 @@ public class UriHandlerActivity extends BaseActivity { } public static void scan(final Activity activity, final boolean provisioning) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M - || ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) - == PackageManager.PERMISSION_GRANTED) { + if (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) { @@ -103,6 +100,7 @@ public class UriHandlerActivity extends BaseActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.binding = DataBindingUtil.setContentView(this, R.layout.activity_uri_handler); + Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); } @Override @@ -138,7 +136,7 @@ public class UriHandlerActivity extends BaseActivity { startActivity(intent); return true; } - if (accounts.size() == 0 + if (accounts.isEmpty() && xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y" .equalsIgnoreCase( @@ -154,7 +152,7 @@ public class UriHandlerActivity extends BaseActivity { return false; } - if (accounts.size() == 0) { + if (accounts.isEmpty()) { if (xmppUri.isValidJid()) { intent = SignupUtils.getSignUpIntent(this); intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); @@ -211,14 +209,14 @@ public class UriHandlerActivity extends BaseActivity { private void checkForLinkHeader(final HttpUrl url) { Log.d(Config.LOGTAG, "checking for link header on " + url); this.call = - HttpConnectionManager.OK_HTTP_CLIENT.newCall( + HttpConnectionManager.okHttpClient(this).newCall( new Request.Builder().url(url).head().build()); this.call.enqueue( new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { Log.d(Config.LOGTAG, "unable to check HTTP url", e); - showError(R.string.no_xmpp_adddress_found); + showErrorOnUiThread(R.string.no_xmpp_adddress_found); } @Override @@ -229,7 +227,7 @@ public class UriHandlerActivity extends BaseActivity { return; } } - showError(R.string.no_xmpp_adddress_found); + showErrorOnUiThread(R.string.no_xmpp_adddress_found); } }); } @@ -253,6 +251,10 @@ public class UriHandlerActivity extends BaseActivity { this.binding.error.setVisibility(View.VISIBLE); } + private void showErrorOnUiThread(@StringRes int error) { + runOnUiThread(()-> showError(error)); + } + private static Class findShareViaAccountClass() { try { return Class.forName("eu.siacs.conversations.ui.ShareViaAccountActivity"); diff --git a/src/main/res/layout/activity_uri_handler.xml b/src/main/res/layout/activity_uri_handler.xml index bc543076b6b499f397811f12c6d42ae4191871fb..1c0296ffcdd5419414d047af88ad22520573d171 100644 --- a/src/main/res/layout/activity_uri_handler.xml +++ b/src/main/res/layout/activity_uri_handler.xml @@ -4,7 +4,7 @@ + android:indeterminateTint="?colorPrimary" /> From cf92527afff6c2d57be15c5774b73785a239fc1e Mon Sep 17 00:00:00 2001 From: divergency Date: Sat, 3 Aug 2024 11:59:25 +0000 Subject: [PATCH 153/192] Translated using Weblate (Russian) Currently translated at 99.2% (1012 of 1020 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/ru/ --- src/main/res/values-ru/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index e8821bbc10c2aacb03d2b875962dc2a8ab2a62b9..721e08aa51cf461adf56ad5285a95e9f62a83ac2 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -274,7 +274,7 @@ Игнорировать Внимание: Если обновления присутствия не включены на обеих сторонах, это может привести к возникновению неожиданных проблем.\n\nПросмотрите сведения о контакте для проверки настроек обновлений присутствия. Безопасность - Разрешить исправление сообщений + Исправление сообщений Позволить контактам редактировать сообщения Расширенные настройки Пожалуйста, будьте осторожны с данными настройками @@ -464,8 +464,8 @@ Устанавливает статус \"Не беспокоить\", когда устройство в беззвучном режиме Не доступен в режиме вибрации Устанавливает статус \"Не беспокоить\", когда устройство в режиме вибрации - Расширенные настройки подключения - Показывать имя сервера и порт в настройках аккаунтов + Имя хоста и Порт + Показывать расширенные настройки соединения при настройке аккаунта xmpp.example.com Авторизироваться с помощью сертификата Не удалось прочитать сертификат From 019a09faa17af491c42232a15c1d9fc0ed79543c Mon Sep 17 00:00:00 2001 From: kazushi Date: Sun, 4 Aug 2024 07:14:16 +0000 Subject: [PATCH 154/192] Translated using Weblate (Japanese) Currently translated at 100.0% (2 of 2 strings) Translation: Conversations/App Store Metadata (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata-conversations/ja/ --- .../android/ja-JP/full_description.txt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/conversations/fastlane/metadata/android/ja-JP/full_description.txt diff --git a/src/conversations/fastlane/metadata/android/ja-JP/full_description.txt b/src/conversations/fastlane/metadata/android/ja-JP/full_description.txt new file mode 100644 index 0000000000000000000000000000000000000000..a6b13e23cf759a2894d11c5fdd69aaa7dd432493 --- /dev/null +++ b/src/conversations/fastlane/metadata/android/ja-JP/full_description.txt @@ -0,0 +1,39 @@ +簡単に使えて信頼でき、バッテリー使用量も抑えます。画像、グループ、エンドツーエンド暗号化に標準で対応。 + +設計原則: + +* セキュリティやプライバシーを犠牲にせず美しく簡単に使用できる +* 既存の確立されたプロトコルに準拠する +* Google アカウント、特に Google Cloud Messaging (GCM) を必須としない +* できるだけ最小の権限のみ要求する + +機能: + +* OMEMO または OpenPGP によるエンドツーエンド暗号化 +* 画像の送受信 +* 暗号化された音声・ビデオ通話 (DTLS-SRTP) +* デザインガイトライン準拠の直感的な UI +* 連絡先の画像/アバター +* デスクトップクライアントとの同期 +* Conferences (with support for bookmarks) +* Address book integration +* Multiple accounts / unified inbox +* Very low impact on battery life + +Conversations makes it very easy to create an account on the free conversations.im server. However Conversations will work with any other XMPP server as well. A lot of XMPP servers are run by volunteers and are free of charge. + +XMPP Features: + +Conversations works with every XMPP server out there. However XMPP is an extensible protocol. These extensions are standardized as well in so called XEP’s. Conversations supports a couple of those to make the overall user experience better. There is a chance that your current XMPP server does not support these extensions. Therefore to get the most out of Conversations you should consider either switching to an XMPP server that does or - even better - run your own XMPP server for you and your friends. + +These XEPs are - as of now: + +* XEP-0065: SOCKS5 Bytestreams (or mod_proxy65). Will be used to transfer files if both parties are behind a firewall (NAT). +* XEP-0163: Personal Eventing Protocol for avatars +* XEP-0191: Blocking command lets you blacklist spammers or block contacts without removing them from your roster. +* XEP-0198: Stream Management allows XMPP to survive small network outages and changes of the underlying TCP connection. +* XEP-0280: Message Carbons which automatically syncs the messages you send to your desktop client and thus allows you to switch seamlessly from your mobile client to your desktop client and back within one conversation. +* XEP-0237: Roster Versioning mainly to save bandwidth on poor mobile connections +* XEP-0313: Message Archive Management synchronize message history with the server. Catch up with messages that were sent while Conversations was offline. +* XEP-0352: Client State Indication lets the server know whether or not Conversations is in the background. Allows the server to save bandwidth by withholding unimportant packages. +* XEP-0363: HTTP File Upload allows you to share files in conferences and with offline contacts. Requires an additional component on your server. From b90f132de1e360d79da4235946f58e0be60b26de Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 4 Aug 2024 09:23:48 +0200 Subject: [PATCH 155/192] fixup: delete ja-JP full_description translation --- .../android/ja-JP/full_description.txt | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 src/conversations/fastlane/metadata/android/ja-JP/full_description.txt diff --git a/src/conversations/fastlane/metadata/android/ja-JP/full_description.txt b/src/conversations/fastlane/metadata/android/ja-JP/full_description.txt deleted file mode 100644 index a6b13e23cf759a2894d11c5fdd69aaa7dd432493..0000000000000000000000000000000000000000 --- a/src/conversations/fastlane/metadata/android/ja-JP/full_description.txt +++ /dev/null @@ -1,39 +0,0 @@ -簡単に使えて信頼でき、バッテリー使用量も抑えます。画像、グループ、エンドツーエンド暗号化に標準で対応。 - -設計原則: - -* セキュリティやプライバシーを犠牲にせず美しく簡単に使用できる -* 既存の確立されたプロトコルに準拠する -* Google アカウント、特に Google Cloud Messaging (GCM) を必須としない -* できるだけ最小の権限のみ要求する - -機能: - -* OMEMO または OpenPGP によるエンドツーエンド暗号化 -* 画像の送受信 -* 暗号化された音声・ビデオ通話 (DTLS-SRTP) -* デザインガイトライン準拠の直感的な UI -* 連絡先の画像/アバター -* デスクトップクライアントとの同期 -* Conferences (with support for bookmarks) -* Address book integration -* Multiple accounts / unified inbox -* Very low impact on battery life - -Conversations makes it very easy to create an account on the free conversations.im server. However Conversations will work with any other XMPP server as well. A lot of XMPP servers are run by volunteers and are free of charge. - -XMPP Features: - -Conversations works with every XMPP server out there. However XMPP is an extensible protocol. These extensions are standardized as well in so called XEP’s. Conversations supports a couple of those to make the overall user experience better. There is a chance that your current XMPP server does not support these extensions. Therefore to get the most out of Conversations you should consider either switching to an XMPP server that does or - even better - run your own XMPP server for you and your friends. - -These XEPs are - as of now: - -* XEP-0065: SOCKS5 Bytestreams (or mod_proxy65). Will be used to transfer files if both parties are behind a firewall (NAT). -* XEP-0163: Personal Eventing Protocol for avatars -* XEP-0191: Blocking command lets you blacklist spammers or block contacts without removing them from your roster. -* XEP-0198: Stream Management allows XMPP to survive small network outages and changes of the underlying TCP connection. -* XEP-0280: Message Carbons which automatically syncs the messages you send to your desktop client and thus allows you to switch seamlessly from your mobile client to your desktop client and back within one conversation. -* XEP-0237: Roster Versioning mainly to save bandwidth on poor mobile connections -* XEP-0313: Message Archive Management synchronize message history with the server. Catch up with messages that were sent while Conversations was offline. -* XEP-0352: Client State Indication lets the server know whether or not Conversations is in the background. Allows the server to save bandwidth by withholding unimportant packages. -* XEP-0363: HTTP File Upload allows you to share files in conferences and with offline contacts. Requires an additional component on your server. From f764b24ffc1089cad147887053d8b64d8207b248 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 5 Aug 2024 10:59:59 +0200 Subject: [PATCH 156/192] bump bc version. refactor XmppDomainVerifier to work with new api --- build.gradle | 4 +- .../crypto/XmppDomainVerifier.java | 74 ++++++++++++------- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/build.gradle b/build.gradle index 2086c8c0274c66104e400be65c6693ecf2cd5b46..0b3510414ab0fbd069de75d283af023ec7ad2b3d 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ dependencies { implementation "androidx.emoji2:emoji2:1.4.0" freeImplementation "androidx.emoji2:emoji2-bundled:1.4.0" - implementation 'org.bouncycastle:bcmail-jdk15on:1.64' + implementation 'org.bouncycastle:bcmail-jdk18on:1.78.1' //zxing stopped supporting Java 7 so we have to stick with 3.3.3 //https://github.com/zxing/zxing/issues/1170 implementation 'com.google.zxing:core:3.3.3' @@ -243,7 +243,7 @@ android { } packagingOptions { resources { - excludes += ['META-INF/BCKEY.DSA', 'META-INF/BCKEY.SF'] + excludes += ['META-INF/BCKEY.DSA', 'META-INF/BCKEY.SF', 'META-INF/versions/9/OSGI-INF/MANIFEST.MF'] } } lint { diff --git a/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java b/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java index ed17c5695bcf4cbca881dd97882a39c376fb34c1..4e43dd93ab487f9159c7261d3aca2c8509b9b5a4 100644 --- a/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java +++ b/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java @@ -3,11 +3,13 @@ package eu.siacs.conversations.crypto; import android.util.Log; import android.util.Pair; +import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; +import org.bouncycastle.asn1.ASN1Object; import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.DERIA5String; -import org.bouncycastle.asn1.DERTaggedObject; import org.bouncycastle.asn1.DERUTF8String; import org.bouncycastle.asn1.DLSequence; import org.bouncycastle.asn1.x500.RDN; @@ -37,13 +39,15 @@ public class XmppDomainVerifier { private static final String SRV_NAME = "1.3.6.1.5.5.7.8.7"; private static final String XMPP_ADDR = "1.3.6.1.5.5.7.8.5"; - private static List getCommonNames(X509Certificate certificate) { + private static List getCommonNames(final X509Certificate certificate) { List domains = new ArrayList<>(); try { X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); RDN[] rdns = x500name.getRDNs(BCStyle.CN); for (int i = 0; i < rdns.length; ++i) { - domains.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue())); + domains.add( + IETFUtils.valueToString( + x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue())); } return domains; } catch (CertificateEncodingException e) { @@ -51,26 +55,26 @@ public class XmppDomainVerifier { } } - private static Pair parseOtherName(byte[] otherName) { + private static Pair parseOtherName(final byte[] otherName) { try { ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(otherName); - if (asn1Primitive instanceof DERTaggedObject) { - ASN1Primitive inner = ((DERTaggedObject) asn1Primitive).getObject(); - if (inner instanceof DLSequence) { - DLSequence sequence = (DLSequence) inner; - if (sequence.size() >= 2 && sequence.getObjectAt(1) instanceof DERTaggedObject) { - String oid = sequence.getObjectAt(0).toString(); - ASN1Primitive value = ((DERTaggedObject) sequence.getObjectAt(1)).getObject(); - if (value instanceof DERUTF8String) { - return new Pair<>(oid, ((DERUTF8String) value).getString()); - } else if (value instanceof DERIA5String) { - return new Pair<>(oid, ((DERIA5String) value).getString()); + if (asn1Primitive instanceof ASN1TaggedObject taggedObject) { + final ASN1Object inner = taggedObject.getBaseObject(); + if (inner instanceof DLSequence sequence) { + if (sequence.size() >= 2 + && sequence.getObjectAt(1) instanceof ASN1TaggedObject evenInner) { + final String oid = sequence.getObjectAt(0).toString(); + final ASN1Object value = evenInner.getBaseObject(); + if (value instanceof DERUTF8String derutf8String) { + return new Pair<>(oid, derutf8String.getString()); + } else if (value instanceof DERIA5String deria5String) { + return new Pair<>(oid, deria5String.getString()); } } } } return null; - } catch (IOException e) { + } catch (final IOException e) { return null; } } @@ -98,14 +102,15 @@ public class XmppDomainVerifier { return false; } - public boolean verify(final String unicodeDomain, final String unicodeHostname, SSLSession sslSession) throws SSLPeerUnverifiedException { + public boolean verify( + final String unicodeDomain, final String unicodeHostname, SSLSession sslSession) + throws SSLPeerUnverifiedException { final String domain = IDN.toASCII(unicodeDomain); final String hostname = unicodeHostname == null ? null : IDN.toASCII(unicodeHostname); final Certificate[] chain = sslSession.getPeerCertificates(); - if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) { + if (chain.length == 0 || !(chain[0] instanceof X509Certificate certificate)) { return false; } - final X509Certificate certificate = (X509Certificate) chain[0]; final List commonNames = getCommonNames(certificate); if (isSelfSigned(certificate)) { if (commonNames.size() == 1 && matchDomain(domain, commonNames)) { @@ -115,11 +120,11 @@ public class XmppDomainVerifier { } try { final ValidDomains validDomains = parseValidDomains(certificate); - Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + validDomains.srvNames + " xmppAddrs: " + validDomains.xmppAddrs + " domains:" + validDomains.domains); + Log.d(LOGTAG, "searching for " + domain + " in " + validDomains); if (hostname != null) { Log.d(LOGTAG, "also trying to verify hostname " + hostname); } - return validDomains.xmppAddrs.contains(domain) + return validDomains.xmppAddresses.contains(domain) || validDomains.srvNames.contains("_xmpp-client." + domain) || matchDomain(domain, validDomains.domains) || (hostname != null && matchDomain(hostname, validDomains.domains)); @@ -128,7 +133,8 @@ public class XmppDomainVerifier { } } - public static ValidDomains parseValidDomains(final X509Certificate certificate) throws CertificateParsingException { + public static ValidDomains parseValidDomains(final X509Certificate certificate) + throws CertificateParsingException { final List commonNames = getCommonNames(certificate); final Collection> alternativeNames = certificate.getSubjectAlternativeNames(); final List xmppAddrs = new ArrayList<>(); @@ -148,7 +154,9 @@ public class XmppDomainVerifier { xmppAddrs.add(otherName.second.toLowerCase(Locale.US)); break; default: - Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second); + Log.d( + LOGTAG, + "oid: " + otherName.first + " value: " + otherName.second); } } } else if (type == 2) { @@ -159,30 +167,40 @@ public class XmppDomainVerifier { } } } - if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) { + if (srvNames.isEmpty() && xmppAddrs.isEmpty() && domains.isEmpty()) { domains.addAll(commonNames); } return new ValidDomains(xmppAddrs, srvNames, domains); } public static final class ValidDomains { - final List xmppAddrs; + final List xmppAddresses; final List srvNames; final List domains; - private ValidDomains(List xmppAddrs, List srvNames, List domains) { - this.xmppAddrs = xmppAddrs; + private ValidDomains( + List xmppAddresses, List srvNames, List domains) { + this.xmppAddresses = xmppAddresses; this.srvNames = srvNames; this.domains = domains; } public List all() { ImmutableList.Builder all = new ImmutableList.Builder<>(); - all.addAll(xmppAddrs); + all.addAll(xmppAddresses); all.addAll(srvNames); all.addAll(domains); return all.build(); } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("xmppAddresses", xmppAddresses) + .add("srvNames", srvNames) + .add("domains", domains) + .toString(); + } } private boolean isSelfSigned(X509Certificate certificate) { From 6633afb768d6794ef5cd4345a651ef614b47d07b Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 5 Aug 2024 11:00:55 +0200 Subject: [PATCH 157/192] bump zxing --- build.gradle | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 0b3510414ab0fbd069de75d283af023ec7ad2b3d..167713db3adce0195d2cce3dc754c7918045294c 100644 --- a/build.gradle +++ b/build.gradle @@ -60,9 +60,7 @@ dependencies { freeImplementation "androidx.emoji2:emoji2-bundled:1.4.0" implementation 'org.bouncycastle:bcmail-jdk18on:1.78.1' - //zxing stopped supporting Java 7 so we have to stick with 3.3.3 - //https://github.com/zxing/zxing/issues/1170 - implementation 'com.google.zxing:core:3.3.3' + implementation 'com.google.zxing:core:3.5.3' implementation 'org.minidns:minidns-client:1.0.4' implementation 'org.minidns:minidns-dnssec:1.0.4' implementation 'me.leolin:ShortcutBadger:1.1.22@aar' From d95b30f71661644ccd43da6380c9e1706df91204 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 5 Aug 2024 12:55:37 +0200 Subject: [PATCH 158/192] show toast after failure to disable video --- .../eu/siacs/conversations/ui/RtpSessionActivity.java | 11 ++++++++--- src/main/res/values/strings.xml | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 06cf4e5140654a82ab66a3b78372414139034f78..2c3bbcc1a8d189c1bafad0bc302cea8ca88b92da 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -1126,7 +1126,7 @@ public class RtpSessionActivity extends XmppActivity MainThreadExecutor.getInstance()); } - private void enableVideo(View view) { + private void enableVideo(final View view) { try { requireRtpConnection().setVideoEnabled(true); } catch (final IllegalStateException e) { @@ -1136,14 +1136,19 @@ public class RtpSessionActivity extends XmppActivity updateInCallButtonConfigurationVideo(true, requireRtpConnection().isCameraSwitchable()); } - private void disableVideo(View view) { + private void disableVideo(final View view) { final JingleRtpConnection rtpConnection = requireRtpConnection(); final ContentAddition pending = rtpConnection.getPendingContentAddition(); if (pending != null && pending.direction == ContentAddition.Direction.OUTGOING) { rtpConnection.retractContentAdd(); return; } - requireRtpConnection().setVideoEnabled(false); + try { + requireRtpConnection().setVideoEnabled(false); + } catch (final IllegalStateException e) { + Toast.makeText(this, R.string.could_not_disable_video, Toast.LENGTH_SHORT).show(); + return; + } updateInCallButtonConfigurationVideo(false, requireRtpConnection().isCameraSwitchable()); } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 658b78983578c6509142fc134a9b3da29046fe16..e8f258ea4069c3b47e2862d8983872771750b819 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -992,6 +992,7 @@ No active accounts support this feature The backup has been started. You’ll get a notification once it has been completed. Unable to enable video. + Could not disable video. Plain text document Account registrations are not supported No XMPP address found From 925868f5e90a4c6854f2de8f24b4b2463b386f7a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 6 Aug 2024 10:33:37 +0200 Subject: [PATCH 159/192] add content descriptions for image buttons --- .../ui/ConferenceDetailsActivity.java | 5 +++++ .../ui/StartConversationActivity.java | 1 + src/main/res/layout/activity_edit_account.xml | 17 +++++++++++------ src/main/res/layout/activity_muc_details.xml | 3 +++ .../layout/activity_publish_profile_picture.xml | 3 ++- src/main/res/values/strings.xml | 6 ++++++ 6 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 928a625d84e241b2a8756b44c3cc8517c628f1bf..2cd9cb5d32305f25eed9023339173deb5c097d06 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -211,6 +211,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers intent.putExtra("uuid", mConversation.getUuid()); startActivity(intent); }); + this.binding.editMucNameButton.setContentDescription(getString(R.string.edit_name_and_topic)); this.binding.editMucNameButton.setOnClickListener(this::onMucEditButtonClicked); this.binding.mucEditTitle.addTextChangedListener(this); this.binding.mucEditSubject.addTextChangedListener(this); @@ -288,6 +289,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers this.binding.mucEditor.setVisibility(View.VISIBLE); this.binding.mucDisplay.setVisibility(View.GONE); this.binding.editMucNameButton.setImageResource(R.drawable.ic_cancel_24dp); + this.binding.editMucNameButton.setContentDescription(getString(R.string.cancel)); final String name = mucOptions.getName(); this.binding.mucEditTitle.setText(""); final boolean owner = mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER); @@ -322,6 +324,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers this.binding.mucEditor.setVisibility(View.GONE); this.binding.mucDisplay.setVisibility(View.VISIBLE); this.binding.editMucNameButton.setImageResource(R.drawable.ic_edit_24dp); + this.binding.editMucNameButton.setContentDescription(getString(R.string.edit_name_and_topic)); } private void onMucInfoUpdated(String subject, String name) { @@ -630,8 +633,10 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers boolean nameChanged = changed(binding.mucEditTitle.getEditableText().toString(), mucOptions.getName()); if (subjectChanged || nameChanged) { this.binding.editMucNameButton.setImageResource(R.drawable.ic_save_24dp); + this.binding.editMucNameButton.setContentDescription(getString(R.string.save)); } else { this.binding.editMucNameButton.setImageResource(R.drawable.ic_cancel_24dp); + this.binding.editMucNameButton.setContentDescription(getString(R.string.cancel)); } } } diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 8710a746facff4aaa94341cdfc843cb50bde61d6..e086b3bbb7a50d3ba2deae68b9b518f8652e949f 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -418,6 +418,7 @@ public class StartConversationActivity extends XmppActivity .create(); speedDialView.addActionItem(actionItem); } + speedDialView.setContentDescription(getString(R.string.add_contact_or_create_or_join_group_chat)); } public static boolean isValidJid(String input) { diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml index 76c8deff0052dd755f7d46013e927aaf30c9f0e7..cab6c3a72de4ced8970f910abdf1b70a257b92e2 100644 --- a/src/main/res/layout/activity_edit_account.xml +++ b/src/main/res/layout/activity_edit_account.xml @@ -107,8 +107,8 @@ android:id="@+id/hostname_layout" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_weight="0.7" android:layout_marginEnd="4sp" + android:layout_weight="0.7" android:hint="@string/account_settings_hostname"> + android:visibility="gone" + tools:visibility="visible"> + android:visibility="gone" + tools:visibility="visible"> @@ -554,6 +557,7 @@ android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:background="?attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/delete_pgp_key" android:padding="@dimen/image_button_padding" android:src="@drawable/ic_delete_24dp" android:visibility="visible" /> @@ -600,7 +604,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="?attr/selectableItemBackgroundBorderless" - android:contentDescription="@string/copy_omemo_clipboard_description" + android:contentDescription="@string/show_qr_code" android:padding="@dimen/image_button_padding" android:src="@drawable/ic_qr_code_24dp" android:visibility="visible" /> @@ -628,7 +632,8 @@ android:layout_marginTop="@dimen/activity_vertical_margin" android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginBottom="@dimen/activity_vertical_margin" - android:visibility="gone"> + android:visibility="gone" + tools:visibility="visible"> @@ -307,6 +308,7 @@ android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:background="?attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/edit_nick" android:padding="@dimen/image_button_padding" android:src="@drawable/ic_edit_24dp" /> @@ -334,6 +336,7 @@ android:layout_centerVertical="true" android:layout_gravity="center_horizontal" android:background="?attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/change_notification_settings" android:padding="@dimen/image_button_padding" android:src="@drawable/ic_notifications_24dp" /> diff --git a/src/main/res/layout/activity_publish_profile_picture.xml b/src/main/res/layout/activity_publish_profile_picture.xml index a6c7c1fc01c068ccebbd08afa4c189ae8f856a5e..0eb4dd516ca6e8af33a5682fe388476650859e1e 100644 --- a/src/main/res/layout/activity_publish_profile_picture.xml +++ b/src/main/res/layout/activity_publish_profile_picture.xml @@ -45,7 +45,8 @@ + android:layout_height="@dimen/publish_avatar_size" + android:contentDescription="@string/your_avatar_tap_to_select_new_avatar" /> All chats This chat Your avatar + Your avatar. Tap to select new avatar from gallery. Avatar for %s Encrypted with OMEMO Encrypted with OpenPGP @@ -1064,4 +1065,9 @@ Allow this app to show incoming call notifications that take up the full screen when the device is locked. Unsupported operation Allow private messages + Edit nick + Delete OpenPGP key + Edit name and topic + Change configuration + Change notification settings From 791f0cd2acb216b3fb74d296decfef2a8bdabcde Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 6 Aug 2024 10:53:46 +0200 Subject: [PATCH 160/192] add content descriptions for call action buttons --- .../conversations/ui/RtpSessionActivity.java | 18 ++++++++++++++++++ src/main/res/values/strings.xml | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 2c3bbcc1a8d189c1bafad0bc302cea8ca88b92da..f7818e2921f4c619159c6e2880fde82e6571340e 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -1051,13 +1051,19 @@ public class RtpSessionActivity extends XmppActivity case EARPIECE -> { this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_off_24dp); if (numberOfChoices >= 2) { + this.binding.inCallActionRight.setContentDescription( + getString(R.string.call_is_using_earpiece_tap_to_switch_to_speaker)); this.binding.inCallActionRight.setOnClickListener(this::switchToSpeaker); } else { + this.binding.inCallActionRight.setContentDescription( + getString(R.string.call_is_using_earpiece)); this.binding.inCallActionRight.setOnClickListener(null); this.binding.inCallActionRight.setClickable(false); } } case WIRED_HEADSET -> { + this.binding.inCallActionRight.setContentDescription( + getString(R.string.call_is_using_wired_headset)); this.binding.inCallActionRight.setImageResource(R.drawable.ic_headset_mic_24dp); this.binding.inCallActionRight.setOnClickListener(null); this.binding.inCallActionRight.setClickable(false); @@ -1065,13 +1071,19 @@ public class RtpSessionActivity extends XmppActivity case SPEAKER_PHONE -> { this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_up_24dp); if (numberOfChoices >= 2) { + this.binding.inCallActionRight.setContentDescription( + getString(R.string.call_is_using_speaker_tap_to_switch_to_earpiece)); this.binding.inCallActionRight.setOnClickListener(this::switchToEarpiece); } else { + this.binding.inCallActionRight.setContentDescription( + getString(R.string.call_is_using_speaker)); this.binding.inCallActionRight.setOnClickListener(null); this.binding.inCallActionRight.setClickable(false); } } case BLUETOOTH -> { + this.binding.inCallActionRight.setContentDescription( + getString(R.string.call_is_using_bluetooth)); this.binding.inCallActionRight.setImageResource(R.drawable.ic_bluetooth_audio_24dp); this.binding.inCallActionRight.setOnClickListener(null); this.binding.inCallActionRight.setClickable(false); @@ -1089,15 +1101,21 @@ public class RtpSessionActivity extends XmppActivity R.drawable.ic_flip_camera_android_24dp); this.binding.inCallActionFarRight.setVisibility(View.VISIBLE); this.binding.inCallActionFarRight.setOnClickListener(this::switchCamera); + this.binding.inCallActionFarRight.setContentDescription( + getString(R.string.flip_camera)); } else { this.binding.inCallActionFarRight.setVisibility(View.GONE); } if (videoEnabled) { this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_24dp); this.binding.inCallActionRight.setOnClickListener(this::disableVideo); + this.binding.inCallActionRight.setContentDescription( + getString(R.string.video_is_enabled_tap_to_disable)); } else { this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_off_24dp); this.binding.inCallActionRight.setOnClickListener(this::enableVideo); + this.binding.inCallActionRight.setContentDescription( + getString(R.string.video_is_disabled_tap_to_enable)); } } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 5074e6eac46532a3317f3f988ad0adb7c28aae21..d712c3d3c9db542bc7c1897e101bb6ab133e2e48 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -1070,4 +1070,13 @@ Edit name and topic Change configuration Change notification settings + Call is using earpiece. Tap to switch to speaker. + Call is using earpiece. + Call is using wired headset + Call is using speaker. Tap to switch to earpiece. + Call is using speaker. + Call is using bluetooth. + Flip camera + Video is enabled. Tap to disable. + Video is disabled. Tap to enabled. From a5496a6f4d2f8d9510a0a4997ca045111a534a99 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 6 Aug 2024 15:12:16 +0200 Subject: [PATCH 161/192] fix typo --- src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index d712c3d3c9db542bc7c1897e101bb6ab133e2e48..d3c501f486e6f181a8d7af73d58360ede69a755c 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -1078,5 +1078,5 @@ Call is using bluetooth. Flip camera Video is enabled. Tap to disable. - Video is disabled. Tap to enabled. + Video is disabled. Tap to enable. From 4a238bf243da0eae57ebe148b98a2294a177bb65 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 7 Aug 2024 12:07:51 +0200 Subject: [PATCH 162/192] reset quick start after invalid mechanism --- .../conversations/xmpp/XmppConnection.java | 31 +++++-- .../xmpp/model/AuthenticationFailure.java | 18 ++++ .../android/xmpp/model/sasl/Failure.java | 11 +++ .../android/xmpp/model/sasl/SaslError.java | 89 +++++++++++++++++++ .../xmpp/model/sasl2/Authenticate.java | 3 +- .../android/xmpp/model/sasl2/Failure.java | 12 +++ 6 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 src/main/java/im/conversations/android/xmpp/model/AuthenticationFailure.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/Failure.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl/SaslError.java create mode 100644 src/main/java/im/conversations/android/xmpp/model/sasl2/Failure.java diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 1b5269b274d4e842182425ddb555146f35382e36..31f4ac32a371038e3773bbbfb6c8c488900af14d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -63,6 +63,7 @@ import eu.siacs.conversations.xmpp.bind.Bind2; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived; +import im.conversations.android.xmpp.model.AuthenticationFailure; import im.conversations.android.xmpp.model.AuthenticationRequest; import im.conversations.android.xmpp.model.AuthenticationStreamFeature; import im.conversations.android.xmpp.model.StreamElement; @@ -75,8 +76,10 @@ import im.conversations.android.xmpp.model.fast.Fast; import im.conversations.android.xmpp.model.fast.RequestToken; import im.conversations.android.xmpp.model.jingle.Jingle; import im.conversations.android.xmpp.model.sasl.Auth; +import im.conversations.android.xmpp.model.sasl.Failure; import im.conversations.android.xmpp.model.sasl.Mechanisms; import im.conversations.android.xmpp.model.sasl.Response; +import im.conversations.android.xmpp.model.sasl.SaslError; import im.conversations.android.xmpp.model.sasl.Success; import im.conversations.android.xmpp.model.sasl2.Authenticate; import im.conversations.android.xmpp.model.sasl2.Authentication; @@ -616,8 +619,13 @@ public class XmppConnection implements Runnable { if (processSuccess(success)) { break; } - } else if (nextTag.isStart("failure")) { - final Element failure = tagReader.readElement(nextTag); + } else if (nextTag.isStart("failure", Namespace.SASL)) { + final var failure = tagReader.readElement(nextTag, Failure.class); + processFailure(failure); + } else if (nextTag.isStart("failure", Namespace.SASL_2)) { + final var failure = + tagReader.readElement( + nextTag, im.conversations.android.xmpp.model.sasl2.Failure.class); processFailure(failure); } else if (nextTag.isStart("continue", Namespace.SASL_2)) { // two step sasl2 - we don’t support this yet @@ -962,7 +970,7 @@ public class XmppConnection implements Runnable { } } - private void processFailure(final Element failure) throws IOException { + private void processFailure(final AuthenticationFailure failure) throws IOException { final SaslMechanism.Version version; try { version = SaslMechanism.Version.of(failure); @@ -976,10 +984,21 @@ public class XmppConnection implements Runnable { account.resetFastToken(); mXmppConnectionService.databaseBackend.updateAccount(account); } - if (failure.hasChild("temporary-auth-failure")) { + final var errorCondition = failure.getErrorCondition(); + if (errorCondition instanceof SaslError.InvalidMechanism + || errorCondition instanceof SaslError.MechanismTooWeak) { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": invalid or too weak mechanism. resetting quick start"); + if (account.setOption(Account.OPTION_QUICKSTART_AVAILABLE, false)) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } + throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); + } else if (errorCondition instanceof SaslError.TemporaryAuthFailure) { throw new StateChangingException(Account.State.TEMPORARY_AUTH_FAILURE); - } else if (failure.hasChild("account-disabled")) { - final String text = failure.findChildContent("text"); + } else if (errorCondition instanceof SaslError.AccountDisabled) { + final String text = failure.getText(); if (Strings.isNullOrEmpty(text)) { throw new StateChangingException(Account.State.UNAUTHORIZED); } diff --git a/src/main/java/im/conversations/android/xmpp/model/AuthenticationFailure.java b/src/main/java/im/conversations/android/xmpp/model/AuthenticationFailure.java new file mode 100644 index 0000000000000000000000000000000000000000..7d6790a6449d08e579f36197ce021f9be3eda54b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/AuthenticationFailure.java @@ -0,0 +1,18 @@ +package im.conversations.android.xmpp.model; + +import im.conversations.android.xmpp.model.sasl.SaslError; + +public abstract class AuthenticationFailure extends StreamElement { + + protected AuthenticationFailure(Class clazz) { + super(clazz); + } + + public SaslError getErrorCondition() { + return this.getExtension(SaslError.class); + } + + public String getText() { + return this.findChildContent("text"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Failure.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Failure.java new file mode 100644 index 0000000000000000000000000000000000000000..1db7029b502ed50541e4fabd8b5fb0424f4f2f32 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Failure.java @@ -0,0 +1,11 @@ +package im.conversations.android.xmpp.model.sasl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.AuthenticationFailure; + +@XmlElement +public class Failure extends AuthenticationFailure { + public Failure() { + super(Failure.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/SaslError.java b/src/main/java/im/conversations/android/xmpp/model/sasl/SaslError.java new file mode 100644 index 0000000000000000000000000000000000000000..54a26709bc37b03c15cd6c8076ce7d982f96a74d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/SaslError.java @@ -0,0 +1,89 @@ +package im.conversations.android.xmpp.model.sasl; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +public class SaslError extends Extension { + + private SaslError(final Class clazz) { + super(clazz); + } + + @XmlElement + public static class Aborted extends SaslError { + public Aborted() { + super(Aborted.class); + } + } + + @XmlElement + public static class AccountDisabled extends SaslError { + public AccountDisabled() { + super(AccountDisabled.class); + } + } + + @XmlElement + public static class CredentialsExpired extends SaslError { + public CredentialsExpired() { + super(CredentialsExpired.class); + } + } + + @XmlElement + public static class EncryptionRequired extends SaslError { + public EncryptionRequired() { + super(EncryptionRequired.class); + } + } + + @XmlElement + public static class IncorrectEncoding extends SaslError { + public IncorrectEncoding() { + super(IncorrectEncoding.class); + } + } + + @XmlElement + public static class InvalidAuthzid extends SaslError { + public InvalidAuthzid() { + super(InvalidAuthzid.class); + } + } + + @XmlElement + public static class InvalidMechanism extends SaslError { + public InvalidMechanism() { + super(InvalidMechanism.class); + } + } + + @XmlElement + public static class MalformedRequest extends SaslError { + public MalformedRequest() { + super(MalformedRequest.class); + } + } + + @XmlElement + public static class MechanismTooWeak extends SaslError { + public MechanismTooWeak() { + super(MechanismTooWeak.class); + } + } + + @XmlElement + public static class NotAuthorized extends SaslError { + + public NotAuthorized() { + super(NotAuthorized.class); + } + } + + @XmlElement + public static class TemporaryAuthFailure extends SaslError { + public TemporaryAuthFailure() { + super(TemporaryAuthFailure.class); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java index c709a779dd4614b9ec0f5e5dc2e6adf549004819..ac99ed346d49d0c15f3601b772f28c5be07431fb 100644 --- a/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java @@ -1,10 +1,9 @@ package im.conversations.android.xmpp.model.sasl2; import eu.siacs.conversations.crypto.sasl.SaslMechanism; -import eu.siacs.conversations.xmpp.XmppConnection; + import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.AuthenticationRequest; -import im.conversations.android.xmpp.model.StreamElement; @XmlElement public class Authenticate extends AuthenticationRequest { diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Failure.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Failure.java new file mode 100644 index 0000000000000000000000000000000000000000..bb0e327d9b28e624bbaa6ec6863c6dc8e2672fc5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Failure.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.sasl2; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.AuthenticationFailure; + +@XmlElement +public class Failure extends AuthenticationFailure { + + public Failure() { + super(Failure.class); + } +} From 5e40d7362de6129fbc2e0835c4f3d7a30d4567d9 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 7 Aug 2024 14:32:33 +0200 Subject: [PATCH 163/192] reset quick start setting when account is disabled under some circumstances Conversations wont upgrade to 'better' SASL mechanisms once a mechanism has been selected for quick start (and remains available). disabling an account now resets the quick start availability so upon the next reconnect a new decision can be made --- .../eu/siacs/conversations/ui/ManageAccountActivity.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java index 2f4c81671ca459553156fe6a8736f276c1bc0be3..6ab32682fd866ac7d906d058220fdbe2ea7598a4 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -109,7 +109,6 @@ public class ManageAccountActivity extends XmppActivity registerForContextMenu(binding.accountList); } - @Override public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) { if (selectedAccount != null) { @@ -352,8 +351,14 @@ public class ManageAccountActivity extends XmppActivity } } - private void disableAccount(Account account) { + private void disableAccount(final Account account) { account.setOption(Account.OPTION_DISABLED, true); + if (account.setOption(Account.OPTION_QUICKSTART_AVAILABLE, false)) { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": 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(); } From f716e983ca97720baefc8aa01d1543eddd2bca65 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 7 Aug 2024 17:12:52 +0200 Subject: [PATCH 164/192] show login mechanism, sasl 2, bind 2 in server info --- .../conversations/ui/EditAccountActivity.java | 1208 ++++++++++------- .../conversations/xmpp/XmppConnection.java | 15 + src/main/res/layout/activity_edit_account.xml | 103 +- src/main/res/values/strings.xml | 3 + 4 files changed, 836 insertions(+), 493 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index aed19cf6ff59c8c003def262faf0a176fdc6247e..0e710eaed96d083449b127829ef68ea57202b262 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -40,6 +40,7 @@ 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; @@ -88,8 +89,14 @@ import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, OnUpdateBlocklist, - OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched { +public class EditAccountActivity extends OmemoActivity + implements OnAccountUpdate, + OnUpdateBlocklist, + OnKeyStatusUpdated, + OnCaptchaRequested, + KeyChainAliasCallback, + XmppConnectionService.OnShowErrorToast, + XmppConnectionService.OnMamPreferencesFetched { public static final String EXTRA_OPENED_FROM_NOTIFICATION = "opened_from_notification"; public static final String EXTRA_FORCE_REGISTER = "force_register"; @@ -105,37 +112,44 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat private boolean mUsernameMode = false; private boolean mShowOptions = false; private Account mAccount; - private final OnClickListener mCancelButtonClickListener = v -> { - deleteAccountAndReturnIfNecessary(); - finish(); - }; - private final UiCallback mAvatarFetchCallback = new UiCallback() { + private final OnClickListener mCancelButtonClickListener = + v -> { + deleteAccountAndReturnIfNecessary(); + finish(); + }; + private final UiCallback mAvatarFetchCallback = + new UiCallback() { - @Override - public void userInputRequired(final PendingIntent pi, final Avatar avatar) { - finishInitialSetup(avatar); - } + @Override + public void userInputRequired(final PendingIntent pi, final Avatar avatar) { + finishInitialSetup(avatar); + } - @Override - public void success(final Avatar avatar) { - finishInitialSetup(avatar); - } + @Override + public void success(final Avatar avatar) { + finishInitialSetup(avatar); + } - @Override - public void error(final int errorCode, final Avatar avatar) { - finishInitialSetup(avatar); - } - }; - private final OnClickListener mAvatarClickListener = new OnClickListener() { - @Override - public void onClick(final View view) { - if (mAccount != null) { - final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toEscapedString()); - startActivity(intent); - } - } - }; + @Override + public void error(final int errorCode, final Avatar avatar) { + finishInitialSetup(avatar); + } + }; + private final OnClickListener mAvatarClickListener = + new OnClickListener() { + @Override + public void onClick(final View view) { + if (mAccount != null) { + final Intent intent = + new Intent( + getApplicationContext(), + PublishProfilePictureActivity.class); + intent.putExtra( + EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toEscapedString()); + startActivity(intent); + } + } + }; private String messageFingerprint; private boolean mFetchingAvatar = false; private Toast mFetchingMamPrefsToast; @@ -144,209 +158,263 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat private XmppUri pendingUri = null; private boolean mUseTor; private ActivityEditAccountBinding binding; - private final OnClickListener mSaveButtonClickListener = new OnClickListener() { - - @Override - public void onClick(final View v) { - final String password = binding.accountPassword.getText().toString(); - final boolean wasDisabled = mAccount != null && mAccount.getStatus() == Account.State.DISABLED; - final boolean accountInfoEdited = accountInfoEdited(); - - if (mInitMode && mAccount != null) { - mAccount.setOption(Account.OPTION_DISABLED, false); - } - if (mAccount != null && Arrays.asList(Account.State.DISABLED, Account.State.LOGGED_OUT).contains(mAccount.getStatus()) && !accountInfoEdited) { - mAccount.setOption(Account.OPTION_SOFT_DISABLED, false); - mAccount.setOption(Account.OPTION_DISABLED, false); - if (!xmppConnectionService.updateAccount(mAccount)) { - Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show(); - } - return; - } - final boolean registerNewAccount; - if (mForceRegister != null) { - registerNewAccount = mForceRegister; - } else { - registerNewAccount = binding.accountRegisterNew.isChecked() && !Config.DISALLOW_REGISTRATION_IN_UI; - } - if (mUsernameMode && binding.accountJid.getText().toString().contains("@")) { - binding.accountJidLayout.setError(getString(R.string.invalid_username)); - removeErrorsOnAllBut(binding.accountJidLayout); - binding.accountJid.requestFocus(); - return; - } + private final OnClickListener mSaveButtonClickListener = + new OnClickListener() { + + @Override + public void onClick(final View v) { + final String password = binding.accountPassword.getText().toString(); + final boolean wasDisabled = + mAccount != null && mAccount.getStatus() == Account.State.DISABLED; + final boolean accountInfoEdited = accountInfoEdited(); + + if (mInitMode && mAccount != null) { + mAccount.setOption(Account.OPTION_DISABLED, false); + } + if (mAccount != null + && Arrays.asList(Account.State.DISABLED, Account.State.LOGGED_OUT) + .contains(mAccount.getStatus()) + && !accountInfoEdited) { + mAccount.setOption(Account.OPTION_SOFT_DISABLED, false); + mAccount.setOption(Account.OPTION_DISABLED, false); + if (!xmppConnectionService.updateAccount(mAccount)) { + Toast.makeText( + EditAccountActivity.this, + R.string.unable_to_update_account, + Toast.LENGTH_SHORT) + .show(); + } + return; + } + final boolean registerNewAccount; + if (mForceRegister != null) { + registerNewAccount = mForceRegister; + } else { + registerNewAccount = + binding.accountRegisterNew.isChecked() + && !Config.DISALLOW_REGISTRATION_IN_UI; + } + if (mUsernameMode && binding.accountJid.getText().toString().contains("@")) { + binding.accountJidLayout.setError(getString(R.string.invalid_username)); + removeErrorsOnAllBut(binding.accountJidLayout); + binding.accountJid.requestFocus(); + return; + } - XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection(); - final boolean startOrbot = mAccount != null && mAccount.getStatus() == Account.State.TOR_NOT_AVAILABLE; - if (startOrbot) { - if (TorServiceUtils.isOrbotInstalled(EditAccountActivity.this)) { - TorServiceUtils.startOrbot(EditAccountActivity.this, REQUEST_ORBOT); - } else { - TorServiceUtils.downloadOrbot(EditAccountActivity.this, REQUEST_ORBOT); - } - return; - } + XmppConnection connection = + mAccount == null ? null : mAccount.getXmppConnection(); + final boolean startOrbot = + mAccount != null + && mAccount.getStatus() == Account.State.TOR_NOT_AVAILABLE; + if (startOrbot) { + if (TorServiceUtils.isOrbotInstalled(EditAccountActivity.this)) { + TorServiceUtils.startOrbot(EditAccountActivity.this, REQUEST_ORBOT); + } else { + TorServiceUtils.downloadOrbot(EditAccountActivity.this, REQUEST_ORBOT); + } + return; + } - if (inNeedOfSaslAccept()) { - mAccount.resetPinnedMechanism(); - if (!xmppConnectionService.updateAccount(mAccount)) { - Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show(); - } - return; - } + if (inNeedOfSaslAccept()) { + mAccount.resetPinnedMechanism(); + if (!xmppConnectionService.updateAccount(mAccount)) { + Toast.makeText( + EditAccountActivity.this, + R.string.unable_to_update_account, + Toast.LENGTH_SHORT) + .show(); + } + return; + } - final boolean openRegistrationUrl = registerNewAccount && !accountInfoEdited && mAccount != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB; - final boolean openPaymentUrl = mAccount != null && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED; - final boolean redirectionWorthyStatus = openPaymentUrl || openRegistrationUrl; - final HttpUrl url = connection != null && redirectionWorthyStatus ? connection.getRedirectionUrl() : null; - if (url != null && !wasDisabled) { - try { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url.toString()))); - return; - } catch (ActivityNotFoundException e) { - Toast.makeText(EditAccountActivity.this, R.string.application_found_to_open_website, Toast.LENGTH_SHORT).show(); - return; - } - } + final boolean openRegistrationUrl = + registerNewAccount + && !accountInfoEdited + && mAccount != null + && mAccount.getStatus() == Account.State.REGISTRATION_WEB; + final boolean openPaymentUrl = + mAccount != null + && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED; + final boolean redirectionWorthyStatus = openPaymentUrl || openRegistrationUrl; + final HttpUrl url = + connection != null && redirectionWorthyStatus + ? connection.getRedirectionUrl() + : null; + if (url != null && !wasDisabled) { + try { + startActivity( + new Intent(Intent.ACTION_VIEW, Uri.parse(url.toString()))); + return; + } catch (ActivityNotFoundException e) { + Toast.makeText( + EditAccountActivity.this, + R.string.application_found_to_open_website, + Toast.LENGTH_SHORT) + .show(); + return; + } + } - final Jid jid; - try { - if (mUsernameMode) { - jid = Jid.ofEscaped(binding.accountJid.getText().toString(), getUserModeDomain(), null); - } else { - jid = Jid.ofEscaped(binding.accountJid.getText().toString()); - Resolver.checkDomain(jid); - } - } catch (final NullPointerException | IllegalArgumentException e) { - if (mUsernameMode) { - binding.accountJidLayout.setError(getString(R.string.invalid_username)); - } else { - binding.accountJidLayout.setError(getString(R.string.invalid_jid)); - } - binding.accountJid.requestFocus(); - removeErrorsOnAllBut(binding.accountJidLayout); - return; - } - final String hostname; - int numericPort = 5222; - if (mShowOptions) { - hostname = CharMatcher.whitespace().removeFrom(binding.hostname.getText()); - final String port = CharMatcher.whitespace().removeFrom(binding.port.getText()); - if (Resolver.invalidHostname(hostname)) { - binding.hostnameLayout.setError(getString(R.string.not_valid_hostname)); - binding.hostname.requestFocus(); - removeErrorsOnAllBut(binding.hostnameLayout); - return; - } - if (!hostname.isEmpty()) { + final Jid jid; try { - numericPort = Integer.parseInt(port); - if (numericPort < 0 || numericPort > 65535) { - binding.portLayout.setError(getString(R.string.not_a_valid_port)); - removeErrorsOnAllBut(binding.portLayout); - binding.port.requestFocus(); + if (mUsernameMode) { + jid = + Jid.ofEscaped( + binding.accountJid.getText().toString(), + getUserModeDomain(), + null); + } else { + jid = Jid.ofEscaped(binding.accountJid.getText().toString()); + Resolver.checkDomain(jid); + } + } catch (final NullPointerException | IllegalArgumentException e) { + if (mUsernameMode) { + binding.accountJidLayout.setError(getString(R.string.invalid_username)); + } else { + binding.accountJidLayout.setError(getString(R.string.invalid_jid)); + } + binding.accountJid.requestFocus(); + removeErrorsOnAllBut(binding.accountJidLayout); + return; + } + final String hostname; + int numericPort = 5222; + if (mShowOptions) { + hostname = CharMatcher.whitespace().removeFrom(binding.hostname.getText()); + final String port = + CharMatcher.whitespace().removeFrom(binding.port.getText()); + if (Resolver.invalidHostname(hostname)) { + binding.hostnameLayout.setError(getString(R.string.not_valid_hostname)); + binding.hostname.requestFocus(); + removeErrorsOnAllBut(binding.hostnameLayout); return; } + if (!hostname.isEmpty()) { + try { + numericPort = Integer.parseInt(port); + if (numericPort < 0 || numericPort > 65535) { + binding.portLayout.setError( + getString(R.string.not_a_valid_port)); + removeErrorsOnAllBut(binding.portLayout); + binding.port.requestFocus(); + return; + } + + } catch (NumberFormatException e) { + binding.portLayout.setError(getString(R.string.not_a_valid_port)); + removeErrorsOnAllBut(binding.portLayout); + binding.port.requestFocus(); + return; + } + } + } else { + hostname = null; + } - } catch (NumberFormatException e) { - binding.portLayout.setError(getString(R.string.not_a_valid_port)); - removeErrorsOnAllBut(binding.portLayout); - binding.port.requestFocus(); + if (jid.getLocal() == null) { + if (mUsernameMode) { + binding.accountJidLayout.setError(getString(R.string.invalid_username)); + } else { + binding.accountJidLayout.setError(getString(R.string.invalid_jid)); + } + removeErrorsOnAllBut(binding.accountJidLayout); + binding.accountJid.requestFocus(); return; } + if (mAccount != null) { + if (mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) { + mAccount.setOption( + Account.OPTION_MAGIC_CREATE, + mAccount.getPassword().contains(password)); + } + mAccount.setJid(jid); + mAccount.setPort(numericPort); + mAccount.setHostname(hostname); + binding.accountJidLayout.setError(null); + mAccount.setPassword(password); + mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); + if (!xmppConnectionService.updateAccount(mAccount)) { + Toast.makeText( + EditAccountActivity.this, + R.string.unable_to_update_account, + Toast.LENGTH_SHORT) + .show(); + return; + } + } else { + if (xmppConnectionService.findAccountByJid(jid) != null) { + binding.accountJidLayout.setError( + getString(R.string.account_already_exists)); + removeErrorsOnAllBut(binding.accountJidLayout); + binding.accountJid.requestFocus(); + return; + } + mAccount = new Account(jid.asBareJid(), password); + mAccount.setPort(numericPort); + mAccount.setHostname(hostname); + mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); + xmppConnectionService.createAccount(mAccount); + } + binding.hostnameLayout.setError(null); + binding.portLayout.setError(null); + if (mAccount.isOnion()) { + Toast.makeText( + EditAccountActivity.this, + R.string.audio_video_disabled_tor, + Toast.LENGTH_LONG) + .show(); + } + if (mAccount.isEnabled() && !registerNewAccount && !mInitMode) { + finish(); + } else { + updateSaveButton(); + updateAccountInformation(true); + } } - } else { - hostname = null; - } - - if (jid.getLocal() == null) { - if (mUsernameMode) { - binding.accountJidLayout.setError(getString(R.string.invalid_username)); - } else { - binding.accountJidLayout.setError(getString(R.string.invalid_jid)); - } - removeErrorsOnAllBut(binding.accountJidLayout); - binding.accountJid.requestFocus(); - return; - } - if (mAccount != null) { - if (mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) { - mAccount.setOption(Account.OPTION_MAGIC_CREATE, mAccount.getPassword().contains(password)); - } - mAccount.setJid(jid); - mAccount.setPort(numericPort); - mAccount.setHostname(hostname); - binding.accountJidLayout.setError(null); - mAccount.setPassword(password); - mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); - if (!xmppConnectionService.updateAccount(mAccount)) { - Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show(); - return; - } - } else { - if (xmppConnectionService.findAccountByJid(jid) != null) { - binding.accountJidLayout.setError(getString(R.string.account_already_exists)); - removeErrorsOnAllBut(binding.accountJidLayout); - binding.accountJid.requestFocus(); - return; + }; + private final TextWatcher mTextWatcher = + new TextWatcher() { + + @Override + public void onTextChanged( + final CharSequence s, final int start, final int before, final int count) { + updatePortLayout(); + updateSaveButton(); } - mAccount = new Account(jid.asBareJid(), password); - mAccount.setPort(numericPort); - mAccount.setHostname(hostname); - mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); - xmppConnectionService.createAccount(mAccount); - } - binding.hostnameLayout.setError(null); - binding.portLayout.setError(null); - if (mAccount.isOnion()) { - Toast.makeText(EditAccountActivity.this, R.string.audio_video_disabled_tor, Toast.LENGTH_LONG).show(); - } - if (mAccount.isEnabled() - && !registerNewAccount - && !mInitMode) { - finish(); - } else { - updateSaveButton(); - updateAccountInformation(true); - } - } - }; - private final TextWatcher mTextWatcher = new TextWatcher() { - - @Override - public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { - updatePortLayout(); - updateSaveButton(); - } - - @Override - public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { - } - - @Override - public void afterTextChanged(final Editable s) { - - } - }; - private final View.OnFocusChangeListener mEditTextFocusListener = new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View view, boolean b) { - EditText et = (EditText) view; - if (b) { - int resId = mUsernameMode ? R.string.username : R.string.account_settings_example_jabber_id; - if (view.getId() == R.id.hostname) { - resId = mUseTor ? R.string.hostname_or_onion : R.string.hostname_example; + @Override + public void beforeTextChanged( + final CharSequence s, final int start, final int count, final int after) {} + + @Override + public void afterTextChanged(final Editable s) {} + }; + private final View.OnFocusChangeListener mEditTextFocusListener = + new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean b) { + EditText et = (EditText) view; + if (b) { + int resId = + mUsernameMode + ? R.string.username + : R.string.account_settings_example_jabber_id; + if (view.getId() == R.id.hostname) { + resId = + mUseTor + ? R.string.hostname_or_onion + : R.string.hostname_example; + } + final int res = resId; + new Handler().postDelayed(() -> et.setHint(res), 200); + } else { + et.setHint(null); + } } - final int res = resId; - new Handler().postDelayed(() -> et.setHint(res), 200); - } else { - et.setHint(null); - } - } - }; + }; - private static void setAvailabilityRadioButton(Presence.Status status, DialogPresenceBinding binding) { + private static void setAvailabilityRadioButton( + Presence.Status status, DialogPresenceBinding binding) { if (status == null) { binding.online.setChecked(true); return; @@ -380,9 +448,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat public void refreshUiReal() { invalidateOptionsMenu(); - if (mAccount != null - && mAccount.getStatus() != Account.State.ONLINE - && mFetchingAvatar) { + if (mAccount != null && mAccount.getStatus() != Account.State.ONLINE && mFetchingAvatar) { Intent intent = new Intent(this, StartConversationActivity.class); StartConversationActivity.addInviteUri(intent, getIntent()); startActivity(intent); @@ -412,30 +478,41 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } private void deleteAccountAndReturnIfNecessary() { - if (mInitMode && mAccount != null && !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)) { + if (mInitMode + && mAccount != null + && !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)) { xmppConnectionService.deleteAccount(mAccount); } - final boolean magicCreate = mAccount != null && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY); + final boolean magicCreate = + mAccount != null + && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) + && !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY); final Jid jid = mAccount == null ? null : mAccount.getJid(); - if (SignupUtils.isSupportTokenRegistry() && jid != null && magicCreate && !jid.getDomain().equals(Config.MAGIC_CREATE_DOMAIN)) { + if (SignupUtils.isSupportTokenRegistry() + && jid != null + && magicCreate + && !jid.getDomain().equals(Config.MAGIC_CREATE_DOMAIN)) { final Jid preset; if (mAccount.isOptionSet(Account.OPTION_FIXED_USERNAME)) { preset = jid.asBareJid(); } else { preset = jid.getDomain(); } - final Intent intent = SignupUtils.getTokenRegistrationIntent(this, preset, mAccount.getKey(Account.KEY_PRE_AUTH_REGISTRATION_TOKEN)); + final Intent intent = + SignupUtils.getTokenRegistrationIntent( + this, preset, mAccount.getKey(Account.KEY_PRE_AUTH_REGISTRATION_TOKEN)); StartConversationActivity.addInviteUri(intent, getIntent()); startActivity(intent); return; } - - final List accounts = xmppConnectionService == null ? null : xmppConnectionService.getAccounts(); + final List accounts = + xmppConnectionService == null ? null : xmppConnectionService.getAccounts(); if (accounts != null && accounts.size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) { - Intent intent = SignupUtils.getSignUpIntent(this, mForceRegister != null && mForceRegister); + Intent intent = + SignupUtils.getSignUpIntent(this, mForceRegister != null && mForceRegister); StartConversationActivity.addInviteUri(intent, getIntent()); startActivity(intent); } @@ -447,29 +524,40 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } protected void finishInitialSetup(final Avatar avatar) { - runOnUiThread(() -> { - SoftKeyboardUtils.hideSoftKeyboard(EditAccountActivity.this); - final Intent intent; - final XmppConnection connection = mAccount.getXmppConnection(); - final boolean wasFirstAccount = xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1; - if (avatar != null || (connection != null && !connection.getFeatures().pep())) { - intent = new Intent(getApplicationContext(), StartConversationActivity.class); - if (wasFirstAccount) { - intent.putExtra("init", true); - } - intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toEscapedString()); - } else { - intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toEscapedString()); - intent.putExtra("setup", true); - } - if (wasFirstAccount) { - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - } - StartConversationActivity.addInviteUri(intent, getIntent()); - startActivity(intent); - finish(); - }); + runOnUiThread( + () -> { + SoftKeyboardUtils.hideSoftKeyboard(EditAccountActivity.this); + final Intent intent; + final XmppConnection connection = mAccount.getXmppConnection(); + final boolean wasFirstAccount = + xmppConnectionService != null + && xmppConnectionService.getAccounts().size() == 1; + if (avatar != null || (connection != null && !connection.getFeatures().pep())) { + intent = + new Intent( + getApplicationContext(), StartConversationActivity.class); + if (wasFirstAccount) { + intent.putExtra("init", true); + } + intent.putExtra( + EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toEscapedString()); + } else { + intent = + new Intent( + getApplicationContext(), + PublishProfilePictureActivity.class); + intent.putExtra( + EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toEscapedString()); + intent.putExtra("setup", true); + } + if (wasFirstAccount) { + intent.setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + } + StartConversationActivity.addInviteUri(intent, getIntent()); + startActivity(intent); + finish(); + }); } @Override @@ -498,7 +586,9 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } protected void processFingerprintVerification(XmppUri uri, boolean showWarningToast) { - if (mAccount != null && mAccount.getJid().asBareJid().equals(uri.getJid()) && uri.hasFingerprints()) { + if (mAccount != null + && mAccount.getJid().asBareJid().equals(uri.getJid()) + && uri.hasFingerprints()) { if (xmppConnectionService.verifyFingerprints(mAccount, uri.getFingerprints())) { Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show(); updateAccountInformation(false); @@ -525,10 +615,14 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat this.binding.saveButton.setText(R.string.save); this.binding.saveButton.setEnabled(true); } else if (mAccount != null - && (mAccount.getStatus() == Account.State.CONNECTING || mAccount.getStatus() == Account.State.REGISTRATION_SUCCESSFUL || mFetchingAvatar)) { + && (mAccount.getStatus() == Account.State.CONNECTING + || mAccount.getStatus() == Account.State.REGISTRATION_SUCCESSFUL + || mFetchingAvatar)) { this.binding.saveButton.setEnabled(false); this.binding.saveButton.setText(R.string.account_status_connecting); - } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !mInitMode) { + } else if (mAccount != null + && mAccount.getStatus() == Account.State.DISABLED + && !mInitMode) { this.binding.saveButton.setEnabled(true); this.binding.saveButton.setText(R.string.enable); } else if (torNeedsInstall(mAccount)) { @@ -546,8 +640,14 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat this.binding.saveButton.setEnabled(false); } } else { - XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection(); - HttpUrl url = connection != null && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED ? connection.getRedirectionUrl() : null; + XmppConnection connection = + mAccount == null ? null : mAccount.getXmppConnection(); + HttpUrl url = + connection != null + && mAccount.getStatus() + == Account.State.PAYMENT_REQUIRED + ? connection.getRedirectionUrl() + : null; if (url != null) { this.binding.saveButton.setText(R.string.open_website); } else if (inNeedOfSaslAccept()) { @@ -558,8 +658,13 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } } else { XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection(); - HttpUrl url = connection != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB ? connection.getRedirectionUrl() : null; - if (url != null && this.binding.accountRegisterNew.isChecked() && !accountInfoEdited) { + HttpUrl url = + connection != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB + ? connection.getRedirectionUrl() + : null; + if (url != null + && this.binding.accountRegisterNew.isChecked() + && !accountInfoEdited) { this.binding.saveButton.setText(R.string.open_website); } else { this.binding.saveButton.setText(R.string.next); @@ -569,7 +674,9 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } private boolean torNeedsInstall(final Account account) { - return account != null && account.getStatus() == Account.State.TOR_NOT_AVAILABLE && !TorServiceUtils.isOrbotInstalled(this); + return account != null + && account.getStatus() == Account.State.TOR_NOT_AVAILABLE + && !TorServiceUtils.isOrbotInstalled(this); } private boolean torNeedsStart(final Account account) { @@ -580,10 +687,13 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat if (this.mAccount == null) { return false; } - return jidEdited() || - !this.mAccount.getPassword().equals(this.binding.accountPassword.getText().toString()) || - !this.mAccount.getHostname().equals(this.binding.hostname.getText().toString()) || - !String.valueOf(this.mAccount.getPort()).equals(this.binding.port.getText().toString()); + return jidEdited() + || !this.mAccount + .getPassword() + .equals(this.binding.accountPassword.getText().toString()) + || !this.mAccount.getHostname().equals(this.binding.hostname.getText().toString()) + || !String.valueOf(this.mAccount.getPort()) + .equals(this.binding.port.getText().toString()); } protected boolean jidEdited() { @@ -630,7 +740,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat if (savedInstanceState != null && savedInstanceState.getBoolean("showMoreTable")) { changeMoreTableVisibility(true); } - final OnCheckedChangeListener OnCheckedShowConfirmPassword = (buttonView, isChecked) -> updateSaveButton(); + final OnCheckedChangeListener OnCheckedShowConfirmPassword = + (buttonView, isChecked) -> updateSaveButton(); this.binding.accountRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword); if (Config.DISALLOW_REGISTRATION_IN_UI) { this.binding.accountRegisterNew.setVisibility(View.GONE); @@ -640,18 +751,23 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } private void onEditYourNameClicked(View view) { - quickEdit(mAccount.getDisplayName(), R.string.your_name, value -> { - final String displayName = value.trim(); - updateDisplayName(displayName); - mAccount.setDisplayName(displayName); - xmppConnectionService.publishDisplayName(mAccount); - refreshAvatar(); - return null; - }, true); + quickEdit( + mAccount.getDisplayName(), + R.string.your_name, + value -> { + final String displayName = value.trim(); + updateDisplayName(displayName); + mAccount.setDisplayName(displayName); + xmppConnectionService.publishDisplayName(mAccount); + refreshAvatar(); + return null; + }, + true); } private void refreshAvatar() { - AvatarWorkerTask.loadAvatar(mAccount, binding.avater, R.dimen.avatar_on_details_screen_size); + AvatarWorkerTask.loadAvatar( + mAccount, binding.avater, R.dimen.avatar_on_details_screen_size); } @Override @@ -726,9 +842,13 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } } boolean init = intent.getBooleanExtra("init", false); - boolean openedFromNotification = intent.getBooleanExtra(EXTRA_OPENED_FROM_NOTIFICATION, false); + boolean openedFromNotification = + intent.getBooleanExtra(EXTRA_OPENED_FROM_NOTIFICATION, false); Log.d(Config.LOGTAG, "extras " + intent.getExtras()); - this.mForceRegister = intent.hasExtra(EXTRA_FORCE_REGISTER) ? intent.getBooleanExtra(EXTRA_FORCE_REGISTER, false) : null; + this.mForceRegister = + intent.hasExtra(EXTRA_FORCE_REGISTER) + ? intent.getBooleanExtra(EXTRA_FORCE_REGISTER, false) + : null; Log.d(Config.LOGTAG, "force register=" + mForceRegister); this.mInitMode = init || this.jidToEdit == null; this.messageFingerprint = intent.getStringExtra("fingerprint"); @@ -738,7 +858,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat configureActionBar(getSupportActionBar(), !openedFromNotification); } else { this.binding.avater.setVisibility(View.GONE); - configureActionBar(getSupportActionBar(), !(init && Config.MAGIC_CREATE_DOMAIN == null)); + configureActionBar( + getSupportActionBar(), !(init && Config.MAGIC_CREATE_DOMAIN == null)); if (mForceRegister != null) { if (mForceRegister) { setTitle(R.string.register_new_account); @@ -751,8 +872,16 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } } SharedPreferences preferences = getPreferences(); - mUseTor = QuickConversationsService.isConversations() && preferences.getBoolean("use_tor", getResources().getBoolean(R.bool.use_tor)); - this.mShowOptions = mUseTor || (QuickConversationsService.isConversations() && preferences.getBoolean("show_connection_options", getResources().getBoolean(R.bool.show_connection_options))); + mUseTor = + QuickConversationsService.isConversations() + && preferences.getBoolean( + "use_tor", getResources().getBoolean(R.bool.use_tor)); + this.mShowOptions = + mUseTor + || (QuickConversationsService.isConversations() + && preferences.getBoolean( + "show_connection_options", + getResources().getBoolean(R.bool.show_connection_options))); this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); if (mForceRegister != null) { this.binding.accountRegisterNew.setVisibility(View.GONE); @@ -767,13 +896,15 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat TextView warning = view.findViewById(R.id.warning); warning.setText(R.string.verifying_omemo_keys_trusted_source_account); builder.setView(view); - builder.setPositiveButton(R.string.continue_btn, (dialog, which) -> { - if (isTrustedSource.isChecked()) { - processFingerprintVerification(xmppUri, false); - } else { - finish(); - } - }); + builder.setPositiveButton( + R.string.continue_btn, + (dialog, which) -> { + if (isTrustedSource.isChecked()) { + processFingerprintVerification(xmppUri, false); + } else { + finish(); + } + }); builder.setNegativeButton(R.string.cancel, (dialog, which) -> finish()); final var dialog = builder.create(); dialog.setCanceledOnTouchOutside(false); @@ -797,9 +928,11 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat @Override public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) { if (mAccount != null) { - savedInstanceState.putString("account", mAccount.getJid().asBareJid().toEscapedString()); + savedInstanceState.putString( + "account", mAccount.getJid().asBareJid().toEscapedString()); savedInstanceState.putBoolean("initMode", mInitMode); - savedInstanceState.putBoolean("showMoreTable", binding.serverInfoMore.getVisibility() == View.VISIBLE); + savedInstanceState.putBoolean( + "showMoreTable", binding.serverInfoMore.getVisibility() == View.VISIBLE); } super.onSaveInstanceState(savedInstanceState); } @@ -808,7 +941,9 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat boolean init = true; if (mSavedInstanceAccount != null) { try { - this.mAccount = xmppConnectionService.findAccountByJid(Jid.ofEscaped(mSavedInstanceAccount)); + this.mAccount = + xmppConnectionService.findAccountByJid( + Jid.ofEscaped(mSavedInstanceAccount)); this.mInitMode = mSavedInstanceInit; init = false; } catch (IllegalArgumentException e) { @@ -821,7 +956,9 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat if (mAccount != null) { this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER); - this.mUsernameMode |= mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && mAccount.isOptionSet(Account.OPTION_REGISTER); + this.mUsernameMode |= + mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) + && mAccount.isOptionSet(Account.OPTION_REGISTER); if (mPendingFingerprintVerificationUri != null) { processFingerprintVerification(mPendingFingerprintVerificationUri, false); mPendingFingerprintVerificationUri = null; @@ -829,16 +966,18 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat updateAccountInformation(init); } - - if (Config.MAGIC_CREATE_DOMAIN == null && this.xmppConnectionService.getAccounts().size() == 0) { + if (Config.MAGIC_CREATE_DOMAIN == null + && this.xmppConnectionService.getAccounts().size() == 0) { this.binding.cancelButton.setEnabled(false); } if (mUsernameMode) { this.binding.accountJidLayout.setHint(getString(R.string.username_hint)); } else { - final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this, - R.layout.item_autocomplete, - xmppConnectionService.getKnownHosts()); + final KnownHostsAdapter mKnownHostsAdapter = + new KnownHostsAdapter( + this, + R.layout.item_autocomplete, + xmppConnectionService.getKnownHosts()); this.binding.accountJid.setAdapter(mKnownHostsAdapter); } @@ -886,7 +1025,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat shareLink(false); break; case R.id.action_change_password_on_server: - gotoChangePassword(null); + gotoChangePassword(); break; case R.id.action_delete_account: deleteAccount(); @@ -905,13 +1044,18 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } private void deleteAccount() { - this.deleteAccount(mAccount,()->{ - finish(); - }); + this.deleteAccount( + mAccount, + () -> { + finish(); + }); } private boolean inNeedOfSaslAccept() { - return mAccount != null && mAccount.getLastErrorStatus() == Account.State.DOWNGRADE_ATTACK && mAccount.getPinnedMechanismPriority() >= 0 && !accountInfoEdited(); + return mAccount != null + && mAccount.getLastErrorStatus() == Account.State.DOWNGRADE_ATTACK + && mAccount.getPinnedMechanismPriority() >= 0 + && !accountInfoEdited(); } private void shareBarcode() { @@ -922,16 +1066,14 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat startActivity(Intent.createChooser(intent, getText(R.string.share_with))); } - private void changeMoreTableVisibility(boolean visible) { + private void changeMoreTableVisibility(final boolean visible) { binding.serverInfoMore.setVisibility(visible ? View.VISIBLE : View.GONE); + binding.serverInfoLoginMechanism.setVisibility(visible ? View.VISIBLE : View.GONE); } - private void gotoChangePassword(String newPassword) { + private void gotoChangePassword() { final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class); changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toEscapedString()); - if (newPassword != null) { - changePasswordIntent.putExtra("password", newPassword); - } startActivity(changePasswordIntent); } @@ -941,9 +1083,13 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat private void changePresence() { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - boolean manualStatus = sharedPreferences.getBoolean(AppSettings.MANUALLY_CHANGE_PRESENCE, getResources().getBoolean(R.bool.manually_change_presence)); + boolean manualStatus = + sharedPreferences.getBoolean( + AppSettings.MANUALLY_CHANGE_PRESENCE, + getResources().getBoolean(R.bool.manually_change_presence)); final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); - final DialogPresenceBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.dialog_presence, null, false); + final DialogPresenceBinding binding = + DataBindingUtil.inflate(getLayoutInflater(), R.layout.dialog_presence, null, false); String current = mAccount.getPresenceStatusMessage(); if (current != null && !current.trim().isEmpty()) { binding.statusMessage.append(current); @@ -951,47 +1097,66 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat setAvailabilityRadioButton(mAccount.getPresenceStatus(), binding); binding.show.setVisibility(manualStatus ? View.VISIBLE : View.GONE); List templates = xmppConnectionService.getPresenceTemplates(mAccount); - PresenceTemplateAdapter presenceTemplateAdapter = new PresenceTemplateAdapter(this, R.layout.item_autocomplete, templates); + PresenceTemplateAdapter presenceTemplateAdapter = + new PresenceTemplateAdapter(this, R.layout.item_autocomplete, templates); binding.statusMessage.setAdapter(presenceTemplateAdapter); - binding.statusMessage.setOnItemClickListener((parent, view, position, id) -> { - PresenceTemplate template = (PresenceTemplate) parent.getItemAtPosition(position); - setAvailabilityRadioButton(template.getStatus(), binding); - }); + binding.statusMessage.setOnItemClickListener( + (parent, view, position, id) -> { + PresenceTemplate template = + (PresenceTemplate) parent.getItemAtPosition(position); + setAvailabilityRadioButton(template.getStatus(), binding); + }); builder.setTitle(R.string.edit_status_message_title); builder.setView(binding.getRoot()); builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.confirm, (dialog, which) -> { - PresenceTemplate template = new PresenceTemplate(getAvailabilityRadioButton(binding), binding.statusMessage.getText().toString().trim()); - if (mAccount.getPgpId() != 0 && hasPgp()) { - generateSignature(null, template); - } else { - xmppConnectionService.changeStatus(mAccount, template, null); - } - }); + builder.setPositiveButton( + R.string.confirm, + (dialog, which) -> { + PresenceTemplate template = + new PresenceTemplate( + getAvailabilityRadioButton(binding), + binding.statusMessage.getText().toString().trim()); + if (mAccount.getPgpId() != 0 && hasPgp()) { + generateSignature(null, template); + } else { + xmppConnectionService.changeStatus(mAccount, template, null); + } + }); builder.create().show(); } private void generateSignature(Intent intent, PresenceTemplate template) { - xmppConnectionService.getPgpEngine().generateSignature(intent, mAccount, template.getStatusMessage(), new UiCallback() { - @Override - public void success(String signature) { - xmppConnectionService.changeStatus(mAccount, template, signature); - } - - @Override - public void error(int errorCode, String object) { - - } - - @Override - public void userInputRequired(PendingIntent pi, String object) { - mPendingPresenceTemplate.push(template); - try { - startIntentSenderForResult(pi.getIntentSender(), REQUEST_CHANGE_STATUS, null, 0, 0, 0, Compatibility.pgpStartIntentSenderOptions()); - } catch (final IntentSender.SendIntentException ignored) { - } - } - }); + xmppConnectionService + .getPgpEngine() + .generateSignature( + intent, + mAccount, + template.getStatusMessage(), + new UiCallback() { + @Override + public void success(String signature) { + xmppConnectionService.changeStatus(mAccount, template, signature); + } + + @Override + public void error(int errorCode, String object) {} + + @Override + public void userInputRequired(PendingIntent pi, String object) { + mPendingPresenceTemplate.push(template); + try { + startIntentSenderForResult( + pi.getIntentSender(), + REQUEST_CHANGE_STATUS, + null, + 0, + 0, + 0, + Compatibility.pgpStartIntentSenderOptions()); + } catch (final IntentSender.SendIntentException ignored) { + } + } + }); } @Override @@ -1005,9 +1170,15 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat 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().getEscapedLocal()); } else { - this.binding.accountJid.getEditableText().append(this.mAccount.getJid().asBareJid().toEscapedString()); + this.binding + .accountJid + .getEditableText() + .append(this.mAccount.getJid().asBareJid().toEscapedString()); } this.binding.accountPassword.getEditableText().clear(); this.binding.accountPassword.getEditableText().append(this.mAccount.getPassword()); @@ -1016,26 +1187,30 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat this.binding.port.setText(""); this.binding.port.getEditableText().append(String.valueOf(this.mAccount.getPort())); this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); - } if (!mInitMode && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { this.binding.accountPassword.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); } - final boolean editable = !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY) && !mAccount.isOptionSet(Account.OPTION_FIXED_USERNAME) && QuickConversationsService.isConversations(); + final boolean editable = + !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY) + && !mAccount.isOptionSet(Account.OPTION_FIXED_USERNAME) + && QuickConversationsService.isConversations(); this.binding.accountJid.setEnabled(editable); this.binding.accountJid.setFocusable(editable); this.binding.accountJid.setFocusableInTouchMode(editable); this.binding.accountJid.setCursorVisible(editable); - final String displayName = mAccount.getDisplayName(); updateDisplayName(displayName); - - final boolean togglePassword = mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) || !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY); - final boolean neverLoggedIn = !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY) && QuickConversationsService.isConversations(); + final boolean togglePassword = + mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) + || !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY); + final boolean neverLoggedIn = + !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY) + && QuickConversationsService.isConversations(); final boolean editPassword = mAccount.unauthorized() || neverLoggedIn; this.binding.accountPasswordLayout.setPasswordVisibilityToggleEnabled(togglePassword); @@ -1047,11 +1222,13 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat if (!mInitMode) { this.binding.avater.setVisibility(View.VISIBLE); - AvatarWorkerTask.loadAvatar(mAccount, binding.avater, R.dimen.avatar_on_details_screen_size); + AvatarWorkerTask.loadAvatar( + mAccount, binding.avater, R.dimen.avatar_on_details_screen_size); } else { this.binding.avater.setVisibility(View.GONE); } - this.binding.accountRegisterNew.setChecked(this.mAccount.isOptionSet(Account.OPTION_REGISTER)); + this.binding.accountRegisterNew.setChecked( + this.mAccount.isOptionSet(Account.OPTION_REGISTER)); if (this.mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) { if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) { ActionBar actionBar = getSupportActionBar(); @@ -1066,13 +1243,14 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat this.binding.accountRegisterNew.setVisibility(View.GONE); } if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) { - Features features = this.mAccount.getXmppConnection().getFeatures(); + final Features features = this.mAccount.getXmppConnection().getFeatures(); this.binding.stats.setVisibility(View.VISIBLE); boolean showBatteryWarning = isOptimizingBattery(); boolean showDataSaverWarning = isAffectedByDataSaver(); showOsOptimizationWarning(showBatteryWarning, showDataSaverWarning); - this.binding.sessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection() - .getLastSessionEstablished())); + this.binding.sessionEst.setText( + UIHelper.readableTimeDifferenceFull( + this, this.mAccount.getXmppConnection().getLastSessionEstablished())); if (features.rosterVersioning()) { this.binding.serverInfoRosterVersion.setText(R.string.server_info_available); } else { @@ -1108,6 +1286,17 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } else { this.binding.serverInfoExternalService.setText(R.string.server_info_unavailable); } + if (features.bind2()) { + this.binding.serverInfoBind2.setText(R.string.server_info_available); + } else { + this.binding.serverInfoBind2.setText(R.string.server_info_unavailable); + } + if (features.sasl2()) { + this.binding.serverInfoSasl2.setText(R.string.server_info_available); + } else { + this.binding.serverInfoSasl2.setText(R.string.server_info_unavailable); + } + this.binding.loginMechanism.setText(Strings.nullToEmpty(features.loginMechanism())); if (features.pep()) { AxolotlService axolotlService = this.mAccount.getAxolotlService(); if (axolotlService != null && axolotlService.isPepBroken()) { @@ -1123,7 +1312,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat if (features.httpUpload(0)) { final long maxFileSize = features.getMaxHttpUploadSize(); if (maxFileSize > 0) { - this.binding.serverInfoHttpUpload.setText(UIHelper.filesizeToString(maxFileSize)); + this.binding.serverInfoHttpUpload.setText( + UIHelper.filesizeToString(maxFileSize)); } else { this.binding.serverInfoHttpUpload.setText(R.string.server_info_available); } @@ -1131,7 +1321,10 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat this.binding.serverInfoHttpUpload.setText(R.string.server_info_unavailable); } - this.binding.pushRow.setVisibility(xmppConnectionService.getPushManagementService().isStub() ? View.GONE : View.VISIBLE); + this.binding.pushRow.setVisibility( + xmppConnectionService.getPushManagementService().isStub() + ? View.GONE + : View.VISIBLE); if (xmppConnectionService.getPushManagementService().available(mAccount)) { this.binding.serverInfoPush.setText(R.string.server_info_available); @@ -1146,24 +1339,36 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat this.binding.pgpFingerprint.setText(OpenPgpUtils.convertKeyIdToHex(pgpKeyId)); this.binding.pgpFingerprint.setOnClickListener(openPgp); if ("pgp".equals(messageFingerprint)) { - this.binding.pgpFingerprintDesc.setTextColor(MaterialColors.getColor(binding.pgpFingerprintDesc, com.google.android.material.R.attr.colorPrimaryVariant)); + this.binding.pgpFingerprintDesc.setTextColor( + MaterialColors.getColor( + binding.pgpFingerprintDesc, + com.google.android.material.R.attr.colorPrimaryVariant)); } this.binding.pgpFingerprintDesc.setOnClickListener(openPgp); this.binding.actionDeletePgp.setOnClickListener(delete); } else { this.binding.pgpFingerprintBox.setVisibility(View.GONE); } - final String ownAxolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint(); + final String ownAxolotlFingerprint = + this.mAccount.getAxolotlService().getOwnFingerprint(); if (ownAxolotlFingerprint != null && Config.supportOmemo()) { this.binding.axolotlFingerprintBox.setVisibility(View.VISIBLE); if (ownAxolotlFingerprint.equals(messageFingerprint)) { - this.binding.ownFingerprintDesc.setTextColor(MaterialColors.getColor(binding.ownFingerprintDesc, com.google.android.material.R.attr.colorPrimaryVariant)); - this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint_selected_message); + this.binding.ownFingerprintDesc.setTextColor( + MaterialColors.getColor( + binding.ownFingerprintDesc, + com.google.android.material.R.attr.colorPrimaryVariant)); + this.binding.ownFingerprintDesc.setText( + R.string.omemo_fingerprint_selected_message); } else { - this.binding.ownFingerprintDesc.setTextColor(MaterialColors.getColor(binding.ownFingerprintDesc, com.google.android.material.R.attr.colorOnSurface)); + this.binding.ownFingerprintDesc.setTextColor( + MaterialColors.getColor( + binding.ownFingerprintDesc, + com.google.android.material.R.attr.colorOnSurface)); this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint); } - this.binding.axolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2))); + this.binding.axolotlFingerprint.setText( + CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2))); this.binding.showQrCodeButton.setVisibility(View.VISIBLE); this.binding.showQrCodeButton.setOnClickListener(v -> showQrCode()); } else { @@ -1172,7 +1377,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat boolean hasKeys = false; boolean showUnverifiedWarning = false; binding.otherDeviceKeys.removeAllViews(); - for (final XmppAxolotlSession session : mAccount.getAxolotlService().findOwnSessions()) { + for (final XmppAxolotlSession session : + mAccount.getAxolotlService().findOwnSessions()) { final FingerprintStatus trust = session.getTrust(); if (!trust.isCompromised()) { boolean highlight = session.getFingerprint().equals(messageFingerprint); @@ -1183,7 +1389,10 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat showUnverifiedWarning = true; } } - if (hasKeys && Config.supportOmemo()) { //TODO: either the button should be visible if we print an active device or the device list should be fed with reactived devices + if (hasKeys + && Config.supportOmemo()) { // TODO: either the button should be visible if we + // print an active device or the device list should + // be fed with reactived devices this.binding.otherDeviceKeysCard.setVisibility(View.VISIBLE); Set otherDevices = mAccount.getAxolotlService().getOwnDeviceIds(); if (otherDevices == null || otherDevices.isEmpty()) { @@ -1191,7 +1400,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } else { binding.clearDevices.setVisibility(View.VISIBLE); } - binding.unverifiedWarning.setVisibility(showUnverifiedWarning ? View.VISIBLE : View.GONE); + binding.unverifiedWarning.setVisibility( + showUnverifiedWarning ? View.VISIBLE : View.GONE); binding.scanButton.setVisibility(showUnverifiedWarning ? View.VISIBLE : View.GONE); } else { this.binding.otherDeviceKeysCard.setVisibility(View.GONE); @@ -1199,7 +1409,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } else { final TextInputLayout errorLayout; if (this.mAccount.errorStatus()) { - if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED || this.mAccount.getStatus() == Account.State.DOWNGRADE_ATTACK) { + if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED + || this.mAccount.getStatus() == Account.State.DOWNGRADE_ATTACK) { errorLayout = this.binding.accountPasswordLayout; } else if (mShowOptions && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND @@ -1224,10 +1435,16 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat private void updateDisplayName(String displayName) { if (TextUtils.isEmpty(displayName)) { this.binding.yourName.setText(R.string.no_name_set_instructions); - this.binding.yourName.setTextColor(MaterialColors.getColor(binding.yourName, com.google.android.material.R.attr.colorOnSurfaceVariant)); + this.binding.yourName.setTextColor( + MaterialColors.getColor( + binding.yourName, + com.google.android.material.R.attr.colorOnSurfaceVariant)); } else { this.binding.yourName.setText(displayName); - this.binding.yourName.setTextColor(MaterialColors.getColor(binding.yourName, com.google.android.material.R.attr.colorOnSurfaceVariant)); + this.binding.yourName.setTextColor( + MaterialColors.getColor( + binding.yourName, + com.google.android.material.R.attr.colorOnSurfaceVariant)); } } @@ -1255,46 +1472,71 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat builder.setTitle(R.string.unpublish_pgp); builder.setMessage(R.string.unpublish_pgp_message); builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.confirm, (dialogInterface, i) -> { - mAccount.setPgpSignId(0); - mAccount.unsetPgpSignature(); - xmppConnectionService.databaseBackend.updateAccount(mAccount); - xmppConnectionService.sendPresence(mAccount); - refreshUiReal(); - }); + builder.setPositiveButton( + R.string.confirm, + (dialogInterface, i) -> { + mAccount.setPgpSignId(0); + mAccount.unsetPgpSignature(); + xmppConnectionService.databaseBackend.updateAccount(mAccount); + xmppConnectionService.sendPresence(mAccount); + refreshUiReal(); + }); builder.create().show(); } - private void showOsOptimizationWarning(boolean showBatteryWarning, boolean showDataSaverWarning) { - this.binding.osOptimization.setVisibility(showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE); - if (showDataSaverWarning && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + private void showOsOptimizationWarning( + boolean showBatteryWarning, boolean showDataSaverWarning) { + this.binding.osOptimization.setVisibility( + showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE); + if (showDataSaverWarning + && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { this.binding.osOptimizationHeadline.setText(R.string.data_saver_enabled); - this.binding.osOptimizationBody.setText(getString(R.string.data_saver_enabled_explained, getString(R.string.app_name))); + this.binding.osOptimizationBody.setText( + getString(R.string.data_saver_enabled_explained, getString(R.string.app_name))); this.binding.osOptimizationDisable.setText(R.string.allow); - this.binding.osOptimizationDisable.setOnClickListener(v -> { - Intent intent = new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS); - Uri uri = Uri.parse("package:" + getPackageName()); - intent.setData(uri); - try { - startActivityForResult(intent, REQUEST_DATA_SAVER); - } catch (ActivityNotFoundException e) { - Toast.makeText(EditAccountActivity.this, getString(R.string.device_does_not_support_data_saver, getString(R.string.app_name)), Toast.LENGTH_SHORT).show(); - } - }); + this.binding.osOptimizationDisable.setOnClickListener( + v -> { + Intent intent = + new Intent( + Settings + .ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS); + Uri uri = Uri.parse("package:" + getPackageName()); + intent.setData(uri); + try { + startActivityForResult(intent, REQUEST_DATA_SAVER); + } catch (ActivityNotFoundException e) { + Toast.makeText( + EditAccountActivity.this, + getString( + R.string.device_does_not_support_data_saver, + getString(R.string.app_name)), + Toast.LENGTH_SHORT) + .show(); + } + }); } else if (showBatteryWarning) { this.binding.osOptimizationDisable.setText(R.string.disable); this.binding.osOptimizationHeadline.setText(R.string.battery_optimizations_enabled); - this.binding.osOptimizationBody.setText(getString(R.string.battery_optimizations_enabled_explained, getString(R.string.app_name))); - this.binding.osOptimizationDisable.setOnClickListener(v -> { - Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - Uri uri = Uri.parse("package:" + getPackageName()); - intent.setData(uri); - try { - startActivityForResult(intent, REQUEST_BATTERY_OP); - } catch (ActivityNotFoundException e) { - Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); - } - }); + this.binding.osOptimizationBody.setText( + getString( + R.string.battery_optimizations_enabled_explained, + getString(R.string.app_name))); + this.binding.osOptimizationDisable.setOnClickListener( + v -> { + Intent intent = + new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + Uri uri = Uri.parse("package:" + getPackageName()); + intent.setData(uri); + try { + startActivityForResult(intent, REQUEST_BATTERY_OP); + } catch (ActivityNotFoundException e) { + Toast.makeText( + EditAccountActivity.this, + R.string.device_does_not_support_battery_op, + Toast.LENGTH_SHORT) + .show(); + } + }); } } @@ -1304,13 +1546,15 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat builder.setIconAttribute(android.R.attr.alertDialogIcon); builder.setMessage(getString(R.string.clear_other_devices_desc)); builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.accept), + builder.setPositiveButton( + getString(R.string.accept), (dialog, which) -> mAccount.getAxolotlService().wipeOtherPepDevices()); builder.create().show(); } private void editMamPrefs() { - this.mFetchingMamPrefsToast = Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG); + this.mFetchingMamPrefsToast = + Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG); this.mFetchingMamPrefsToast.show(); xmppConnectionService.fetchMamPreferences(mAccount, this); } @@ -1321,88 +1565,110 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } @Override - public void onCaptchaRequested(final Account account, final String id, final Data data, final Bitmap captcha) { - runOnUiThread(() -> { - if (mCaptchaDialog != null && mCaptchaDialog.isShowing()) { - mCaptchaDialog.dismiss(); - } - if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { - Log.d(Config.LOGTAG,"activity not running when captcha was requested"); - return; - } - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(EditAccountActivity.this); - final View view = getLayoutInflater().inflate(R.layout.captcha, null); - final ImageView imageView = view.findViewById(R.id.captcha); - final EditText input = view.findViewById(R.id.input); - imageView.setImageBitmap(captcha); - - builder.setTitle(getString(R.string.captcha_required)); - builder.setView(view); - - builder.setPositiveButton(getString(R.string.ok), - (dialog, which) -> { - String rc = input.getText().toString(); - data.put("username", account.getUsername()); - data.put("password", account.getPassword()); - data.put("ocr", rc); - data.submit(); - - if (xmppConnectionServiceBound) { - xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, id, data); - } - }); - builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> { - if (xmppConnectionService != null) { - xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); - } - }); - - builder.setOnCancelListener(dialog -> { - if (xmppConnectionService != null) { - xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); - } - }); - mCaptchaDialog = builder.create(); - mCaptchaDialog.show(); - input.requestFocus(); - }); + public void onCaptchaRequested( + final Account account, final String id, final Data data, final Bitmap captcha) { + runOnUiThread( + () -> { + if (mCaptchaDialog != null && mCaptchaDialog.isShowing()) { + mCaptchaDialog.dismiss(); + } + if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { + Log.d(Config.LOGTAG, "activity not running when captcha was requested"); + return; + } + final MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(EditAccountActivity.this); + final View view = getLayoutInflater().inflate(R.layout.captcha, null); + final ImageView imageView = view.findViewById(R.id.captcha); + final EditText input = view.findViewById(R.id.input); + imageView.setImageBitmap(captcha); + + builder.setTitle(getString(R.string.captcha_required)); + builder.setView(view); + + builder.setPositiveButton( + getString(R.string.ok), + (dialog, which) -> { + String rc = input.getText().toString(); + data.put("username", account.getUsername()); + data.put("password", account.getPassword()); + data.put("ocr", rc); + data.submit(); + + if (xmppConnectionServiceBound) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket( + account, id, data); + } + }); + builder.setNegativeButton( + getString(R.string.cancel), + (dialog, which) -> { + if (xmppConnectionService != null) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket( + account, null, null); + } + }); + + builder.setOnCancelListener( + dialog -> { + if (xmppConnectionService != null) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket( + account, null, null); + } + }); + mCaptchaDialog = builder.create(); + mCaptchaDialog.show(); + input.requestFocus(); + }); } public void onShowErrorToast(final int resId) { - runOnUiThread(() -> Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show()); + runOnUiThread( + () -> Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show()); } @Override public void onPreferencesFetched(final Element prefs) { - runOnUiThread(() -> { - if (mFetchingMamPrefsToast != null) { - mFetchingMamPrefsToast.cancel(); - } - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(EditAccountActivity.this); - builder.setTitle(R.string.server_side_mam_prefs); - String defaultAttr = prefs.getAttribute("default"); - final List defaults = Arrays.asList("never", "roster", "always"); - final AtomicInteger choice = new AtomicInteger(Math.max(0, defaults.indexOf(defaultAttr))); - builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), (dialog, which) -> choice.set(which)); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.ok, (dialog, which) -> { - prefs.setAttribute("default", defaults.get(choice.get())); - xmppConnectionService.pushMamPreferences(mAccount, prefs); - }); - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { - builder.create().show(); - } - }); + runOnUiThread( + () -> { + if (mFetchingMamPrefsToast != null) { + mFetchingMamPrefsToast.cancel(); + } + final MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(EditAccountActivity.this); + builder.setTitle(R.string.server_side_mam_prefs); + String defaultAttr = prefs.getAttribute("default"); + final List defaults = Arrays.asList("never", "roster", "always"); + final AtomicInteger choice = + new AtomicInteger(Math.max(0, defaults.indexOf(defaultAttr))); + builder.setSingleChoiceItems( + R.array.mam_prefs, choice.get(), (dialog, which) -> choice.set(which)); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton( + R.string.ok, + (dialog, which) -> { + prefs.setAttribute("default", defaults.get(choice.get())); + xmppConnectionService.pushMamPreferences(mAccount, prefs); + }); + if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { + builder.create().show(); + } + }); } @Override public void onPreferencesFetchFailed() { - runOnUiThread(() -> { - if (mFetchingMamPrefsToast != null) { - mFetchingMamPrefsToast.cancel(); - } - Toast.makeText(EditAccountActivity.this, R.string.unable_to_fetch_mam_prefs, Toast.LENGTH_LONG).show(); - }); + runOnUiThread( + () -> { + if (mFetchingMamPrefsToast != null) { + mFetchingMamPrefsToast.cancel(); + } + Toast.makeText( + EditAccountActivity.this, + R.string.unable_to_fetch_mam_prefs, + Toast.LENGTH_LONG) + .show(); + }); } @Override diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 31f4ac32a371038e3773bbbfb6c8c488900af14d..aa2e8878cd4cfe4ddbed937fd5cecb939206e524 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -2981,6 +2981,21 @@ public class XmppConnection implements Runnable { } } + public boolean bind2() { + final var loginInfo = XmppConnection.this.loginInfo; + return loginInfo != null && !loginInfo.inlineBindFeatures.isEmpty(); + } + + public boolean sasl2() { + final var loginInfo = XmppConnection.this.loginInfo; + return loginInfo != null && loginInfo.saslVersion == SaslMechanism.Version.SASL_2; + } + + public String loginMechanism() { + final var loginInfo = XmppConnection.this.loginInfo; + return loginInfo == null ? null : loginInfo.saslMechanism.getMechanism(); + } + public boolean pepPublishOptions() { return hasDiscoFeature(account.getJid().asBareJid(), Namespace.PUBSUB_PUBLISH_OPTIONS); } diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml index cab6c3a72de4ced8970f910abdf1b70a257b92e2..45d8710d03673fb3208b6a006993cd28d0c72c5f 100644 --- a/src/main/res/layout/activity_edit_account.xml +++ b/src/main/res/layout/activity_edit_account.xml @@ -244,7 +244,36 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end" - android:paddingLeft="4dp" + android:textAppearance="?textAppearanceBodyMedium" /> + + + + + + + + + + + @@ -274,8 +303,7 @@ android:id="@+id/server_info_pep" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="right" - android:paddingLeft="4dp" + android:layout_gravity="end" android:textAppearance="?textAppearanceBodyMedium" tools:ignore="RtlHardcoded" /> @@ -296,8 +324,7 @@ android:id="@+id/server_info_blocking" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="right" - android:paddingLeft="4dp" + android:layout_gravity="end" android:textAppearance="?textAppearanceBodyMedium" tools:ignore="RtlHardcoded" /> @@ -318,8 +345,7 @@ android:id="@+id/server_info_sm" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="right" - android:paddingLeft="4dp" + android:layout_gravity="end" android:textAppearance="?textAppearanceBodyMedium" tools:ignore="RtlHardcoded" /> @@ -340,8 +366,7 @@ android:id="@+id/server_info_external_service" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="right" - android:paddingLeft="4dp" + android:layout_gravity="end" android:textAppearance="?textAppearanceBodyMedium" tools:ignore="RtlHardcoded" /> @@ -362,8 +387,7 @@ android:id="@+id/server_info_roster_version" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="right" - android:paddingLeft="4dp" + android:layout_gravity="end" android:textAppearance="?textAppearanceBodyMedium" tools:ignore="RtlHardcoded" /> @@ -384,8 +408,7 @@ android:id="@+id/server_info_carbons" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="right" - android:paddingLeft="4dp" + android:layout_gravity="end" android:textAppearance="?textAppearanceBodyMedium" tools:ignore="RtlHardcoded" /> @@ -406,8 +429,7 @@ android:id="@+id/server_info_mam" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="right" - android:paddingLeft="4dp" + android:layout_gravity="end" android:textAppearance="?textAppearanceBodyMedium" tools:ignore="RtlHardcoded" /> @@ -428,8 +450,7 @@ android:id="@+id/server_info_csi" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="right" - android:paddingLeft="4dp" + android:layout_gravity="end" android:textAppearance="?textAppearanceBodyMedium" tools:ignore="RtlHardcoded" /> @@ -451,8 +472,7 @@ android:id="@+id/server_info_push" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="right" - android:paddingLeft="4dp" + android:layout_gravity="end" android:textAppearance="?textAppearanceBodyMedium" /> @@ -461,7 +481,6 @@ android:layout_height="wrap_content"> + + + + + + + + + + + + + + + XEP-0215: External Service Discovery XEP-0163: PEP (Avatars / OMEMO) XEP-0363: HTTP File Upload + XEP-0386: Bind 2 + XEP-0388: Extensible SASL Profile XEP-0357: Push available unavailable @@ -1079,4 +1081,5 @@ Flip camera Video is enabled. Tap to disable. Video is disabled. Tap to enable. + Login mechanism From a06c0d0ecd17b603af1502d35a41958f6e2facf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 6 Aug 2024 11:25:36 +0000 Subject: [PATCH 165/192] Added translation using Weblate (Estonian) --- src/conversations/res/values-et/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/conversations/res/values-et/strings.xml diff --git a/src/conversations/res/values-et/strings.xml b/src/conversations/res/values-et/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..a6b3daec9354f9ae75cdf8d94a67446c6227dd96 --- /dev/null +++ b/src/conversations/res/values-et/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 56d902c8b200023f9d2394da70328d0ff84c8529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 6 Aug 2024 21:33:04 +0000 Subject: [PATCH 166/192] Added translation using Weblate (Estonian) --- src/main/res/values-et/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/main/res/values-et/strings.xml diff --git a/src/main/res/values-et/strings.xml b/src/main/res/values-et/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..a6b3daec9354f9ae75cdf8d94a67446c6227dd96 --- /dev/null +++ b/src/main/res/values-et/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From d21e0b079a21570191b7ad12c84e28eecc8cdaa5 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Tue, 6 Aug 2024 16:23:12 +0000 Subject: [PATCH 167/192] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1036 of 1036 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/uk/ --- src/main/res/values-uk/strings.xml | 44 ++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 33e3cb4477923a5fc13798847401c8a64297880a..0d0b0a24c4b5f9430c4caeb5f6d0aee919e1638c 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -163,7 +163,7 @@ OMEMO Вилучити обліковий запис Тимчасово вимкнути - Опублікувати піктограму користувача + Опублікувати аватар Опублікувати публічний ключ OpenPGP Вилучити публічний ключ OpenPGP Ви впевнені, що хочете вилучити свій публічний ключ OpenPGP з оголошення про присутність\? @@ -187,7 +187,7 @@ XEP-0237: Зміни у списку контактів XEP-0198: Керування потоком XEP-0215: Виявлення зовнішньої служби - XEP-0163: PEP (піктограми користувачів, OMEMO) + XEP-0163: PEP (аватари користувачів, OMEMO) XEP-0363: Обмін файлами через HTTP XEP-0357: Push-повідомлення так @@ -245,13 +245,13 @@ %1$s +%2$d дочитали до цього місця Усі прочитали до цього місця Опублікувати - Торкніться піктограми користувача, щоб вибрати зображення з галереї + Торкніться аватара, щоб вибрати зображення з галереї Публікація… Сервер відхилив Вашу публікацію Не вдалося конвертувати Ваше зображення - Неможливо зберегти піктограму користувача на пристрій + Неможливо зберегти аватар на пристрій (Або натисніть і тримайте, щоб скинути до значення за замовчуванням) - Ваш сервер не підтримує публікацію піктограм користувачів + Ваш сервер не підтримує публікацію аватарів шепоче %s Надіслати приватне повідомлення %s @@ -344,7 +344,7 @@ Увімкнути сповіщення Не знайдено сервер групи Не вдалося створити групу - Піктограма облікового запису + Зображення облікового запису Копіювати цифровий підпис OMEMO Повторно створити ключ OMEMO Стерти пристрої @@ -409,7 +409,7 @@ документ PDF Програма Android Контакт - Піктограму користувача опубліковано! + Аватар опубліковано! Надсилання %s Пропозиція %s Сховати поза мережею @@ -571,7 +571,7 @@ Вилучити ідентифікаційні дані OMEMO Створити наново Ваші ключі OMEMO. Всі Ваші контакти будуть змушені підтвердити Вас знову. Використовуйте це, лише якщо немає іншого вибору. Вилучити вибрані ключі - Потрібно підключення, щоб можна було опублікувати піктограму користувача. + Потрібно підключення, щоб можна було опублікувати аватар. Показати повідомлення про помилку Повідомлення про помилку Увімкнено заощадження трафіку @@ -719,9 +719,9 @@ Доступ до файлів через HTTP для S3 Безпосередній пошук На екрані «Нова розмова» показувати клавіатуру та розміщувати курсор у полі пошуку - Піктограма групи - Сервер не підтримує піктограми груп - Лише власник може змінити піктограму групи + Зображення групи + Сервер не підтримує зображень груп + Лише власник може змінити зображення групи Ім\'я контакту Прізвисько Ім\'я @@ -1018,9 +1018,9 @@ Не знайдено адреси XMPP Немає (вимкнено) Відхилити - Ваша піктограма - Піктограма для %s - Вилучити піктограму + Ваш аватар + Аватар для %s + Вилучити аватар Не вдалося обробити запрошення Створення запрошень не підтримується сервером Реєстрація облікових записів не підтримується @@ -1118,4 +1118,20 @@ Створити резервну копію, запланувати повторюване резервування Створити резервну копію Дозволити приватні повідомлення + Ваш аватар. Торкніться, щоб вибрати новий аватар із галереї. + Не вдалося вимкнути відео. + Видалити ключ OpenPGP + Змінити налаштування сповіщень + Змінити налаштування + Змінити прізвисько + Редагувати назву і тему + Виклик здійснюється через навушник. Торкніться, щоб переключитися на динамік. + Виклик здійснюється через навушник. + Виклик здійснюється за допомогою дротової гарнітури + Відео ввімкнено. Натисніть, щоб вимкнути. + Відео вимкнено. Натисніть, щоб увімкнути. + Виклик здійснюється через динамік. Торкніться, щоб переключитися на навушник. + Виклик здійснюється через динамік. + Виклик здійснюється через Bluetooth. + Розвернути камеру \ No newline at end of file From a036dbe1c7fbf58ab7c4e5dc41c1b803ac446e0f Mon Sep 17 00:00:00 2001 From: licaon-kter Date: Tue, 6 Aug 2024 11:43:05 +0000 Subject: [PATCH 168/192] Translated using Weblate (Romanian) Currently translated at 100.0% (1036 of 1036 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/ro/ --- src/main/res/values-ro-rRO/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index de6ff2e90369970f751b4237e9f1fdbef12adab3..5fbe5b3c6d479c907c641b3cb0b4a9e5db54bba2 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -1089,4 +1089,20 @@ Operațiune neacceptată Atunci când dispozitivul este blocat permite aplicației să arate notificările apelurilor pe tot ecranul. Permite mesaje private + Nu s-a putut dezactiva videoul. + Avatarul dumneavoastră. Atingeți pentru a selecta un nou avatar din galerie. + Schimbă setările notificărilor + Schimbă configurația + Editare nume și subiect + Ștergere cheie OpenPGP + Editare nume + Video dezactivat. Atingeți pentru activare. + Video activat. Atingeți pentru dezactivare. + Întoarce camera + Apelul folosește Bluetooth. + Apelul folosește difuzorul. + Apelul folosește un set de căști cu fir + Apelul folosește receptorul. + Apelul folosește difuzorul. Atingeți pentru a trece la receptor. + Apelul folosește receptorul. Atingeți pentru a trece la difuzor. \ No newline at end of file From 78dfeee3ab362c14eca1c1ca333e54400d31613e Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Tue, 6 Aug 2024 13:34:38 +0000 Subject: [PATCH 169/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1036 of 1036 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 98 +++++++++++++++----------- 1 file changed, 57 insertions(+), 41 deletions(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index e73a297199d58e4f1539734eb36df02536815311..618cfcca9d2b4eee6e96958f44e9f417c952e502 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -26,7 +26,7 @@ 1 分钟前 %d 分钟前 - %d 个未读聊天 + %d 个未读对话 正在发送… 解密消息中。请稍候… @@ -38,7 +38,7 @@ 主持人 参与者 访客 - 是否从联系人列表中移除 %s?将不会移除与对方的聊天。 + 是否从联系人列表中移除 %s?将不会移除与对方的对话。 是否屏蔽 %s 向您发送消息? 是否解除屏蔽 %s 并允许对方向您发送消息? 屏蔽来自 %s 的所有联系人? @@ -67,7 +67,7 @@ 不再询问 无法连接到账号 无法连接到多个账号 - 轻击即可管理账号 + 点击以管理您的账号 附加文件 对方不在您的联系人列表中,是否添加? 添加联系人 @@ -75,22 +75,22 @@ 正在准备发送图片 正在准备发送图片 分享文件中。请稍候… - 清空历史记录 + 清空聊天记录 清空聊天记录 - 是否要删除此聊天中的所有消息? -\n + 是否要删除此对话中的所有消息? +\n \n警告:存储在其他设备或服务器上的消息将不受影响。 删除文件 是否确定要删除此文件? \n \n警告:存储在其他设备或服务器上此文件的副本将不会删除。 选择设备 - 发送明文消息 + 发送未加密消息 发送消息 发送消息至 %s 发送 v\\OMEMO 加密消息 正在使用新昵称 - 发送明文 + 发送未加密 解密失败。也许您没有正确的私钥。 OpenKeychain %1$s 使用 <b>OpenKeychain</b> 来加密和解密消息并管理公钥。<br><br>它在 GPLv3+ 许可证下授权并可在 F-Droid 和 Google Play 上获得。<br><br><small>(请之后重启 %1$s。)</small> @@ -169,7 +169,7 @@ 客户端不兼容 流错误 流打开错误 - 明文 + 未加密 OTR OpenPGP OMEMO @@ -259,7 +259,7 @@ %1$s 和其他 %2$d 人已阅读至此 每个人都已阅读至此 发布 - 轻击头像即可从图库中选择图片 + 点击头像从图库中选择图片 正在发布… 服务器拒绝了您的发布 无法转换图片 @@ -299,7 +299,7 @@ 通知将在免打扰时间内静音 其他 同步书签 - 在进入或离开多用户聊天时设置“自动加入”标志,并回应其他客户端所做的改变。 + 在进入或离开群聊时设置“自动加入”标志,并回应其他客户端所做的改变。 OMEMO 指纹已复制到剪贴板 此群聊已将您封禁了 此群聊仅成员进入 @@ -371,8 +371,8 @@ 此联系人没有可用的密钥。 \n请确保双方都有在线状态订阅。 出了点问题 - 正在从服务器获取历史记录 - 服务器上没有更多历史记录 + 正在从服务器获取聊天记录 + 服务器上没有更多聊天记录 正在更新… 密码已修改! 无法修改密码 @@ -399,14 +399,14 @@ 无法更改 %s 的从属关系 从群聊中封禁 从频道中封禁 - 您正试图从公开频道中移除 %s。唯一的办法就是永远封禁此用户。 + 您正试图从公开频道中移除 %s。唯一的办法就是永远封禁该用户。 立即封禁 无法更改 %s 的角色 私人群聊配置 公开频道配置 私人,仅成员进入 公开用户的 XMPP 地址 - 对频道进行审核 + 开启频道发言审核 您未参与 群聊配置修改成功! 无法修改群聊配置 @@ -702,14 +702,14 @@ 无法获取设备列表 无法获取密钥 提示:某些情况下,双方可以添加到联系人列表解决此问题。 - 是否确定要禁用此聊天的 OMEMO 加密? + 是否确定要禁用此对话的 OMEMO 加密? \n将允许服务器管理员读取您的消息,但可能是与使用过时客户端的用户交流的唯一方法。 立即禁用 草稿: OMEMO 加密 - OMEMO 将始终用于一对一聊天和私人群聊。 - 新聊天将默认使用 OMEMO。 - 新聊天必须明确开启 OMEMO。 + OMEMO 将始终用于一对一和私人群聊。 + 新对话将默认使用 OMEMO。 + 新对话必须明确开启 OMEMO。 创建快捷方式 默认开启 默认关闭 @@ -730,14 +730,14 @@ 授予 %1$s 访问麦克风的权限 搜索消息 GIF - 查看聊天 + 查看对话 位置共享插件 使用位置共享插件代替内置地图 复制 web 地址 复制 XMPP 地址 用于 S3 的 HTTP 文件共享 直接搜索 - 在“新聊天”屏幕上打开键盘并将光标放在搜索栏中 + 在“新对话”屏幕上打开键盘并将光标放在搜索栏中 群聊头像 主机不支持群聊头像 只有所有者才能更改群聊头像 @@ -887,7 +887,7 @@ 无法执行此操作 加入公开频道… 共享应用未授予访问此文件的权限。 - 群聊 + 群聊和频道 jabber.network 本地服务器 大多数用户应该选择“jabber.network”以便从整个公共 XMPP 生态系统中获得更好的建议。 @@ -902,11 +902,11 @@ 添加额外轨道? 正在连接 已连接 - 正在重连 + 正在重新连接 正在接受通话 正在结束通话 - 应答 - 忽略 + 接听 + 拒接 正在发现设备 正在响铃 占线 @@ -918,8 +918,8 @@ 挂断 正在进行的通话 正在进行的视频通话 - 正在重连通话 - 正在重连视频通话 + 正在重新连接通话 + 正在重新连接视频通话 禁用 Tor 以进行通话 来电 未接来电 · %s @@ -945,8 +945,8 @@ 取消置顶 GPX 轨迹 无法更正消息 - 所有聊天 - 此聊天 + 所有对话 + 此对话 您的头像 %s 的头像 用 OMEMO 加密 @@ -1013,25 +1013,25 @@ 联系人不可用 没有拨打电话的权限 呼叫集成不可用! - 是否移除 %s 的书签并存档聊天? + 是否移除 %s 的书签并存档对话? 是否移除 %s 的书签? - 删除并存档聊天 + 删除并存档对话 频道发现使用称为 <a href=https://search.jabber.network>search.jabber.network</a> 的第三方服务。<br><br>使用此功能会将您的 IP 地址和搜索词传输到此服务。请参阅其 <a href=https://search.jabber.network/privacy>隐私政策</a> 以获取更多信息。 - 开始聊天 + 开始对话 分享至… 为已发送和已接收消息使用不同的背景颜色 未选择客户端证书! 彩色聊天气泡 动态色彩 系统色彩 (Material You) - 新聊天 - 之后删除聊天 - 聊天已存档 - 切换到聊天 - 存档聊天 - 二维码不包含此聊天的指纹。 + 新对话 + 之后删除对话 + 对话已存档 + 切换到对话 + 存档对话 + 二维码不包含此对话的指纹。 加入对话 - 相应的聊天已存档。 + 相应的对话已存档。 发送崩溃报告 安全 通知 @@ -1074,6 +1074,22 @@ 全屏通知 当设备锁定时,允许此应用显示占据全屏的来电通知。 不支持的操作 - 创建一次性、计划定期备份 + 创建一次性备份、计划定期备份 允许私信 - + 编辑昵称 + 您的头像。点击从图库中选择新头像。 + 无法禁用视频。 + 删除 OpenPGP 密钥 + 编辑名称和话题 + 更改配置 + 更改通知设置 + 正在使用听筒进行通话。 + 正在使用有线耳机进行通话 + 正在使用扬声器进行通话。 + 正在使用蓝牙进行通话。 + 翻转摄像头 + 视频已禁用,点击以启用。 + 视频已启用,点击以禁用。 + 正在使用扬声器进行通话,点击切换到听筒。 + 正在使用听筒进行通话,点击切换到扬声器。 + \ No newline at end of file From c608ebe5adf0b2ccfeb63408d6ac5d281028a6ec Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Tue, 6 Aug 2024 13:33:48 +0000 Subject: [PATCH 170/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (13 of 13 strings) Translation: Conversations/Android App (Conversations) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-conversations/zh_Hans/ --- src/conversations/res/values-zh-rCN/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversations/res/values-zh-rCN/strings.xml b/src/conversations/res/values-zh-rCN/strings.xml index b16b819f7b288f0229938a714499e72571b1a6ac..e1507867a4fffc811492721f88e39a81b136be03 100644 --- a/src/conversations/res/values-zh-rCN/strings.xml +++ b/src/conversations/res/values-zh-rCN/strings.xml @@ -13,7 +13,7 @@ \n向其他 XMPP 用户提供您的完整地址,就能和对方交流。 您的服务器邀请 配置代码格式不正确 - 轻击分享按钮,向您的联系人发送加入 %1$s 的邀请。 + 点击分享按钮,向您的联系人发送加入 %1$s 的邀请。 如果您的联系人在附近,对方也可以扫描下方二维码接受邀请。 加入 %1$s 和我聊天:%2$s 分享邀请至… From 662ae27a5b45fd5cb89a694f31aa0d3aa211760c Mon Sep 17 00:00:00 2001 From: SomeTr Date: Tue, 6 Aug 2024 16:56:26 +0000 Subject: [PATCH 171/192] Translated using Weblate (Ukrainian) Currently translated at 100.0% (69 of 69 strings) Translation: Conversations/App Store Metadata (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/app-store-metadata/uk/ --- fastlane/metadata/android/uk/changelogs/367.txt | 2 +- fastlane/metadata/android/uk/changelogs/395.txt | 2 +- fastlane/metadata/android/uk/changelogs/402.txt | 2 +- fastlane/metadata/android/uk/changelogs/42041.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fastlane/metadata/android/uk/changelogs/367.txt b/fastlane/metadata/android/uk/changelogs/367.txt index 4e697be13f077bb41714522ba476de35a0216ca0..8431ce8075eefa57f5258421d67f045d88a33e11 100644 --- a/fastlane/metadata/android/uk/changelogs/367.txt +++ b/fastlane/metadata/android/uk/changelogs/367.txt @@ -1,2 +1,2 @@ -* Виправлено вибір піктограми користувача на деяких пристроях з Android 10 +* Виправлено вибір аватара на деяких пристроях з Android 10 * Виправлення обміну файлами для великих файлів diff --git a/fastlane/metadata/android/uk/changelogs/395.txt b/fastlane/metadata/android/uk/changelogs/395.txt index 890b5c4739597ac5df3989d058aa069537f21898..b7d80af5796dda8c3d6925a162cbda3865fdd2c8 100644 --- a/fastlane/metadata/android/uk/changelogs/395.txt +++ b/fastlane/metadata/android/uk/changelogs/395.txt @@ -1,3 +1,3 @@ -* Додано «Повернутися до чату» на екрані звукового виклику +* Додано «Повернутися до чату» на екрані голосового виклику * Удосконалено комбінації клавіш * Виправлення помилок diff --git a/fastlane/metadata/android/uk/changelogs/402.txt b/fastlane/metadata/android/uk/changelogs/402.txt index 1f2ec0fbd50a2ffd6eaccd3a20420059b504bdb2..8058246597cc730cdc50c8b5919920c9a20a7c99 100644 --- a/fastlane/metadata/android/uk/changelogs/402.txt +++ b/fastlane/metadata/android/uk/changelogs/402.txt @@ -1,3 +1,3 @@ * Просте створення запрошень на серверах з підтримкою запрошень * Перегляд файлів GIF, отриманих з Movim -* Піктограми користувачів зберігаються у кеші +* Аватари зберігаються у кеші diff --git a/fastlane/metadata/android/uk/changelogs/42041.txt b/fastlane/metadata/android/uk/changelogs/42041.txt index f0c9635229e92a1a904cae432ba467767f9ce05d..2c9c5d4b62d067a88992dbe40270ed9b7b210f3f 100644 --- a/fastlane/metadata/android/uk/changelogs/42041.txt +++ b/fastlane/metadata/android/uk/changelogs/42041.txt @@ -1,5 +1,5 @@ * Реалізація Extensible SASL Profile, Bind 2.0 і Fast для швидшого повторного з'єднання * Реалізація Channel Binding * Додано можливість перемикатися з голосового на відеовиклик -* Додано можливість видаляти свою піктограму користувача +* Додано можливість видаляти свій аватар * Додано сповіщення про пропущені виклики From 280b5412aaa2c46131fc2853a11f228d1ebab99d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 6 Aug 2024 21:33:24 +0000 Subject: [PATCH 172/192] Added translation using Weblate (Estonian) --- src/quicksy/res/values-et/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/quicksy/res/values-et/strings.xml diff --git a/src/quicksy/res/values-et/strings.xml b/src/quicksy/res/values-et/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..a6b3daec9354f9ae75cdf8d94a67446c6227dd96 --- /dev/null +++ b/src/quicksy/res/values-et/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From e4ae1e455e8eee7ae109dbea4cd9397e712baf35 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Tue, 6 Aug 2024 22:02:55 +0000 Subject: [PATCH 173/192] Translated using Weblate (Ukrainian) Currently translated at 99.9% (1035 of 1036 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/uk/ --- src/main/res/values-uk/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 0d0b0a24c4b5f9430c4caeb5f6d0aee919e1638c..138df5098399e39bc1e9a0baa5158a7adf33dd55 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -1128,7 +1128,7 @@ Виклик здійснюється через навушник. Торкніться, щоб переключитися на динамік. Виклик здійснюється через навушник. Виклик здійснюється за допомогою дротової гарнітури - Відео ввімкнено. Натисніть, щоб вимкнути. + Відео ввімкнено. Торкніться, щоб вимкнути. Відео вимкнено. Натисніть, щоб увімкнути. Виклик здійснюється через динамік. Торкніться, щоб переключитися на навушник. Виклик здійснюється через динамік. From 153dd7f2e40f6c3d3a61d582adb8218f418ee538 Mon Sep 17 00:00:00 2001 From: Stephan-P Date: Wed, 7 Aug 2024 04:42:02 +0000 Subject: [PATCH 174/192] Translated using Weblate (Dutch) Currently translated at 100.0% (1036 of 1036 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/nl/ --- src/main/res/values-nl/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index 81d180457cf5b55135eed0361a22da3f690548f8..b6c1e08264cdd889d46398fd52ada2812be44dc2 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -1086,4 +1086,20 @@ Interactie Op apparaat Niet-ondersteunde actie + Jouw avatar. Tik om een nieuwe avatar uit de galerij te selecteren. + Video kan niet worden uitgeschakeld. + Bijnaam bewerken + OpenPGP-sleutel verwijderen + Naam en onderwerp bewerken + Configuratie wijzigen + Meldingsinstellingen wijzigen + Oproep maakt gebruik van luidspreker. Tik om over te schakelen naar de oortelefoon. + Oproep maakt gebruik van een oortelefoon. + Oproep maakt gebruik van bedrade headset + Oproep maakt gebruik van luidspreker. + Oproep maakt gebruik van bluetooth. + Camera omdraaien + Video is ingeschakeld. Tik om uit te schakelen. + Oproep maakt gebruik van een oortelefoon. Tik om over te schakelen naar de luidspreker. + Video is uitgeschakeld. Tik om in te schakelen. \ No newline at end of file From 5d4b55790b90998a1ec8d1a0d01816ac22f4e20f Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Wed, 7 Aug 2024 04:55:29 +0000 Subject: [PATCH 175/192] Translated using Weblate (Polish) Currently translated at 100.0% (1036 of 1036 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/pl/ --- src/main/res/values-pl/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 8a92894cdb861e2cc7226018ba5653f2fe985601..1da7d181400a7adda385d26a55f8f3fec0b83539 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -1102,4 +1102,20 @@ Nieobsługiwana operacja Pozwól tej aplikacji na pokazywanie powiadomień o przychodzącym połączeniu, które zajmują cały ekran gdy urządzenie jest zablokowane. Pozwól na prywatne wiadomości + Edytuj nazwę + Usuń klucz OpenPGP + Edytuj nazwę i temat + Zmień konfigurację + Zmień ustawienia powiadomień + Rozmowa używa słuchawek. Dotknij aby przełączyć na głośnik. + Rozmowa używa słuchawek. + Rozmowa używa przewodowego zestawu słuchawkowego + Rozmowa używa głośnika. Dotknij aby przełączyć na słuchawki. + Rozmowa używa głośnika. + Rozmowa używa Bluetooth. + Odwróć kamerę + Wideo jest włączone. Dotknij aby wyłączyć. + Wideo jest wyłączone. Dotknij aby włączyć. + Nie udało się wyłączyć wideo. + Twój awatar. Dotknij aby wybrać nowy awatar z galerii. \ No newline at end of file From d829cc60c5d24350ae5e4da70228ca483f853f10 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Tue, 6 Aug 2024 22:03:16 +0000 Subject: [PATCH 176/192] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1036 of 1036 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/uk/ --- src/main/res/values-uk/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 138df5098399e39bc1e9a0baa5158a7adf33dd55..58165f62aeae12b4ec4acd0b39112a1fa8c456c1 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -1129,7 +1129,7 @@ Виклик здійснюється через навушник. Виклик здійснюється за допомогою дротової гарнітури Відео ввімкнено. Торкніться, щоб вимкнути. - Відео вимкнено. Натисніть, щоб увімкнути. + Відео вимкнено. Торкніться, щоб увімкнути. Виклик здійснюється через динамік. Торкніться, щоб переключитися на навушник. Виклик здійснюється через динамік. Виклик здійснюється через Bluetooth. From 52d9b20fafabdd88955d5174f7bfe592d7bc761f Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Tue, 6 Aug 2024 23:04:25 +0000 Subject: [PATCH 177/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1036 of 1036 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 618cfcca9d2b4eee6e96958f44e9f417c952e502..5b0dfd0aa1fb5fba86cb4184da2e074c0192e3a8 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -78,7 +78,7 @@ 清空聊天记录 清空聊天记录 是否要删除此对话中的所有消息? -\n +\n \u0020 \n警告:存储在其他设备或服务器上的消息将不受影响。 删除文件 是否确定要删除此文件? From 07a23f8cd3561476bdc3eab3cad7d6c9b55494df Mon Sep 17 00:00:00 2001 From: random_r Date: Wed, 7 Aug 2024 10:14:50 +0000 Subject: [PATCH 178/192] Translated using Weblate (Italian) Currently translated at 100.0% (1036 of 1036 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/it/ --- src/main/res/values-it/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index ca5c68be5af3657d883331e45339e775d75ad83e..7bb6fccc3853c477e1d2093150a38e8944608ee0 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -1084,4 +1084,20 @@ Consenti messaggi privati Consenti a questa app di mostrare notifiche di chiamate in arrivo che occupano l\'intero schermo quando il dispositivo è bloccato. Crea backup una volta + Il tuo avatar. Tocca per selezionare un nuovo avatar dalla galleria. + Impossibile disattivare il video. + Modifica nick + Elimina chiave OpenPGP + Modifica nome e argomento + Cambia configurazione + Cambia impostazioni di notifica + La chiamata sta usando gli auricolari. Tocca per passare agli altoparlanti. + La chiamata sta usando gli auricolari. + La chiamata sta usando cuffie cablate + La chiamata sta usando gli altoparlanti. Tocca per passare agli auricolari. + La chiamata sta usando gli altoparlanti. + La chiamata sta usando il bluetooth. + Il video è attivo. Tocca per disattivarlo. + Il video è disattivato. Tocca per attivarlo. + Cambia fotocamera \ No newline at end of file From 86616f4e7e4691c19214ab17a0e00e1f750c5590 Mon Sep 17 00:00:00 2001 From: Besnik_b Date: Wed, 7 Aug 2024 10:27:52 +0000 Subject: [PATCH 179/192] Translated using Weblate (Albanian) Currently translated at 98.8% (1024 of 1036 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/sq/ --- src/main/res/values-sq-rAL/strings.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/res/values-sq-rAL/strings.xml b/src/main/res/values-sq-rAL/strings.xml index 2a54f1d04ea2d6569330138d5259940a715518fc..a16397b57c1196285d59e49fe123134034893cd2 100644 --- a/src/main/res/values-sq-rAL/strings.xml +++ b/src/main/res/values-sq-rAL/strings.xml @@ -1081,4 +1081,17 @@ Kopjeruajtje ripërsëritëse Krijoni një kopjeruajtje një here të vetme Lejoni mesazhe private + Përpunoni nofkë + Përpunoni emër dhe temë + Ndryshoni formësim + Ndryshoni rregullime njoftimesh + Thirrja po përdor kufje. + Thirrja po përdor kufje me fill + Thirrja po përdor altoparlant. Prekeni, që të kalohet në kufje. + Thirrja po përdor altoparlant. + Thirrja po përdor Bluetooth. + Fshini kyç OpenPGP + S’u çaktivizua dot videoja. + Avatari juaj. Prekeni, që të përzgjidhni prej galerisë avatar të ri. + Thirrja po përdor kufje. Prekeni, që të kalohet në altoparlant. \ No newline at end of file From 66f53eebb1ae8394354b9675f98de04468682b11 Mon Sep 17 00:00:00 2001 From: nautilusx Date: Wed, 7 Aug 2024 13:16:25 +0000 Subject: [PATCH 180/192] Translated using Weblate (German) Currently translated at 100.0% (1036 of 1036 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/de/ --- src/main/res/values-de/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index ab85cf80faa19fb97cd077030cbc45ce105a1872..61306912dc4fc865d49b425196081700248635e1 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -1070,4 +1070,20 @@ Erlaube dieser App, Benachrichtigungen über eingehende Anrufe anzuzeigen, die den gesamten Bildschirm einnehmen, wenn das Gerät gesperrt ist. Einmalige Sicherung erstellen Private Nachrichten erlauben + Video konnte nicht deaktiviert werden. + OpenPGP-Schlüssel löschen + Benachrichtigungseinstellungen ändern + Anruf erfolgt über ein kabelgebundenes Headset. + Anruf erfolgt über Bluetooth. + Kamera wechseln + Video ist aktiviert. Zum Deaktivieren antippen. + Nickname bearbeiten + Anruf erfolgt über den oberen Lautsprecher. + Aufruf erfolgt über den unteren Lautsprecher. + Dein Profilbild. Antippen, um ein neues Profilbild aus der Galerie auszuwählen. + Video ist deaktiviert. Zum Aktivieren antippen. + Anruf erfolgt über den oberen Lautsprecher. Antippen, um zum unteren Lautsprecher zu wechseln. + Konfiguration ändern + Name und Thema bearbeiten + Anruf erfolgt über den unteren Lautsprecher. Antippen, um zum oberen Lautsprecher zu wechseln. \ No newline at end of file From ef7bacc446ccd23b3b8d2fdc8e0b2f64a71bbe20 Mon Sep 17 00:00:00 2001 From: ghose Date: Wed, 7 Aug 2024 12:01:50 +0000 Subject: [PATCH 181/192] Translated using Weblate (Galician) Currently translated at 100.0% (1036 of 1036 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/gl/ --- src/main/res/values-gl/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index 64cf92af05935b1509c91bf3004b3ec0d742a6f8..594b936b6fae934fe0125092593dcaee0927fa6b 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -1072,4 +1072,20 @@ Crear única, Programar recurrentes Crear unha copia de apoio Permitir mensaxes privadas + O teu avatar. Toca para escoller un novo avatar desde a galería. + Non se puido desactivar o vídeo. + Editar nome + Eliminar chave OpenPGP + Editar nome e tema + Cambiar a configuración + Cambiar axustes das notificacións + A chamada está usando o auricular, toca para usar o altofalante. + A chamada está usando auriculares. + A chamada usa auriculares con cable + A chamada usa os altofalandes. Toca para cambiar a auriculares. + A chamada está usando o altofalante. + A chamada está usando bluetooth. + Cambiar de cámara + Video activado. Toca para desactivar. + Video desactivado. Toca para activar. \ No newline at end of file From 1383da27de1f0cc4980c72524f0cc1a2ba6b2a73 Mon Sep 17 00:00:00 2001 From: Besnik_b Date: Wed, 7 Aug 2024 10:39:22 +0000 Subject: [PATCH 182/192] Translated using Weblate (Albanian) Currently translated at 99.1% (1027 of 1036 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/sq/ --- src/main/res/values-sq-rAL/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/res/values-sq-rAL/strings.xml b/src/main/res/values-sq-rAL/strings.xml index a16397b57c1196285d59e49fe123134034893cd2..f16b9bf0cf71bae84f28c4e1a1ad5f7e9c1c2640 100644 --- a/src/main/res/values-sq-rAL/strings.xml +++ b/src/main/res/values-sq-rAL/strings.xml @@ -1094,4 +1094,7 @@ S’u çaktivizua dot videoja. Avatari juaj. Prekeni, që të përzgjidhni prej galerisë avatar të ri. Thirrja po përdor kufje. Prekeni, që të kalohet në altoparlant. + Videoja është e aktivizuar. Prekeni, që të çaktivizohet. + Ktheni kamerën më anë tjetër + Videoja është e çaktivizuar. Prekeni, që të aktivizohet. \ No newline at end of file From cbd74f1903c48e69c3be9c32ce8c560b45b45a07 Mon Sep 17 00:00:00 2001 From: nautilusx Date: Wed, 7 Aug 2024 16:53:49 +0000 Subject: [PATCH 183/192] Translated using Weblate (German) Currently translated at 100.0% (1039 of 1039 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/de/ --- src/main/res/values-de/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index 61306912dc4fc865d49b425196081700248635e1..c26a7e36dbc412582f02d604f4cc25748889eb59 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -1086,4 +1086,7 @@ Konfiguration ändern Name und Thema bearbeiten Anruf erfolgt über den unteren Lautsprecher. Antippen, um zum oberen Lautsprecher zu wechseln. + XEP-0386: Bind 2 + XEP-0388: Extensible SASL Profile + Anmeldeverfahren \ No newline at end of file From e17ccc5469f30349a1969906f47c3f4edb2b741c Mon Sep 17 00:00:00 2001 From: codimp Date: Thu, 8 Aug 2024 09:57:41 +0000 Subject: [PATCH 184/192] Translated using Weblate (French) Currently translated at 99.2% (1031 of 1039 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/fr/ --- src/main/res/values-fr/strings.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 6747989e624383156e328874844fb18258489f0d..a979ea9e04bbd16c20bdaec814affefd357b2a94 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -1091,4 +1091,15 @@ Grande police Augmenter la taille de la police dans les bulles de message Autoriser les messages privés + Votre image de profil. Tapotez pour sélectionner une nouvelle image de profil depuis la galerie. + Impossible de désactiver la vidéo. + XEP-0388 : Extensible SASL Profile + Éditer nom et sujet + Modifier la configuration + Modifier les paramètres de notification + L\'appel passe par les écouteurs. Tapotez pour passer sur haut-parleur. + L\'appel passe par les écouteurs. + XEP-0386 : Bind 2 + Éditer le pseudo + Supprimer la clé OpenPGP \ No newline at end of file From 6cd787606a0aea755c8c387005cdc3656ea821ab Mon Sep 17 00:00:00 2001 From: ghose Date: Thu, 8 Aug 2024 04:47:54 +0000 Subject: [PATCH 185/192] Translated using Weblate (Galician) Currently translated at 100.0% (1039 of 1039 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/gl/ --- src/main/res/values-gl/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index 594b936b6fae934fe0125092593dcaee0927fa6b..d9b4fdd89e506c39954332e87818a340fd5e40f8 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -1088,4 +1088,7 @@ Cambiar de cámara Video activado. Toca para desactivar. Video desactivado. Toca para activar. + XEP-0388: Extensible SASL Profile + Método de acceso + XEP-0386: Bind 2 \ No newline at end of file From a6acd735674acac74ab55f600148f07b11933685 Mon Sep 17 00:00:00 2001 From: Stephan-P Date: Thu, 8 Aug 2024 05:04:22 +0000 Subject: [PATCH 186/192] Translated using Weblate (Dutch) Currently translated at 100.0% (1039 of 1039 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/nl/ --- src/main/res/values-nl/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index b6c1e08264cdd889d46398fd52ada2812be44dc2..7cde7b5351300a3abac036f4b8954cdf4283d244 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -1102,4 +1102,7 @@ Video is ingeschakeld. Tik om uit te schakelen. Oproep maakt gebruik van een oortelefoon. Tik om over te schakelen naar de luidspreker. Video is uitgeschakeld. Tik om in te schakelen. + XEP-0388: Extensible SASL Profile + Inlogmethode + XEP-0386: Bind 2 \ No newline at end of file From f11202140cc74c5e91a1be1b8021b29d5c2476d6 Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Thu, 8 Aug 2024 04:35:03 +0000 Subject: [PATCH 187/192] Translated using Weblate (Polish) Currently translated at 99.9% (1038 of 1039 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/pl/ --- src/main/res/values-pl/strings.xml | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 1da7d181400a7adda385d26a55f8f3fec0b83539..5cec1e259a7fbdc22354c7e2a627475f1170a892 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -9,7 +9,7 @@ Dodaj konto Edytuj nazwę Dodaj do kontaktów - Usuń z rostera + Usuń z rostera Zablokuj kontakt Odblokuj kontakt Zablokuj domenę @@ -190,16 +190,16 @@ Brak pamięci. Obraz jest za duży Czy chcesz dodać %s do listy kontaktów? Informacje o serwerze - XEP-0313: MAM - XEP-0280: Kopie wiadomości - XEP-0352: Wskaźnik stanu klienta - XEP-0191: Polecenia Blokujące - XEP-0237: Roster Versioning - XEP-0198: Zarządzanie Strumieniem - XEP-0215: Wykrywanie Zewnętrznych Usług - XEP-0163: PEP (Awatary / OMEMO) - XEP-0363: Przesyłanie plików przez HTTP - XEP-0357: Push + XEP-0313: zarządzanie archiwami wiadomości + XEP-0280: kopie wiadomości + XEP-0352: wskazywanie stanu klienta + XEP-0191: polecenia blokujące + XEP-0237: wersjonowanie rostera + XEP-0198: zarządzanie strumieniem + XEP-0215: wykrywanie zewnętrznych usług + XEP-0163: protokół osobistych zdarzeń (awatary/OMEMO) + XEP-0363: wysyłanie plików przez HTTP + XEP-0357: powiadomienia Push dostępny niedostępny Brak informacji o kluczu publicznym @@ -1118,4 +1118,6 @@ Wideo jest wyłączone. Dotknij aby włączyć. Nie udało się wyłączyć wideo. Twój awatar. Dotknij aby wybrać nowy awatar z galerii. + XEP-0388: rozszerzalny profil SASL + XEP-0386: uproszczone nawiązywanie połączenia \ No newline at end of file From 75de4f0369042b5e163d5f9d131fb3fe2a93c8b3 Mon Sep 17 00:00:00 2001 From: SomeTr Date: Wed, 7 Aug 2024 18:13:08 +0000 Subject: [PATCH 188/192] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1039 of 1039 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/uk/ --- src/main/res/values-uk/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 58165f62aeae12b4ec4acd0b39112a1fa8c456c1..dba9365be21c2af2b9a6dfee803d5740f1dbf6f1 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -1134,4 +1134,7 @@ Виклик здійснюється через динамік. Виклик здійснюється через Bluetooth. Розвернути камеру + Механізм авторизації + XEP-0386: Bind 2 + XEP-0388: Розширюваний профіль SASL \ No newline at end of file From 79b93dbd6409a003ed3941db0817f69b889383dd Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Thu, 8 Aug 2024 00:12:30 +0000 Subject: [PATCH 189/192] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1039 of 1039 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/zh_Hans/ --- src/main/res/values-zh-rCN/strings.xml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 5b0dfd0aa1fb5fba86cb4184da2e074c0192e3a8..6b98758eb288dc7ef6a62415ef1e6ceba4ea3476 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -77,8 +77,8 @@ 分享文件中。请稍候… 清空聊天记录 清空聊天记录 - 是否要删除此对话中的所有消息? -\n \u0020 + 是否要删除此对话中的所有消息? +\n \n警告:存储在其他设备或服务器上的消息将不受影响。 删除文件 是否确定要删除此文件? @@ -276,7 +276,7 @@ 跳过 禁用通知 启用 - 需要密码才能进入此群聊 + 进入群聊需要输入密码 输入密码 请先向您的联系人请求在线状态更新。 \n @@ -301,10 +301,10 @@ 同步书签 在进入或离开群聊时设置“自动加入”标志,并回应其他客户端所做的改变。 OMEMO 指纹已复制到剪贴板 - 此群聊已将您封禁了 + 禁止您进入此群聊 此群聊仅成员进入 资源限制 - 此群聊已将您踢出了 + 已从群聊中踢出了您 此群聊已关闭 您已不在群聊中 由于技术原因,您离开了群聊 @@ -746,7 +746,7 @@ 名称 提供名称是可选的 群聊名 - 已解散此群聊 + 此群聊已经解散了 无法保存录制 前台服务 此通知类别用于显示永久通知,表明 %1$s 正在运行。 @@ -1092,4 +1092,7 @@ 视频已启用,点击以禁用。 正在使用扬声器进行通话,点击切换到听筒。 正在使用听筒进行通话,点击切换到扬声器。 + 登录机制 + XEP-0386:绑定 2 + XEP-0388:可扩展 SASL 配置文件 \ No newline at end of file From a040ba601a91853677c5a4be568772dc004db3cf Mon Sep 17 00:00:00 2001 From: codimp Date: Thu, 8 Aug 2024 10:00:55 +0000 Subject: [PATCH 190/192] Translated using Weblate (French) Currently translated at 100.0% (1039 of 1039 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/fr/ --- src/main/res/values-fr/strings.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index a979ea9e04bbd16c20bdaec814affefd357b2a94..85847b000d14e9957b00862b539b290a50472e31 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -1102,4 +1102,12 @@ XEP-0386 : Bind 2 Éditer le pseudo Supprimer la clé OpenPGP + L\'appel passe par le bluetooth. + Changer de caméra + La vidéo est activée. Tapotez pour la désactiver. + La vidéo est désactivée. Tapotez pour l\'activer. + L\'appel passe par le casque filaire + L\'appel passe par le haut-parleur. Tapotez pour passer sur les écouteurs. + L\'appel passe par le haut-parleur. + Mécanisme de connexion \ No newline at end of file From 03ca62eca8a5e215f20fb017c674af05e0a7bb13 Mon Sep 17 00:00:00 2001 From: Besnik_b Date: Thu, 8 Aug 2024 11:26:59 +0000 Subject: [PATCH 191/192] Translated using Weblate (Albanian) Currently translated at 99.1% (1030 of 1039 strings) Translation: Conversations/Android App (shared) Translate-URL: https://translate.codeberg.org/projects/conversations/android-app-shared/sq/ --- src/main/res/values-sq-rAL/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/res/values-sq-rAL/strings.xml b/src/main/res/values-sq-rAL/strings.xml index f16b9bf0cf71bae84f28c4e1a1ad5f7e9c1c2640..23f71ebd7c652d87339f4ba0b2a246fce2df34b1 100644 --- a/src/main/res/values-sq-rAL/strings.xml +++ b/src/main/res/values-sq-rAL/strings.xml @@ -1097,4 +1097,7 @@ Videoja është e aktivizuar. Prekeni, që të çaktivizohet. Ktheni kamerën më anë tjetër Videoja është e çaktivizuar. Prekeni, që të aktivizohet. + XEP-0386: Bind 2 + XEP-0388: Profil SASL i Zgjerueshëm + Mekanizëm hyrjesh \ No newline at end of file From b2ea164b41fcd447c654afc2a40b6a244bf84ce8 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 8 Aug 2024 12:59:14 +0200 Subject: [PATCH 192/192] version bump to 2.16.6 --- CHANGELOG.md | 6 ++++++ build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/4211704.txt | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/4211704.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ac556d368d700a25205316b925b0bdc67e98f2c..d64e76c2803a52e9f82b408e40487fe8fe078d13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +### Version 2.16.6 + +* Offer higher automatic file accept values +* Provide more information in 'Server info' +* Various bug fixes + ### Version 2.16.5 * Minor bug fixes diff --git a/build.gradle b/build.gradle index 167713db3adce0195d2cce3dc754c7918045294c..2ca006740591902e6a6b1286538fe5782a2312ff 100644 --- a/build.gradle +++ b/build.gradle @@ -102,8 +102,8 @@ android { defaultConfig { minSdkVersion 23 targetSdkVersion 34 - versionCode 42116 - versionName "2.16.5" + versionCode 42117 + versionName "2.16.6" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/4211704.txt b/fastlane/metadata/android/en-US/changelogs/4211704.txt new file mode 100644 index 0000000000000000000000000000000000000000..3c2e99507c343d12f4707931000266976417ae8c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4211704.txt @@ -0,0 +1,3 @@ +* Offer higher automatic file accept values +* Provide more information in 'Server info' +* Various bug fixes