From 810d3455a2820638763dc7ca52d02a81dfc2008e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 11 Sep 2019 15:42:16 +0200 Subject: [PATCH 01/36] bumped gradle version --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 0a3f2297a..6f3272c54 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build:gradle:3.5.0' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f0ff44317..79bc0165e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip From a39fff5c8efe174beb5fd72ed2816dbfeafacd73 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 11 Sep 2019 15:42:43 +0200 Subject: [PATCH 02/36] fixed 2 issues reported by new linter --- .../services/NotificationService.java | 2 +- .../conversations/ui/widget/CopyTextView.java | 16 ++++------------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 01bf371af..79f1dd526 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -954,7 +954,7 @@ public class NotificationService { createTryAgainIntent()); mBuilder.setDeleteIntent(createDismissErrorIntent()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mBuilder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE); + mBuilder.setVisibility(Notification.VISIBILITY_PRIVATE); mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp); } else { mBuilder.setSmallIcon(R.drawable.ic_stat_alert_warning); diff --git a/src/main/java/eu/siacs/conversations/ui/widget/CopyTextView.java b/src/main/java/eu/siacs/conversations/ui/widget/CopyTextView.java index bed56192e..d22a2deae 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/CopyTextView.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/CopyTextView.java @@ -1,14 +1,12 @@ package eu.siacs.conversations.ui.widget; -import android.annotation.TargetApi; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; -import android.os.Build; +import android.support.v7.widget.AppCompatTextView; import android.util.AttributeSet; -import android.widget.TextView; -public class CopyTextView extends TextView { +public class CopyTextView extends AppCompatTextView { public CopyTextView(Context context) { super(context); @@ -22,14 +20,8 @@ public class CopyTextView extends TextView { super(context, attrs, defStyleAttr); } - @SuppressWarnings("unused") - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public CopyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - public interface CopyHandler { - public String transformTextForCopy(CharSequence text, int start, int end); + String transformTextForCopy(CharSequence text, int start, int end); } private CopyHandler copyHandler; @@ -40,7 +32,7 @@ public class CopyTextView extends TextView { @Override public boolean onTextContextMenuItem(int id) { - CharSequence text = getText(); + final CharSequence text = getText(); int min = 0; int max = text.length(); if (isFocused()) { From 9273ba565339b59cc0a45be6bdb8d596a788845d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 12 Sep 2019 10:09:28 +0200 Subject: [PATCH 03/36] pulled translations from transifex --- src/main/res/values-es/strings.xml | 2 ++ src/main/res/values-it/strings.xml | 1 + src/main/res/values-pl/strings.xml | 1 + src/main/res/values-ro-rRO/strings.xml | 1 + 4 files changed, 5 insertions(+) diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 813be5abc..d1370c337 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -30,6 +30,7 @@ ahora hace 1 min hace %d min + %dconversaciones sin leer enviando… Descifrando mensaje. Por favor, espera... Mensaje cifrado con OpenPGP @@ -872,4 +873,5 @@ El fichero seleccionado no es un respaldo de Conversations Esta cuenta ya fue configurada Por favor ingrese la contraseña para esta cuenta + No se ha podido realizar esta acción diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 2e0f1447d..0297db8b8 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -30,6 +30,7 @@ adesso 1 min fa %d min fa + %d conversazioni non lette invio… Decifrazione messaggio. Attendere prego... Messaggio cifrato con OpenPGP diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index ce4517a8e..68ac6fba1 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -30,6 +30,7 @@ przed chwilą minutę temu %d minut temu + %d nieprzeczytanych konwersacji wysyłanie... Odszyfrowywanie wiadomości. To zajmie tylko chwilę... Wiadomość zaszyfrowana OpenPGP diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index 6492eff3a..b73a0fa8b 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -30,6 +30,7 @@ în acest moment acum un minut acum %d minute + %d conversații necitite trimitere... Decriptez mesaj. Te rog așteaptă... Mesaj criptat cu OpenPGP From 9bf5fb98acce52d49326f2f613a99f5b957f95c7 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 12 Sep 2019 10:12:47 +0200 Subject: [PATCH 04/36] show language in message bubble if multiple language variants were received MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit XML and by inheritence XMPP has the feature of transmitting multiple language variants for the same content. This can be really useful if, for example, you are talking to an automated system. A chat bot could greet you in your own language. On the wire this will usually look like this: ```xml Good morning Guten Morgen ``` However receiving such a message in a group chat can be very confusing and potentially dangerous if the sender puts conflicting information in there and different people get shown different strings. Disabeling support for localization entirely isn’t an ideal solution as on principle it is still a good feature; and other clients might still show a localization even if Conversations would always show the default language. So instead Conversations now shows the displayed language in a corner of the message bubble if more than one translation has been received. If multiple languages are received Conversations will attempt to find one in the language the operating system is set to. If no such translation can be found it will attempt to display the English string. If English can not be found either (for example a message that only has ru and fr on a phone that is set to de) it will display what ever language came first. Furthermore Conversations will discard (not show at all) messages with with multiple bodies of the same language. (This is considered an invalid message) The lanuage tag will not be shown if Conversations received a single body in a language not understood by the user. (For example operating system set to 'de' and message received with one body in 'ru' will just display that body as usual.) As a guide line to the user: If you are reading a message where it is important that this message is not interpreted differently by different people (like a vote (+1 / -1) in a chat room) make sure it has *no* language tag. --- .../crypto/axolotl/XmppAxolotlMessage.java | 2 +- .../entities/IndividualMessage.java | 8 ++- .../siacs/conversations/entities/Message.java | 21 ++++++- .../conversations/parser/MessageParser.java | 29 ++++++---- .../persistance/DatabaseBackend.java | 7 ++- .../ui/adapter/MessageAdapter.java | 23 ++++---- .../eu/siacs/conversations/xml/Element.java | 31 +--------- .../conversations/xml/LocalizedContent.java | 57 +++++++++++++++++++ .../conversations/xmpp/XmppConnection.java | 3 +- .../xmpp/stanzas/MessagePacket.java | 5 +- 10 files changed, 125 insertions(+), 61 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/xml/LocalizedContent.java diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index 4dc904ce1..cd91adb82 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -135,7 +135,7 @@ public class XmppAxolotlMessage { break; } } - Element payloadElement = axolotlMessage.findChild(PAYLOAD); + Element payloadElement = axolotlMessage.findChild(PAYLOAD); //TODO make sure we only have _one_ paypload if (payloadElement != null) { ciphertext = Base64.decode(payloadElement.getContent().trim(), Base64.DEFAULT); } diff --git a/src/main/java/eu/siacs/conversations/entities/IndividualMessage.java b/src/main/java/eu/siacs/conversations/entities/IndividualMessage.java index 42496e4eb..9a3ae6236 100644 --- a/src/main/java/eu/siacs/conversations/entities/IndividualMessage.java +++ b/src/main/java/eu/siacs/conversations/entities/IndividualMessage.java @@ -43,8 +43,8 @@ public class IndividualMessage extends Message { super(conversation); } - private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, String edited, boolean oob, String errorMessage, Set readByMarkers, boolean markable, boolean deleted) { - super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, edited, oob, errorMessage, readByMarkers, markable, deleted); + private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, String edited, boolean oob, String errorMessage, Set readByMarkers, boolean markable, boolean deleted, String bodyLanguage) { + super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, edited, oob, errorMessage, readByMarkers, markable, deleted, bodyLanguage); } @Override @@ -116,6 +116,8 @@ public class IndividualMessage extends Message { cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)), ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))), cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0, - cursor.getInt(cursor.getColumnIndex(DELETED)) > 0); + cursor.getInt(cursor.getColumnIndex(DELETED)) > 0, + cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE)) + ); } } diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 02fa07818..c18693976 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -62,6 +62,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable public static final String COUNTERPART = "counterpart"; public static final String TRUE_COUNTERPART = "trueCounterpart"; public static final String BODY = "body"; + public static final String BODY_LANGUAGE = "bodyLanguage"; public static final String TIME_SENT = "timeSent"; public static final String ENCRYPTION = "encryption"; public static final String STATUS = "status"; @@ -100,6 +101,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable protected String relativeFilePath; protected boolean read = true; protected String remoteMsgId = null; + private String bodyLanguage = null; protected String serverMsgId = null; private final Conversational conversation; protected Transferable transferable = null; @@ -145,7 +147,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable null, null, false, - false); + false, + null); } protected Message(final Conversational conversation, final String uuid, final String conversationUUid, final Jid counterpart, @@ -154,7 +157,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable final String remoteMsgId, final String relativeFilePath, final String serverMsgId, final String fingerprint, final boolean read, final String edited, final boolean oob, final String errorMessage, final Set readByMarkers, - final boolean markable, final boolean deleted) { + final boolean markable, final boolean deleted, final String bodyLanguage) { this.conversation = conversation; this.uuid = uuid; this.conversationUuid = conversationUUid; @@ -177,6 +180,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable this.readByMarkers = readByMarkers == null ? new HashSet<>() : readByMarkers; this.markable = markable; this.deleted = deleted; + this.bodyLanguage = bodyLanguage; } public static Message fromCursor(Cursor cursor, Conversation conversation) { @@ -201,7 +205,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)), ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))), cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0, - cursor.getInt(cursor.getColumnIndex(DELETED)) > 0); + cursor.getInt(cursor.getColumnIndex(DELETED)) > 0, + cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE)) + ); } private static Jid fromString(String value) { @@ -266,6 +272,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable values.put(READ_BY_MARKERS, ReadByMarker.toJson(readByMarkers).toString()); values.put(MARKABLE, markable ? 1 : 0); values.put(DELETED, deleted ? 1 : 0); + values.put(BODY_LANGUAGE, bodyLanguage); return values; } @@ -430,6 +437,14 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable this.edits.add(new Edited(edited, serverMsgId)); } + public String getBodyLanguage() { + return this.bodyLanguage; + } + + public void setBodyLanguage(String language) { + this.bodyLanguage = language; + } + public boolean edited() { return this.edits.size() > 0; } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 33179b851..552f38ebb 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -33,6 +33,7 @@ import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.QuickConversationsService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.xml.LocalizedContent; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.InvalidJid; @@ -328,7 +329,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece if (timestamp == null) { timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet)); } - final String body = packet.getBody(); + final LocalizedContent body = packet.getBody(); final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted"); final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0"); @@ -337,7 +338,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece final URL xP1S3url = xP1S3 == null ? null : P1S3UrlStreamHandler.of(xP1S3); final String oobUrl = oob != null ? oob.findChildContent("url") : null; final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id"); - final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX); + final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX); //TODO make sure we only have _one_ axolotl element! int status; final Jid counterpart; final Jid to = packet.getTo(); @@ -409,10 +410,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, serverMsgId)) { return; } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) { - Message message = conversation.findSentMessageWithBody(packet.getBody()); - if (message != null) { - mXmppConnectionService.markMessage(message, status); - return; + LocalizedContent localizedBody = packet.getBody(); + if (localizedBody != null) { + Message message = conversation.findSentMessageWithBody(localizedBody.content); + if (message != null) { + mXmppConnectionService.markMessage(message, status); + return; + } } } } else { @@ -491,7 +495,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece message.setEncryption(Message.ENCRYPTION_DECRYPTED); } } else { - message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); + message = new Message(conversation, body.content, Message.ENCRYPTION_NONE, status); + if (body.count > 1) { + message.setBodyLanguage(body.language); + } } message.setCounterpart(counterpart); @@ -499,7 +506,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece message.setServerMsgId(serverMsgId); message.setCarbon(isCarbon); message.setTime(timestamp); - if (body != null && body.equals(oobUrl)) { + if (body != null && body.content != null && body.content.equals(oobUrl)) { message.setOob(true); if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) { message.setEncryption(Message.ENCRYPTION_DECRYPTED); @@ -702,11 +709,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } if (isTypeGroupChat) { - if (packet.hasChild("subject")) { + if (packet.hasChild("subject")) { //TODO usually we would want to check for lack of body; however some servers do set a body :( if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0); - String subject = packet.findInternationalizedChildContent("subject"); - if (conversation.getMucOptions().setSubject(subject)) { + final LocalizedContent subject = packet.findInternationalizedChildContentInDefaultNamespace("subject"); + if (subject != null && conversation.getMucOptions().setSubject(subject.content)) { mXmppConnectionService.updateConversation(conversation); } mXmppConnectionService.updateConversationUi(); diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index b9d5d5bbe..bc4fde0ae 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -62,7 +62,7 @@ import rocks.xmpp.addr.Jid; public class DatabaseBackend extends SQLiteOpenHelper { private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 44; + private static final int DATABASE_VERSION = 45; private static DatabaseBackend instance = null; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -239,6 +239,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Message.READ_BY_MARKERS + " TEXT," + Message.MARKABLE + " NUMBER DEFAULT 0," + Message.DELETED + " NUMBER DEFAULT 0," + + Message.BODY_LANGUAGE + " TEXT," + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY(" + Message.CONVERSATION + ") REFERENCES " + Conversation.TABLENAME + "(" + Conversation.UUID @@ -540,6 +541,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX); db.execSQL(CREATE_MESSAGE_TYPE_INDEX); } + + if (oldVersion < 45 && newVersion >= 45) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.BODY_LANGUAGE); + } } private void canonicalizeJids(SQLiteDatabase db) { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 6c4ffd9fd..490bd0f4f 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -36,6 +36,7 @@ import com.google.common.base.Strings; import java.net.URL; import java.util.List; +import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -283,30 +284,32 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.indicator.setVisibility(View.VISIBLE); } - String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(), message.getMergedTimeSent()); - if (message.getStatus() <= Message.STATUS_RECEIVED) { + final String formattedTime = UIHelper.readableTimeDifferenceFull(getContext(), message.getMergedTimeSent()); + final String bodyLanguage = message.getBodyLanguage(); + final String bodyLanguageInfo = bodyLanguage == null ? "" : String.format(" \u00B7 %s", bodyLanguage.toUpperCase(Locale.US)); + if (message.getStatus() <= Message.STATUS_RECEIVED) { ; if ((filesize != null) && (info != null)) { - viewHolder.time.setText(formatedTime + " \u00B7 " + filesize + " \u00B7 " + info); + viewHolder.time.setText(formattedTime + " \u00B7 " + filesize + " \u00B7 " + info + bodyLanguageInfo); } else if ((filesize == null) && (info != null)) { - viewHolder.time.setText(formatedTime + " \u00B7 " + info); + viewHolder.time.setText(formattedTime + " \u00B7 " + info + bodyLanguageInfo); } else if ((filesize != null) && (info == null)) { - viewHolder.time.setText(formatedTime + " \u00B7 " + filesize); + viewHolder.time.setText(formattedTime + " \u00B7 " + filesize + bodyLanguageInfo); } else { - viewHolder.time.setText(formatedTime); + viewHolder.time.setText(formattedTime+bodyLanguageInfo); } } else { if ((filesize != null) && (info != null)) { - viewHolder.time.setText(filesize + " \u00B7 " + info); + viewHolder.time.setText(filesize + " \u00B7 " + info + bodyLanguageInfo); } else if ((filesize == null) && (info != null)) { if (error) { - viewHolder.time.setText(info + " \u00B7 " + formatedTime); + viewHolder.time.setText(info + " \u00B7 " + formattedTime + bodyLanguageInfo); } else { viewHolder.time.setText(info); } } else if ((filesize != null) && (info == null)) { - viewHolder.time.setText(filesize + " \u00B7 " + formatedTime); + viewHolder.time.setText(filesize + " \u00B7 " + formattedTime + bodyLanguageInfo); } else { - viewHolder.time.setText(formatedTime); + viewHolder.time.setText(formattedTime+bodyLanguageInfo); } } } diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index c708ca2b1..9f27844df 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -1,15 +1,11 @@ package eu.siacs.conversations.xml; -import android.support.annotation.NonNull; -import android.util.Log; - import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Locale; -import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.XmlHelper; import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; @@ -71,31 +67,8 @@ public class Element { return element == null ? null : element.getContent(); } - public String findInternationalizedChildContent(String name) { - return findInternationalizedChildContent(name, Locale.getDefault().getLanguage()); - } - - private String findInternationalizedChildContent(String name, @NonNull String language) { - final HashMap contents = new HashMap<>(); - for(Element child : this.children) { - if (name.equals(child.getName())) { - String lang = child.getAttribute("xml:lang"); - String content = child.getContent(); - if (content != null) { - if (language.equals(lang)) { - return content; - } else { - contents.put(lang, content); - } - } - } - } - final String value = contents.get(null); - if (value != null) { - return value; - } - final String[] values = contents.values().toArray(new String[0]); - return values.length == 0 ? null : values[0]; + public LocalizedContent findInternationalizedChildContentInDefaultNamespace(String name) { + return LocalizedContent.get(this, name); } public Element findChild(String name, String xmlns) { diff --git a/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java b/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java new file mode 100644 index 000000000..ee70df859 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java @@ -0,0 +1,57 @@ +package eu.siacs.conversations.xml; + +import com.google.common.collect.Iterables; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class LocalizedContent { + + public static final String STREAM_LANGUAGE = "en"; + + public final String content; + public final String language; + public final int count; + + private LocalizedContent(String content, String language, int count) { + this.content = content; + this.language = language; + this.count = count; + } + + public static LocalizedContent get(final Element element, String name) { + final HashMap contents = new HashMap<>(); + for(Element child : element.children) { + if (name.equals(child.getName())) { + final String namespace = child.getNamespace(); + final String lang = child.getAttribute("xml:lang"); + final String content = child.getContent(); + if (content != null && (namespace == null || "jabber:client".equals(namespace))) { + if (contents.put(lang, content) != null) { + //anything that has multiple contents for the same language is invalid + return null; + } + } + } + } + if (contents.size() == 0) { + return null; + } + final String userLanguage = Locale.getDefault().getLanguage(); + final String localized = contents.get(userLanguage); + if (localized != null) { + return new LocalizedContent(localized, userLanguage, contents.size()); + } + final String defaultLanguageContent = contents.get(null); + if (defaultLanguageContent != null) { + return new LocalizedContent(defaultLanguageContent, STREAM_LANGUAGE, contents.size()); + } + final String streamLanguageContent = contents.get(STREAM_LANGUAGE); + if (streamLanguageContent != null) { + return new LocalizedContent(streamLanguageContent, STREAM_LANGUAGE, contents.size()); + } + final Map.Entry first = Iterables.get(contents.entrySet(), 0); + return new LocalizedContent(first.getValue(), first.getKey(), contents.size()); + } +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index fe384ebf2..45e4cb887 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -80,6 +80,7 @@ import eu.siacs.conversations.utils.SSLSocketHelper; import eu.siacs.conversations.utils.SocksSocketFactory; import eu.siacs.conversations.utils.XmlHelper; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.LocalizedContent; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Tag; import eu.siacs.conversations.xml.TagWriter; @@ -1334,7 +1335,7 @@ public class XmppConnection implements Runnable { final Tag stream = Tag.start("stream:stream"); stream.setAttribute("to", account.getServer()); stream.setAttribute("version", "1.0"); - stream.setAttribute("xml:lang", "en"); + stream.setAttribute("xml:lang", LocalizedContent.STREAM_LANGUAGE); stream.setAttribute("xmlns", "jabber:client"); stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams"); tagWriter.writeTag(stream); diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java index ac75a5e59..86068bf77 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java @@ -4,6 +4,7 @@ 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; @@ -16,8 +17,8 @@ public class MessagePacket extends AbstractAcknowledgeableStanza { super("message"); } - public String getBody() { - return findChildContent("body"); + public LocalizedContent getBody() { + return findInternationalizedChildContentInDefaultNamespace("body"); } public void setBody(String text) { From a7c47a33fafc8295bb076e0919c5cbd58da87042 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 12 Sep 2019 12:42:42 +0200 Subject: [PATCH 05/36] inherit language from parent message when finding localized body --- .../java/eu/siacs/conversations/xml/LocalizedContent.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java b/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java index ee70df859..57a2f3dba 100644 --- a/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java +++ b/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java @@ -22,10 +22,12 @@ public class LocalizedContent { public static LocalizedContent get(final Element element, String name) { final HashMap contents = new HashMap<>(); + final String parentLanguage = element.getAttribute("xml:lang"); for(Element child : element.children) { if (name.equals(child.getName())) { final String namespace = child.getNamespace(); - final String lang = child.getAttribute("xml:lang"); + final String childLanguage = child.getAttribute("xml:lang"); + final String lang = childLanguage == null ? parentLanguage : childLanguage; final String content = child.getContent(); if (content != null && (namespace == null || "jabber:client".equals(namespace))) { if (contents.put(lang, content) != null) { From e395da18bf07d73b4718c5e7a5a2bcf8e8737ae6 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 12 Sep 2019 12:43:11 +0200 Subject: [PATCH 06/36] when parsing omemo messages ensure we only find one element --- .../crypto/axolotl/XmppAxolotlMessage.java | 2 +- .../siacs/conversations/parser/MessageParser.java | 2 +- .../java/eu/siacs/conversations/xml/Element.java | 13 +++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index cd91adb82..853dc4bab 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -135,7 +135,7 @@ public class XmppAxolotlMessage { break; } } - Element payloadElement = axolotlMessage.findChild(PAYLOAD); //TODO make sure we only have _one_ paypload + final Element payloadElement = axolotlMessage.findChildEnsureSingle(PAYLOAD, AxolotlService.PEP_PREFIX); if (payloadElement != null) { ciphertext = Base64.decode(payloadElement.getContent().trim(), Base64.DEFAULT); } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 552f38ebb..738b8fffa 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -338,7 +338,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece final URL xP1S3url = xP1S3 == null ? null : P1S3UrlStreamHandler.of(xP1S3); final String oobUrl = oob != null ? oob.findChildContent("url") : null; final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id"); - final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX); //TODO make sure we only have _one_ axolotl element! + final Element axolotlEncrypted = packet.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX); int status; final Jid counterpart; final Jid to = packet.getTo(); diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index 9f27844df..2efd943de 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -80,6 +80,19 @@ public class Element { return null; } + public Element findChildEnsureSingle(String name, String xmlns) { + final List results = new ArrayList<>(); + for (Element child : this.children) { + if (name.equals(child.getName()) && xmlns.equals(child.getAttribute("xmlns"))) { + results.add(child); + } + } + if (results.size() == 1) { + return results.get(0); + } + return null; + } + public String findChildContent(String name, String xmlns) { Element element = findChild(name,xmlns); return element == null ? null : element.getContent(); From be4953b1e477c693df58e58e7e03ad467239ced4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 13 Sep 2019 16:38:15 +0200 Subject: [PATCH 07/36] parse LMC 1.1 --- .../java/eu/siacs/conversations/Config.java | 2 ++ .../conversations/entities/Conversation.java | 10 +++------- .../siacs/conversations/entities/Message.java | 17 +++++++++++++++++ .../generator/MessageGenerator.java | 2 +- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 0b43675ad..ac637d76f 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -116,6 +116,8 @@ public final class Config { public static final boolean IGNORE_ID_REWRITE_IN_MUC = true; public static final boolean MUC_LEAVE_BEFORE_JOIN = true; + public static final boolean USE_LMC_VERSION_1_1 = false; + public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY * 5; public static final int MAM_MAX_MESSAGES = 750; diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 2004953ee..bd0d4bcd2 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -2,7 +2,6 @@ package eu.siacs.conversations.entities; import android.content.ContentValues; import android.database.Cursor; -import android.graphics.Color; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; @@ -13,23 +12,19 @@ import org.json.JSONObject; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.ListIterator; -import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.OmemoSetting; import eu.siacs.conversations.crypto.PgpDecryptionService; -import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.QuickConversationsService; import eu.siacs.conversations.utils.JidHelper; import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.mam.MamReference; import rocks.xmpp.addr.Jid; @@ -311,11 +306,12 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart, boolean received, boolean carbon) { synchronized (this.messages) { for (int i = this.messages.size() - 1; i >= 0; --i) { - Message message = messages.get(i); + final Message message = messages.get(i); if (counterpart.equals(message.getCounterpart()) && ((message.getStatus() == Message.STATUS_RECEIVED) == received) && (carbon == message.isCarbon() || received)) { - if (id.equals(message.getRemoteMsgId()) && !message.isFileOrImage() && !message.treatAsDownloadable()) { + final boolean idMatch = id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id); + if (idMatch && !message.isFileOrImage() && !message.treatAsDownloadable()) { return message; } else { return null; diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index c18693976..2035d8ac8 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -437,6 +437,15 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable this.edits.add(new Edited(edited, serverMsgId)); } + public boolean remoteMsgIdMatchInEdit(String id) { + for(Edited edit : this.edits) { + if (id.equals(edit.getEditedId())) { + return true; + } + } + return false; + } + public String getBodyLanguage() { return this.bodyLanguage; } @@ -732,6 +741,14 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } } + public String getEditedIdWireFormat() { + if (edits.size() > 0) { + return edits.get(Config.USE_LMC_VERSION_1_1 ? 0 : edits.size() - 1).getEditedId(); + } else { + throw new IllegalStateException("Attempting to store unedited message"); + } + } + public void setOob(boolean isOob) { this.oob = isOob; } diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 771ebccdc..8f56d39bb 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -58,7 +58,7 @@ public class MessageGenerator extends AbstractGenerator { packet.setId(message.getUuid()); packet.addChild("origin-id", Namespace.STANZA_IDS).setAttribute("id", message.getUuid()); if (message.edited()) { - packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedId()); + packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedIdWireFormat()); } return packet; } From 4cd652884c31d65f4261f1812c36af84e81ef244 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 15 Sep 2019 11:49:55 +0200 Subject: [PATCH 08/36] do not finish or repair sessions for untrusted senders finishing (sending a key transport message in response to pre key message) as well as reparing sessions will leak resource and availability and might in certain situations in group chat leak the Jabber ID. Therefor we disable that. Leaking resource might not be considered harmful by a lot of people however we have always doing similar things with receipts. --- .../crypto/axolotl/AxolotlService.java | 46 +++++++++++++------ .../conversations/parser/MessageParser.java | 9 +++- 2 files changed, 40 insertions(+), 15 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 bbc96811b..e44e5dcd2 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -48,7 +48,6 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.SerialSingleThreadExecutor; import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.pep.PublishOptions; @@ -67,8 +66,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { public static final String LOGPREFIX = "AxolotlService"; - public static final int NUM_KEYS_TO_PUBLISH = 100; - public static final int publishTriesThreshold = 3; + private static final int NUM_KEYS_TO_PUBLISH = 100; + private static final int publishTriesThreshold = 3; private final Account account; private final XmppConnectionService mXmppConnectionService; @@ -1469,7 +1468,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } else { Log.d(Config.LOGTAG,account.getJid().asBareJid()+": nothing to flush. Not republishing key"); } - completeSession(session); + if (trustedOrPreviouslyResponded(session)) { + completeSession(session); + } } } @@ -1479,23 +1480,44 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { publishBundlesIfNeeded(false, false); } } - Iterator iterator = postponedSessions.iterator(); + final Iterator iterator = postponedSessions.iterator(); while (iterator.hasNext()) { - completeSession(iterator.next()); + final XmppAxolotlSession session = iterator.next(); + if (trustedOrPreviouslyResponded(session)) { + completeSession(iterator.next()); + } iterator.remove(); } - Iterator postponedHealingAttemptsIterator = postponedHealing.iterator(); + final Iterator postponedHealingAttemptsIterator = postponedHealing.iterator(); while (postponedHealingAttemptsIterator.hasNext()) { notifyRequiresHealing(postponedHealingAttemptsIterator.next()); postponedHealingAttemptsIterator.remove(); } } + + private boolean trustedOrPreviouslyResponded(XmppAxolotlSession session) { + try { + return trustedOrPreviouslyResponded(Jid.of(session.getRemoteAddress().getName())); + } catch (IllegalArgumentException e) { + return false; + } + } + + public boolean trustedOrPreviouslyResponded(Jid jid) { + final Contact contact = account.getRoster().getContact(jid); + if (contact.showInRoster() || contact.isSelf()) { + return true; + } + final Conversation conversation = mXmppConnectionService.find(account, jid); + return conversation != null && conversation.sentMessagesCount() > 0; + } + private void completeSession(XmppAxolotlSession session) { final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId()); axolotlMessage.addDevice(session, true); try { - Jid jid = Jid.of(session.getRemoteAddress().getName()); + final Jid jid = Jid.of(session.getRemoteAddress().getName()); MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage); mXmppConnectionService.sendMessagePacket(account, packet); } catch (IllegalArgumentException e) { @@ -1505,9 +1527,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) { - XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage; - - XmppAxolotlSession session = getReceivingSession(message); + final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage; + final XmppAxolotlSession session = getReceivingSession(message); try { keyTransportMessage = message.getParameters(session, getOwnDeviceId()); Integer preKeyId = session.getPreKeyIdAndReset(); @@ -1516,7 +1537,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } } catch (CryptoFailedException e) { Log.d(Config.LOGTAG, "could not decrypt keyTransport message " + e.getMessage()); - keyTransportMessage = null; + return null; } if (session.isFresh() && keyTransportMessage != null) { @@ -1527,7 +1548,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } private void putFreshSession(XmppAxolotlSession session) { - Log.d(Config.LOGTAG, "put fresh session"); sessions.put(session); if (Config.X509_VERIFICATION) { if (session.getIdentityKey() != null) { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 738b8fffa..972ce4195 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -125,8 +125,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone); } catch (BrokenSessionException e) { if (checkedForDuplicates) { - service.reportBrokenSessionException(e, postpone); - return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status); + if (service.trustedOrPreviouslyResponded(from.asBareJid())) { + service.reportBrokenSessionException(e, postpone); + return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status); + } else { + Log.d(Config.LOGTAG, "ignoring broken session exception because contact was not trusted"); + return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status); + } } else { Log.d(Config.LOGTAG,"ignoring broken session exception because checkForDuplicates failed"); //TODO should be still emit a failed message? From ab57c5983840a3076647d69f499d1f51692a8420 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 15 Sep 2019 12:23:56 +0200 Subject: [PATCH 09/36] clear notifications when deleting account --- .../eu/siacs/conversations/services/XmppConnectionService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index d40a3ccd5..2e90422fa 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -2190,6 +2190,7 @@ public class XmppConnectionService extends Service { leaveMuc(conversation); } conversations.remove(conversation); + mNotificationService.clear(conversation); } } if (account.getXmppConnection() != null) { @@ -2204,7 +2205,7 @@ public class XmppConnectionService extends Service { this.accounts.remove(account); this.mRosterSyncTaskManager.clear(account); updateAccountUi(); - getNotificationService().updateErrorNotification(); + mNotificationService.updateErrorNotification(); syncEnabledAccountSetting(); toggleForegroundService(); } From d963d95e305ad7d9485912e8d33827cd37d54e61 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 16 Sep 2019 14:20:15 +0200 Subject: [PATCH 10/36] fixed some minor NPE --- .../services/XmppConnectionService.java | 41 ++++++++++--------- .../ui/ConversationFragment.java | 3 +- .../adapter/ChannelSearchResultAdapter.java | 4 +- .../ui/util/MucDetailsContextMenuHelper.java | 6 +-- 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 2e90422fa..e51c83a6a 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -2551,6 +2551,9 @@ public class XmppConnectionService extends Service { final MucOptions mucOptions = conversation.getMucOptions(); if (mucOptions.nonanonymous() && !mucOptions.membersOnly() && !conversation.getBooleanAttribute("accept_non_anonymous", false)) { + synchronized (account.inProgressConferenceJoins) { + account.inProgressConferenceJoins.remove(conversation); + } mucOptions.setError(MucOptions.Error.NON_ANONYMOUS); updateConversationUi(); if (onConferenceJoined != null) { @@ -2990,31 +2993,31 @@ public class XmppConnectionService extends Service { @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 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(packet))) { - 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()))) { - pushBookmarks(account); - } - } + if (bookmark != null && (sameBefore || bookmark.getBookmarkName() == null)) { + if (bookmark.setBookmarkName(StringUtils.nullOnEmpty(mucOptions.getName()))) { + pushBookmarks(account); + } + } - if (callback != null) { - callback.onConferenceConfigurationFetched(conversation); - } + if (callback != null) { + callback.onConferenceConfigurationFetched(conversation); + } - - updateConversationUi(); - } else if (packet.getType() == IqPacket.TYPE.ERROR) { + 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.getError()); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 3b4842437..161e5bf42 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1552,7 +1552,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke intent = GeoHelper.getFetchIntent(activity); break; } - if (intent.resolveActivity(getActivity().getPackageManager()) != null) { + final Context context = getActivity(); + if (context != null && intent.resolveActivity(context.getPackageManager()) != null) { if (chooser) { startActivityForResult( Intent.createChooser(intent, getString(R.string.perform_action_with)), diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java index 03f09f70c..d39910cfd 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java @@ -16,6 +16,7 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.SearchResultItemBinding; import eu.siacs.conversations.http.services.MuclumbusService; import eu.siacs.conversations.ui.util.AvatarWorkerTask; +import rocks.xmpp.addr.Jid; public class ChannelSearchResultAdapter extends ListAdapter { @@ -61,7 +62,8 @@ public class ChannelSearchResultAdapter extends ListAdapter listener.onChannelSearchResult(searchResult)); } diff --git a/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java b/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java index 04198e781..28c869f1e 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java @@ -147,6 +147,8 @@ public final class MucDetailsContextMenuHelper { activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.ADMIN, onAffiliationChanged); return true; case R.id.give_membership: + case R.id.remove_admin_privileges: + case R.id.revoke_owner_privileges: activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.MEMBER, onAffiliationChanged); return true; case R.id.give_owner_privileges: @@ -155,10 +157,6 @@ public final class MucDetailsContextMenuHelper { case R.id.remove_membership: activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.NONE, onAffiliationChanged); return true; - case R.id.remove_admin_privileges: - case R.id.revoke_owner_privileges: - activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.MEMBER, onAffiliationChanged); - return true; case R.id.remove_from_room: removeFromRoom(user, activity, onAffiliationChanged); return true; From 2ec1d0cc0942b89d865e75c56c5da439e514cc38 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 16 Sep 2019 15:13:53 +0200 Subject: [PATCH 11/36] warn when using _only_ ambiguous cyrillic --- .../utils/IrregularUnicodeDetector.java | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java b/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java index 42329c41a..b2ef794c8 100644 --- a/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java +++ b/src/main/java/eu/siacs/conversations/utils/IrregularUnicodeDetector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * Copyright (c) 2018-2019, Daniel Gultsch All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -40,6 +40,8 @@ import android.text.style.ForegroundColorSpan; import android.util.LruCache; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -57,6 +59,7 @@ public class IrregularUnicodeDetector { private static final Map NORMALIZATION_MAP; private static final LruCache CACHE = new LruCache<>(4096); + private static final List AMBIGUOUS_CYRILLIC = Arrays.asList("а","г","е","ѕ","і","q","о","р","с","у"); static { Map temp = new HashMap<>(); @@ -185,13 +188,41 @@ public class IrregularUnicodeDetector { private static Set findIrregularCodePoints(String word) { Set codePoints; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - codePoints = eliminateFirstAndGetCodePointsCompat(mapCompat(word)); + final Map> map = mapCompat(word); + final Set set = asSet(map); + if (containsOnlyAmbiguousCyrillic(set)) { + return set; + } + codePoints = eliminateFirstAndGetCodePointsCompat(map); } else { - codePoints = eliminateFirstAndGetCodePoints(map(word)); + final Map> map = map(word); + final Set set = asSet(map); + if (containsOnlyAmbiguousCyrillic(set)) { + return set; + } + codePoints = eliminateFirstAndGetCodePoints(map); } return codePoints; } + private static Set asSet(Map> map) { + final Set flat = new HashSet<>(); + for(List value : map.values()) { + flat.addAll(value); + } + return flat; + } + + + private static boolean containsOnlyAmbiguousCyrillic(Collection codePoints) { + for (String codePoint : codePoints) { + if (!AMBIGUOUS_CYRILLIC.contains(codePoint)) { + return false; + } + } + return true; + } + private static PatternTuple find(Jid jid) { synchronized (CACHE) { PatternTuple pattern = CACHE.get(jid); From 4c92d1b755ca6c667d6a11bf56fc1419f47cc5d5 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 18 Sep 2019 09:17:47 +0200 Subject: [PATCH 12/36] cancel spinning wheel on muclumbus error --- .../services/ChannelDiscoveryService.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java index 306f8e189..24aa7bdac 100644 --- a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java +++ b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java @@ -1,11 +1,10 @@ package eu.siacs.conversations.services; +import android.support.annotation.NonNull; import android.util.Log; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; import java.io.IOException; import java.util.Collections; @@ -32,12 +31,12 @@ public class ChannelDiscoveryService { private final Cache> cache; - public ChannelDiscoveryService(XmppConnectionService service) { + ChannelDiscoveryService(XmppConnectionService service) { this.service = service; this.cache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(); } - public void initializeMuclumbusService() { + void initializeMuclumbusService() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); if (service.useTorToConnect()) { try { @@ -55,7 +54,7 @@ public class ChannelDiscoveryService { this.muclumbusService = retrofit.create(MuclumbusService.class); } - public void discover(String query, OnChannelSearchResultsFound onChannelSearchResultsFound) { + void discover(String query, OnChannelSearchResultsFound onChannelSearchResultsFound) { final boolean all = query == null || query.trim().isEmpty(); Log.d(Config.LOGTAG, "discover channels. query=" + query); List result = cache.getIfPresent(all ? "" : query); @@ -75,9 +74,11 @@ public class ChannelDiscoveryService { try { call.enqueue(new Callback() { @Override - public void onResponse(Call call, Response response) { + public void onResponse(@NonNull Call call, @NonNull Response response) { final MuclumbusService.Rooms body = response.body(); if (body == null) { + Log.d(Config.LOGTAG, "code from muclumbus=" + response.code()); + listener.onChannelSearchResultsFound(Collections.emptyList()); return; } cache.put("", body.items); @@ -85,8 +86,8 @@ public class ChannelDiscoveryService { } @Override - public void onFailure(Call call, Throwable throwable) { - Log.d(Config.LOGTAG, "Unable to query muclumbus on "+Config.CHANNEL_DISCOVERY, throwable); + public void onFailure(@NonNull Call call, @NonNull Throwable throwable) { + Log.d(Config.LOGTAG, "Unable to query muclumbus on " + Config.CHANNEL_DISCOVERY, throwable); listener.onChannelSearchResultsFound(Collections.emptyList()); } }); @@ -100,10 +101,11 @@ public class ChannelDiscoveryService { searchResultCall.enqueue(new Callback() { @Override - public void onResponse(Call call, Response response) { - System.out.println(response.message()); - MuclumbusService.SearchResult body = response.body(); + public void onResponse(@NonNull Call call, @NonNull Response response) { + final MuclumbusService.SearchResult body = response.body(); if (body == null) { + Log.d(Config.LOGTAG, "code from muclumbus=" + response.code()); + listener.onChannelSearchResultsFound(Collections.emptyList()); return; } cache.put(query, body.result.items); @@ -111,8 +113,8 @@ public class ChannelDiscoveryService { } @Override - public void onFailure(Call call, Throwable throwable) { - Log.d(Config.LOGTAG, "Unable to query muclumbus on "+Config.CHANNEL_DISCOVERY, throwable); + public void onFailure(@NonNull Call call, @NonNull Throwable throwable) { + Log.d(Config.LOGTAG, "Unable to query muclumbus on " + Config.CHANNEL_DISCOVERY, throwable); listener.onChannelSearchResultsFound(Collections.emptyList()); } }); From 02351dc0fb3e123b2a7e2705c65aba2b0ba06cc1 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 18 Sep 2019 09:55:18 +0200 Subject: [PATCH 13/36] fixed direct invites after adhoc --- .../generator/MessageGenerator.java | 4 ++++ .../services/ChannelDiscoveryService.java | 18 ++++++++++++++++-- .../services/XmppConnectionService.java | 9 +++++---- .../ui/util/MucDetailsContextMenuHelper.java | 2 +- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 8f56d39bb..dbf8e7cb2 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -193,6 +193,10 @@ public class MessageGenerator extends AbstractGenerator { if (password != null) { x.setAttribute("password", password); } + if (contact.isFullJid()) { + packet.addChild("no-store", "urn:xmpp:hints"); + packet.addChild("no-copy", "urn:xmpp:hints"); + } return packet; } diff --git a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java index 24aa7bdac..680c3af4e 100644 --- a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java +++ b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java @@ -16,6 +16,7 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.http.services.MuclumbusService; import okhttp3.OkHttpClient; +import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -77,8 +78,8 @@ public class ChannelDiscoveryService { public void onResponse(@NonNull Call call, @NonNull Response response) { final MuclumbusService.Rooms body = response.body(); if (body == null) { - Log.d(Config.LOGTAG, "code from muclumbus=" + response.code()); listener.onChannelSearchResultsFound(Collections.emptyList()); + logError(response); return; } cache.put("", body.items); @@ -104,8 +105,8 @@ public class ChannelDiscoveryService { public void onResponse(@NonNull Call call, @NonNull Response response) { final MuclumbusService.SearchResult body = response.body(); if (body == null) { - Log.d(Config.LOGTAG, "code from muclumbus=" + response.code()); listener.onChannelSearchResultsFound(Collections.emptyList()); + logError(response); return; } cache.put(query, body.result.items); @@ -120,6 +121,19 @@ public class ChannelDiscoveryService { }); } + private static void logError(final Response response) { + final ResponseBody errorBody = response.errorBody(); + Log.d(Config.LOGTAG, "code from muclumbus=" + response.code()); + if (errorBody == null) { + return; + } + try { + Log.d(Config.LOGTAG,"error body="+errorBody.string()); + } catch (IOException e) { + //ignored + } + } + public interface OnChannelSearchResultsFound { void onChannelSearchResultsFound(List results); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index e51c83a6a..5024a9613 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -2947,9 +2947,11 @@ public class XmppConnectionService extends Service { for (Jid invite : jids) { invite(conversation, invite); } - if (account.countPresences() > 1) { - directInvite(conversation, account.getJid().asBareJid()); - } + for(String resource : account.getSelfContact().getPresences().toResourceArray()) { + Jid other = account.getJid().withResource(resource); + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": sending direct invite to "+other); + directInvite(conversation, other); + } saveConversationAsBookmark(conversation, name); if (callback != null) { callback.success(conversation); @@ -3077,7 +3079,6 @@ public class XmppConnectionService extends Service { if (packet.getType() == IqPacket.TYPE.RESULT) { Data data = Data.parse(packet.query().findChild("x", Namespace.DATA)); data.submit(options); - Log.d(Config.LOGTAG,data.toString()); IqPacket set = new IqPacket(IqPacket.TYPE.SET); set.setTo(conversation.getJid().asBareJid()); set.query("http://jabber.org/protocol/muc#owner").addChild(data); diff --git a/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java b/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java index 28c869f1e..c3e874310 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java @@ -178,7 +178,7 @@ public final class MucDetailsContextMenuHelper { return true; case R.id.invite: if (user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) { - activity.xmppConnectionService.directInvite(conversation, jid); + activity.xmppConnectionService.directInvite(conversation, jid.asBareJid()); } else { activity.xmppConnectionService.invite(conversation, jid); } From af74c3604cbcb0e67af56980f14e81eef9ad388d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 18 Sep 2019 11:04:36 +0200 Subject: [PATCH 14/36] fixed R8 weirdness --- proguard-rules.pro | 3 +++ .../conversations/http/services/MuclumbusService.java | 3 +-- .../conversations/services/ChannelDiscoveryService.java | 7 ++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/proguard-rules.pro b/proguard-rules.pro index e3858fc38..78cc2a0f9 100644 --- a/proguard-rules.pro +++ b/proguard-rules.pro @@ -21,6 +21,9 @@ -dontwarn java.lang.** -dontwarn javax.lang.** +-keepclassmembers class eu.siacs.conversations.http.services.** { + !transient ; +} # Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and # EnclosingMethod is required to use InnerClasses. diff --git a/src/main/java/eu/siacs/conversations/http/services/MuclumbusService.java b/src/main/java/eu/siacs/conversations/http/services/MuclumbusService.java index 4f2b8f533..89a8e0ec4 100644 --- a/src/main/java/eu/siacs/conversations/http/services/MuclumbusService.java +++ b/src/main/java/eu/siacs/conversations/http/services/MuclumbusService.java @@ -4,7 +4,6 @@ import com.google.common.base.Objects; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Set; import eu.siacs.conversations.services.AvatarService; @@ -83,7 +82,7 @@ public interface MuclumbusService { class SearchRequest { - public Set keywords; + public final Set keywords; public SearchRequest(String keyword) { this.keywords = Collections.singleton(keyword); diff --git a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java index 680c3af4e..f5dd08485 100644 --- a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java +++ b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java @@ -38,7 +38,8 @@ public class ChannelDiscoveryService { } void initializeMuclumbusService() { - OkHttpClient.Builder builder = new OkHttpClient.Builder(); + final OkHttpClient.Builder builder = new OkHttpClient.Builder(); + if (service.useTorToConnect()) { try { builder.proxy(HttpConnectionManager.getProxy()); @@ -57,7 +58,6 @@ public class ChannelDiscoveryService { void discover(String query, OnChannelSearchResultsFound onChannelSearchResultsFound) { final boolean all = query == null || query.trim().isEmpty(); - Log.d(Config.LOGTAG, "discover channels. query=" + query); List result = cache.getIfPresent(all ? "" : query); if (result != null) { onChannelSearchResultsFound.onChannelSearchResultsFound(result); @@ -98,7 +98,8 @@ public class ChannelDiscoveryService { } private void discoverChannels(final String query, OnChannelSearchResultsFound listener) { - Call searchResultCall = muclumbusService.search(new MuclumbusService.SearchRequest(query)); + MuclumbusService.SearchRequest searchRequest = new MuclumbusService.SearchRequest(query); + Call searchResultCall = muclumbusService.search(searchRequest); searchResultCall.enqueue(new Callback() { @Override From abe01f18f2705ce1ee2678ffdd9999ed2e54cb35 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 19 Sep 2019 10:00:50 +0200 Subject: [PATCH 15/36] improved logging for messages waiting for join --- build.gradle | 4 ++-- .../crypto/axolotl/AxolotlService.java | 2 +- .../conversations/parser/MessageParser.java | 3 +-- .../services/XmppConnectionService.java | 23 +++++++++++++++---- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 6f3272c54..e24b05d42 100644 --- a/build.gradle +++ b/build.gradle @@ -66,8 +66,8 @@ dependencies { implementation 'org.conscrypt:conscrypt-android:2.1.0' implementation 'me.drakeet.support:toastcompat:1.1.0' implementation "com.leinardi.android:speed-dial:2.0.1" - implementation 'com.squareup.retrofit2:retrofit:2.6.0' - implementation 'com.squareup.retrofit2:converter-gson:2.6.0' + implementation 'com.squareup.retrofit2:retrofit:2.6.1' + implementation 'com.squareup.retrofit2:converter-gson:2.6.1' implementation 'com.google.guava:guava:27.1-android' quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.10.1' } 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 e44e5dcd2..b804f4222 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -1484,7 +1484,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { while (iterator.hasNext()) { final XmppAxolotlSession session = iterator.next(); if (trustedOrPreviouslyResponded(session)) { - completeSession(iterator.next()); + completeSession(session); } iterator.remove(); } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 972ce4195..fba10bd72 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -803,9 +803,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece mXmppConnectionService.markRead(conversation); } } else if (!counterpart.isBareJid() && trueJid != null) { - ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid); + final ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid); if (message.addReadByMarker(readByMarker)) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": added read by (" + readByMarker.getRealJid() + ") to message '" + message.getBody() + "'"); mXmppConnectionService.updateMessage(message, false); } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 5024a9613..68db573a9 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -1383,10 +1383,8 @@ public class XmppConnectionService extends Service { } } - final boolean inProgressJoin; - synchronized (account.inProgressConferenceJoins) { - inProgressJoin = conversation.getMode() == Conversational.MODE_MULTI && (account.inProgressConferenceJoins.contains(conversation) || account.pendingConferenceJoins.contains(conversation)); - } + final boolean inProgressJoin = isJoinInProgress(conversation); + if (account.isOnlineAndConnected() && !inProgressJoin) { switch (message.getEncryption()) { @@ -1516,6 +1514,23 @@ public class XmppConnectionService extends Service { } } + private boolean isJoinInProgress(final Conversation conversation) { + final Account account = conversation.getAccount(); + synchronized (account.inProgressConferenceJoins) { + if (conversation.getMode() == Conversational.MODE_MULTI) { + final boolean inProgress = account.inProgressConferenceJoins.contains(conversation); + final boolean pending = account.pendingConferenceJoins.contains(conversation); + final boolean inProgressJoin = inProgress || pending; + if (inProgressJoin) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": holding back message to group. inProgress="+inProgress+", pending="+pending); + } + return inProgressJoin; + } else { + return false; + } + } + } + private void sendUnsentMessages(final Conversation conversation) { conversation.findWaitingMessages(message -> resendMessage(message, true)); } From d0d87cb8a63116618c619e55d85ac5319482bf02 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 19 Sep 2019 15:19:09 +0200 Subject: [PATCH 16/36] added doap file --- doap.rdf | 347 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 doap.rdf diff --git a/doap.rdf b/doap.rdf new file mode 100644 index 000000000..ac4ee6822 --- /dev/null +++ b/doap.rdf @@ -0,0 +1,347 @@ + + + + + Conversations + + 2014-01-14 + + Android XMPP Client + + Conversations is an open source XMPP/Jabber client for the Android platform + + + + + + + + + + + + en + + + + + + + Java + + Android + + + + + + + + + Daniel Gultsch + + + + + + + + + + + + + + + + + + + + + + complete + 1.4 + + + + + + complete + 2.5rc3 + + + + + + complete + 1.32.0 + + + + + + complete + 1.1 + + + + + + complete + 1.1.3 + + + + + + complete + 2.1 + + + + + + complete + 1.1 + + + + + + complete + 1.5.1 + + + + + + complete + 1.2.1 + Avatar, Nick, OMEMO + + + + + + complete + 1.1.2 + File transfer only + + + + + + complete + 1.1 + read only + + + + + + complete + 1.4.0 + + + + + + complete + 1.3 + + + + + + complete + 1.6 + + + + + + complete + 2.0.1 + + + + + + complete + 0.19.1 + + + + + + complete + 1.3 + + + + + + complete + 1.0 + + + + + + complete + 1.2 + + + + + + complete + 1.0.3 + + + + + + complete + 1.0 + + + + + + complete + 0.13.1 + + + + + + complete + 1.0 + + + + + + complete + 0.6.3 + + + + + + complete + 1.0.2 + opt-in + + + + + + complete + 0.3 + + + + + + complete + 0.3.0 + + + + + + complete + 0.4.0 + Only available in the version distributed over Google Play + + + + + + complete + 1.1.0 + + + + + + complete + 0.2 + + + + + + complete + 0.3.0 + + + + + + complete + 0.1.2 + 2.5.8 + + + + + + complete + 0.6.0 + 2.3.1 + + + + + + complete + 0.1.4 + 1.22.0 + + + + + + complete + 0.1 + 2.5.8 + + + + + + complete + 0.2.1 + + + + + + complete + 1.0.1 + 2.5.4 + + + + + + complete + 0.2.0 + + + + + + + + 2.5.8 + 2019-09-12 + + + + + From 5e1d2a92a6110fe38dd0b97876996cf047d0990f Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 19 Sep 2019 15:33:21 +0200 Subject: [PATCH 17/36] uploaded doap file to new syntax --- doap.rdf | 561 +++++++++++++++++++++++++++---------------------------- 1 file changed, 277 insertions(+), 284 deletions(-) diff --git a/doap.rdf b/doap.rdf index ac4ee6822..40b5c1884 100644 --- a/doap.rdf +++ b/doap.rdf @@ -22,10 +22,7 @@ en - - - - + Java @@ -55,286 +52,282 @@ - - - - - - complete - 1.4 - - - - - - complete - 2.5rc3 - - - - - - complete - 1.32.0 - - - - - - complete - 1.1 - - - - - - complete - 1.1.3 - - - - - - complete - 2.1 - - - - - - complete - 1.1 - - - - - - complete - 1.5.1 - - - - - - complete - 1.2.1 - Avatar, Nick, OMEMO - - - - - - complete - 1.1.2 - File transfer only - - - - - - complete - 1.1 - read only - - - - - - complete - 1.4.0 - - - - - - complete - 1.3 - - - - - - complete - 1.6 - - - - - - complete - 2.0.1 - - - - - - complete - 0.19.1 - - - - - - complete - 1.3 - - - - - - complete - 1.0 - - - - - - complete - 1.2 - - - - - - complete - 1.0.3 - - - - - - complete - 1.0 - - - - - - complete - 0.13.1 - - - - - - complete - 1.0 - - - - - - complete - 0.6.3 - - - - - - complete - 1.0.2 - opt-in - - - - - - complete - 0.3 - - - - - - complete - 0.3.0 - - - - - - complete - 0.4.0 - Only available in the version distributed over Google Play - - - - - - complete - 1.1.0 - - - - - - complete - 0.2 - - - - - - complete - 0.3.0 - - - - - - complete - 0.1.2 - 2.5.8 - - - - - - complete - 0.6.0 - 2.3.1 - - - - - - complete - 0.1.4 - 1.22.0 - - - - - - complete - 0.1 - 2.5.8 - - - - - - complete - 0.2.1 - - - - - - complete - 1.0.1 - 2.5.4 - - - - - - complete - 0.2.0 - - - - + + + + complete + 1.4 + + + + + + complete + 2.5rc3 + + + + + + complete + 1.32.0 + + + + + + complete + 1.1 + + + + + + complete + 1.1.3 + + + + + + complete + 2.1 + + + + + + complete + 1.1 + + + + + + complete + 1.5.1 + + + + + + complete + 1.2.1 + Avatar, Nick, OMEMO + + + + + + complete + 1.1.2 + File transfer only + + + + + + complete + 1.1 + read only + + + + + + complete + 1.4.0 + + + + + + complete + 1.3 + + + + + + complete + 1.6 + + + + + + complete + 2.0.1 + + + + + + complete + 0.19.1 + + + + + + complete + 1.3 + + + + + + complete + 1.0 + + + + + + complete + 1.2 + + + + + + complete + 1.0.3 + + + + + + complete + 1.0 + + + + + + complete + 0.13.1 + + + + + + complete + 1.0 + + + + + + complete + 0.6.3 + + + + + + complete + 1.0.2 + opt-in + + + + + + complete + 0.3 + + + + + + complete + 0.3.0 + + + + + + complete + 0.4.0 + Only available in the version distributed over Google Play + + + + + + complete + 1.1.0 + + + + + + complete + 0.2 + + + + + + complete + 0.3.0 + + + + + + complete + 0.1.2 + 2.5.8 + + + + + + complete + 0.6.0 + 2.3.1 + + + + + + complete + 0.1.4 + 1.22.0 + + + + + + complete + 0.1 + 2.5.8 + + + + + + complete + 0.2.1 + + + + + + complete + 1.0.1 + 2.5.4 + + + + + + complete + 0.2.0 + + From ed46d1211566abcc91631b025eccaab1238434cc Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 20 Sep 2019 10:00:57 +0200 Subject: [PATCH 18/36] close correct socket after faulty jingle socks connection --- .../siacs/conversations/xmpp/jingle/JingleSocks5Transport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java index 41aa75ad9..be72d9327 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -142,7 +142,7 @@ public class JingleSocks5Transport extends JingleTransport { this.isEstablished = true; FileBackend.close(serverSocket); } else { - this.socket.close(); + FileBackend.close(socket); } } else { socket.close(); From 3820950408fc34f786ff125f8ed11d07da5c2a7d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 21 Sep 2019 10:51:05 +0200 Subject: [PATCH 19/36] show context menu in channel search to share uri --- .../ui/ChannelDiscoveryActivity.java | 18 +++++++--- .../ui/StartConversationActivity.java | 10 ++++-- .../adapter/ChannelSearchResultAdapter.java | 36 ++++++++++++++----- src/main/res/menu/channel_item_context.xml | 6 ++++ 4 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 src/main/res/menu/channel_item_context.xml diff --git a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java index ab5b66b10..af96f700a 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java @@ -37,11 +37,8 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O private static final String CHANNEL_DISCOVERY_OPT_IN = "channel_discovery_opt_in"; private final ChannelSearchResultAdapter adapter = new ChannelSearchResultAdapter(); - - private ActivityChannelDiscoveryBinding binding; - private final PendingItem mInitialSearchValue = new PendingItem<>(); - + private ActivityChannelDiscoveryBinding binding; private MenuItem mMenuSearchView; private EditText mSearchEditText; @@ -198,6 +195,19 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O } + @Override + public boolean onContextItemSelected(MenuItem item) { + final MuclumbusService.Room room = adapter.getCurrent(); + if (room != null) { + switch (item.getItemId()) { + case R.id.share_with: + StartConversationActivity.shareAsChannel(this, room.address); + return true; + } + } + return false; + } + public void joinChannelSearchResult(String accountJid, MuclumbusService.Room result) { final boolean syncAutojoin = getBooleanPreference("autojoin", R.bool.autojoin); Account account = xmppConnectionService.findAccountByJid(Jid.of(accountJid)); diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 4de40206c..d1834e62a 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -405,14 +405,18 @@ public class StartConversationActivity extends XmppActivity implements XmppConne protected void shareBookmarkUri(int position) { Bookmark bookmark = (Bookmark) conferences.get(position); + shareAsChannel(this, bookmark.getJid().asBareJid().toEscapedString()); + } + + public static void shareAsChannel(final Context context, final String address) { Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_TEXT, "xmpp:" + bookmark.getJid().asBareJid().toEscapedString() + "?join"); + shareIntent.putExtra(Intent.EXTRA_TEXT, "xmpp:" + address + "?join"); shareIntent.setType("text/plain"); try { - startActivity(Intent.createChooser(shareIntent, getText(R.string.share_uri_with))); + context.startActivity(Intent.createChooser(shareIntent, context.getText(R.string.share_uri_with))); } catch (ActivityNotFoundException e) { - Toast.makeText(this, 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(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java index d39910cfd..5ba28c446 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java @@ -1,11 +1,13 @@ package eu.siacs.conversations.ui.adapter; +import android.app.Activity; import android.databinding.DataBindingUtil; import android.support.annotation.NonNull; import android.support.v7.recyclerview.extensions.ListAdapter; import android.support.v7.util.DiffUtil; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; +import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -15,12 +17,11 @@ import java.util.Locale; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.SearchResultItemBinding; import eu.siacs.conversations.http.services.MuclumbusService; +import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.ui.util.AvatarWorkerTask; import rocks.xmpp.addr.Jid; -public class ChannelSearchResultAdapter extends ListAdapter { - - private OnChannelSearchResultSelected listener; +public class ChannelSearchResultAdapter extends ListAdapter implements View.OnCreateContextMenuListener { private static final DiffUtil.ItemCallback DIFF = new DiffUtil.ItemCallback() { @Override @@ -33,6 +34,8 @@ public class ChannelSearchResultAdapter extends ListAdapter listener.onChannelSearchResult(searchResult)); + final View root = viewHolder.binding.getRoot(); + root.setTag(searchResult); + root.setOnClickListener(v -> listener.onChannelSearchResult(searchResult)); + root.setOnCreateContextMenuListener(this); } public void setOnChannelSearchResultSelectedListener(OnChannelSearchResultSelected listener) { this.listener = listener; } + public MuclumbusService.Room getCurrent() { + return this.current; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + final Activity activity = XmppActivity.find(v); + final Object tag = v.getTag(); + if (activity != null && tag instanceof MuclumbusService.Room) { + activity.getMenuInflater().inflate(R.menu.channel_item_context, menu); + this.current = (MuclumbusService.Room) tag; + } + } + + + public interface OnChannelSearchResultSelected { + void onChannelSearchResult(MuclumbusService.Room result); + } public static class ViewHolder extends RecyclerView.ViewHolder { @@ -82,8 +106,4 @@ public class ChannelSearchResultAdapter extends ListAdapter + + + \ No newline at end of file From d2ef0728a38a0467b71144cf0b41c6b041daa344 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 21 Sep 2019 10:52:34 +0200 Subject: [PATCH 20/36] pulled translations from transifex --- src/main/res/values-nl/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index 5416974d3..81670b3e7 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -30,6 +30,7 @@ zojuist 1 min. geleden %d min. geleden + %d ongelezen gesprekken versturen… Bericht aan het ontsleutelen. Even geduld… OpenPGP-versleuteld bericht From 426090c301c4ca84379eca7f651bc0882362b02a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 22 Sep 2019 10:00:09 +0200 Subject: [PATCH 21/36] do not parse invites from type=groupchat --- .../conversations/parser/MessageParser.java | 55 +++++++++++-------- .../conversations/parser/PresenceParser.java | 4 +- .../eu/siacs/conversations/xml/Namespace.java | 1 + 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index fba10bd72..1e7aacb1d 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -153,31 +153,28 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece return null; } - private Invite extractInvite(Account account, Element message) { - Element x = message.findChild("x", "http://jabber.org/protocol/muc#user"); - if (x != null) { - Element invite = x.findChild("invite"); + private Invite extractInvite(Element message) { + final Element mucUser = message.findChild("x", Namespace.MUC_USER); + if (mucUser != null) { + Element invite = mucUser.findChild("invite"); if (invite != null) { - String password = x.findChildContent("password"); + String password = mucUser.findChildContent("password"); Jid from = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("from")); - Contact contact = from == null ? null : account.getRoster().getContact(from); Jid room = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from")); if (room == null) { return null; } - return new Invite(room, password, contact); + return new Invite(room, password, false, from); } - } else { - x = message.findChild("x", "jabber:x:conference"); - if (x != null) { - Jid from = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from")); - Contact contact = from == null ? null : account.getRoster().getContact(from); - Jid room = InvalidJid.getNullForInvalid(x.getAttributeAsJid("jid")); - if (room == null) { - return null; - } - return new Invite(room, x.getAttribute("password"), contact); + } + final Element conference = message.findChild("x", "jabber:x:conference"); + if (conference != null) { + Jid from = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from")); + Jid room = InvalidJid.getNullForInvalid(conference.getAttributeAsJid("jid")); + if (room == null) { + return null; } + return new Invite(room, conference.getAttribute("password"), true, from); } return null; } @@ -335,7 +332,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet)); } final LocalizedContent body = packet.getBody(); - final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); + final Element mucUserElement = packet.findChild("x", Namespace.MUC_USER); final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted"); final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0"); final Element oob = packet.findChild("x", Namespace.OOB); @@ -383,9 +380,16 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece selfAddressed = false; } - Invite invite = extractInvite(account, packet); - if (invite != null && invite.execute(account)) { - return; + final Invite invite = extractInvite(packet); + if (invite != null) { + if (isTypeGroupChat) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignoring invite to "+invite.jid+" because type=groupchat"); + } else if (invite.direct && (mucUserElement != null || invite.inviter == null || mXmppConnectionService.isMuc(account, invite.inviter))) { + Log.d(Config.LOGTAG, account.getJid().asBareJid()+": ignoring direct invite to "+invite.jid+" because it was received in MUC"); + } else { + invite.execute(account); + return; + } } if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null || xP1S3 != null) && !isMucStatusMessage) { @@ -884,11 +888,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece private class Invite { final Jid jid; final String password; - final Contact inviter; + final boolean direct; + final Jid inviter; - Invite(Jid jid, String password, Contact inviter) { + Invite(Jid jid, String password, boolean direct, Jid inviter) { this.jid = jid; this.password = password; + this.direct = direct; this.inviter = inviter; } @@ -901,7 +907,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } else { conversation.getMucOptions().setPassword(password); mXmppConnectionService.databaseBackend.updateConversation(conversation); - mXmppConnectionService.joinMuc(conversation, inviter != null && inviter.mutualPresenceSubscription()); + final Contact contact = inviter != null ? account.getRoster().getContactFromContactList(inviter) : null; + mXmppConnectionService.joinMuc(conversation, contact != null && contact.mutualPresenceSubscription()); mXmppConnectionService.updateConversationUi(); } return true; diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 065c06023..9273df976 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -60,7 +60,7 @@ public class PresenceParser extends AbstractParser implements final Jid from = packet.getFrom(); if (!from.isBareJid()) { final String type = packet.getAttribute("type"); - final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user"); + final Element x = packet.findChild("x", Namespace.MUC_USER); Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update")); final List codes = getStatusCodes(x); if (type == null) { @@ -364,7 +364,7 @@ public class PresenceParser extends AbstractParser implements @Override public void onPresencePacketReceived(Account account, PresencePacket packet) { - if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) { + if (packet.hasChild("x", Namespace.MUC_USER)) { this.parseConferencePresence(packet, account); } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) { this.parseConferencePresence(packet, account); diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 55d54853e..26a0b33a2 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -32,4 +32,5 @@ public final class Namespace { public static final String COMMANDS = "http://jabber.org/protocol/commands"; public static final String JINGLE_ENCRYPTED_TRANSPORT = "urn:xmpp:jingle:jet:0"; public static final String JINGLE_ENCRYPTED_TRANSPORT_OMEMO = "urn:xmpp:jingle:jet-omemo:0"; + public static final String MUC_USER = "http://jabber.org/protocol/muc#user"; } From 75d35c357e11565e21b6d9230b4938c741305da8 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 22 Sep 2019 11:39:34 +0200 Subject: [PATCH 22/36] print emoji only status messages larger --- .../conversations/ui/ContactDetailsActivity.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index ee95ae7b6..086ab7101 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -13,6 +13,9 @@ import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Intents; import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.RelativeSizeSpan; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -49,6 +52,7 @@ import eu.siacs.conversations.ui.util.JidDialog; import eu.siacs.conversations.ui.util.MenuDoubleTabUtil; import eu.siacs.conversations.utils.AccountUtils; import eu.siacs.conversations.utils.Compatibility; +import eu.siacs.conversations.utils.Emoticons; import eu.siacs.conversations.utils.IrregularUnicodeDetector; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.XmppUri; @@ -328,14 +332,19 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp List statusMessages = contact.getPresences().getStatusMessages(); if (statusMessages.size() == 0) { binding.statusMessage.setVisibility(View.GONE); + } else if (statusMessages.size() == 1) { + final String message = statusMessages.get(0); + binding.statusMessage.setVisibility(View.VISIBLE); + final Spannable span = new SpannableString(message); + if (Emoticons.isOnlyEmoji(message)) { + span.setSpan(new RelativeSizeSpan(2.0f), 0, message.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + binding.statusMessage.setText(span); } else { StringBuilder builder = new StringBuilder(); binding.statusMessage.setVisibility(View.VISIBLE); int s = statusMessages.size(); for (int i = 0; i < s; ++i) { - if (s > 1) { - builder.append("• "); - } builder.append(statusMessages.get(i)); if (i < s - 1) { builder.append("\n"); From 98384314cd581ed6e548410f077253f68356f758 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 22 Sep 2019 11:43:44 +0200 Subject: [PATCH 23/36] clarify transifex procedure --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 322212ddc..389a59680 100644 --- a/README.md +++ b/README.md @@ -285,7 +285,11 @@ Conversations is trying to get rid of old behaviours and set an example for other clients. #### Translations -Translations are managed on [Transifex](https://www.transifex.com/projects/p/conversations/) +Translations are managed on [Transifex](https://www.transifex.com/projects/p/conversations/). +If you want to become a translator Please register on transifex, apply to join +the translation team and then step by our group chat on +[conversations@conference.siacs.eu](xmpp:conversations@conference.siacs.eu?join) +and introduce yourself to `iNPUTmice` so he can approve your join request. #### How do I backup / move Conversations to a new device? On the one hand Conversations supports Message Archive Management to keep a server side history of your messages so when migrating to a new device that device can display your entire history. However that does not work if you enable OMEMO due to its forward secrecy. (Read [The State of Mobile XMPP in 2016](https://gultsch.de/xmpp_2016.html) especially the section on encryption.) From bea94d565b4e55ab12a19453805ed0a704122e71 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 22 Sep 2019 11:45:10 +0200 Subject: [PATCH 24/36] use http link for join --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 389a59680..6a63d59c8 100644 --- a/README.md +++ b/README.md @@ -288,7 +288,7 @@ other clients. Translations are managed on [Transifex](https://www.transifex.com/projects/p/conversations/). If you want to become a translator Please register on transifex, apply to join the translation team and then step by our group chat on -[conversations@conference.siacs.eu](xmpp:conversations@conference.siacs.eu?join) +[conversations@conference.siacs.eu](https://conversations.im/j/conversations@conference.siacs.eu) and introduce yourself to `iNPUTmice` so he can approve your join request. #### How do I backup / move Conversations to a new device? From 3733148ac8b5b771a400c17940dc225a39adad01 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Sep 2019 12:11:51 +0200 Subject: [PATCH 25/36] version bump to 2.5.9 + changelog --- CHANGELOG.md | 6 +++++- build.gradle | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73f0f42f3..bea65ac7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog -## Version 2.5.8 +### Version 2.5.9 +* fixed minor security issues +* Share XMPP uri from channel search by long pressing a result + +### Version 2.5.8 * fixed connection issues over Tor * P2P file transfer (Jingle) now offers direct candidates * Support XEP-0396: Jingle Encrypted Transports - OMEMO diff --git a/build.gradle b/build.gradle index e24b05d42..08fedf691 100644 --- a/build.gradle +++ b/build.gradle @@ -83,8 +83,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 28 - versionCode 338 - versionName "2.5.8" + versionCode 339 + versionName "2.5.9" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId From 92e27a3649b601cc4221fa30e6d6c04a11e415d3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Sep 2019 12:12:20 +0200 Subject: [PATCH 26/36] pulled translations from transifex --- src/conversations/res/values-ar/strings.xml | 6 ++ .../res/values-zh-rCN/strings.xml | 7 ++ src/main/res/values-ar/strings.xml | 100 +++++++++++++++++- src/quicksy/res/values-ar/strings.xml | 1 + 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/conversations/res/values-ar/strings.xml create mode 100644 src/conversations/res/values-zh-rCN/strings.xml diff --git a/src/conversations/res/values-ar/strings.xml b/src/conversations/res/values-ar/strings.xml new file mode 100644 index 000000000..1e820a9cc --- /dev/null +++ b/src/conversations/res/values-ar/strings.xml @@ -0,0 +1,6 @@ + + + اختر مزود خدمة XMPP الخاص بك + استخدِم conversations.im + أنشئ حسابًا جديدًا + \ No newline at end of file diff --git a/src/conversations/res/values-zh-rCN/strings.xml b/src/conversations/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..ff6a0c393 --- /dev/null +++ b/src/conversations/res/values-zh-rCN/strings.xml @@ -0,0 +1,7 @@ + + + 选择您的XMPP提供者 + 使用 conversations.im + 创建新账户 + 您已经拥有一个XMPP账户了吗?如果您之前使用过其他的XMPP客户端的话,那么您已经拥有这种账户了。如果没有账户的话,您可以现在创建一个。\n提示:有些电子邮件服务也提供XMPP账户。 + \ No newline at end of file diff --git a/src/main/res/values-ar/strings.xml b/src/main/res/values-ar/strings.xml index 22967c71e..7492922a0 100644 --- a/src/main/res/values-ar/strings.xml +++ b/src/main/res/values-ar/strings.xml @@ -7,6 +7,7 @@ أغلق هذه المحادثة بيانات جهة الإتصال تفاصيل مجموعة المحادثة + تفاصيل القناة تشفير المحادثة إضافة حساب تعديل الإسم @@ -16,6 +17,8 @@ إنهاء حجب جهة اتصال حجب دومين إنهاء حجب دومين + احجب المشارِك + إلغاء حجب المشارِك إدارة الحسابات إعدادات مشاركة مع محادثة @@ -50,6 +53,7 @@ مشاركة مع إبداء المحادثة دعوة جهة إتصال + دعوة جهات الإتصال جهة إتصال الغاء @@ -143,6 +147,8 @@ فشلت عملية التفاوض عبر TLS خرق للقواعد لا يتوافق مع السيرفر + خطأ في التدفق + خطأ عند فتح التدفق غير مشفر رسالة مشفرة عبر OTR رسالة مشفرة عبر OpenPGP @@ -157,6 +163,8 @@ هل أنت متأكد ؟ إذا قمت حذفت حسابك، فسوف تفقد سجل محادثاتك بالكامل تسجيل صوت + عنوان XMPP + احجب عنوان XMPP username@example.com كلمة السر خارج الذاكرة. الصورة كبيرة جدا @@ -186,11 +194,13 @@ بصمة OMEMO بصمة v\\OMEMO بصمة OMEMO للرسالة + بصمة v\\OMEMO للرسالة أجهزة أخرى الثقة في بصمات أوميمو OMEMO جارإحضار المفاتيح ... تم فك الشيفرة + الفواصل المرجعية بحث قم بإدخال جهة إتصال حذف جهة الإتصال @@ -200,11 +210,18 @@ أضف إختر جهة الاتصال موجودة لديك مسبقا - دخول + التحق + channel@conference.example.com/nick + channel@conference.example.com حفظ بالمفضلة إحذف من المفضلة + دمر فريق المحادثة + دمر القناة + لم نتمكن مِن تدمير فريق المحادثة + لم نتمكن مِن تدمير القناة موجوده بالمفضلة سابقا تعديل موضوع مجموعة المحادثة + الموضوع في صدد الإنظمام إلى مجموعة المحادثة ... غادر جهة اتصال أضافتك @@ -237,6 +254,7 @@ السماح لمراسليك بتعديل رسائلهم إعدادات متقدمة كن حذراً مع هذه من فضلك + عن %s ساعات السكون وقت البداية وقت النهاية @@ -244,6 +262,7 @@ سوف تكتم التنبيهات إبان ساعات السكون طلب تقارير تسليم الرسائل أخرى + زامِن مع الفواصل المرجعية تم نسخ بصمة OMEMO إلى الحافظة ! حسابك محظور للإلتحاق بمجموعة المحادثة هذه هذه المجموعة متاحة للأعضاء المنتمين إليها فقط @@ -253,11 +272,16 @@ أنت تستعمل حساب %s انقطع الإتصال .. حاول مرة أخرى تحقق من حجم %s + تحقق مِن حجم %1$s على %2$s خيارات الرسالة إقتبس + ألصقه كاقتباس أنسخ الرابط الأصلي أعد الإرسال رابط الملف + تم نسخ عنوان الـ XMPP إلى الحافظة + تم نسخ رسالة الخطأ إلى الحافظة + عنوان الويب إمسح شفرة التّعرّف 2D أظهر شفرة التّعرّف 2D إعرض قائمة المحبوسين @@ -266,6 +290,10 @@ حاول مرة أخرى احتفظ بالتطبيق يعمل في المقدمة منع نظام التشغيل من انهاء اتصالك + أنشئ نسخة احتياطية + تم إنشاء نسختك الاحتياطية + تم استرجاع نسختك الاحتياطية + لا تنسى تنشيط الحساب. اختيار ملف اكتمل الإستلام %1$s (%2$d%% بنسبة) تنزيل %s @@ -280,8 +308,10 @@ تم حذف الملف لا يوجد تطبيق متاح لعرض الملف تعذر العثور على تطبيق يمكنه فتح الرابط + وسوم ديناميكية عرض علامات للقراءة فقط أسفل بيانات جهات الإتصال تفعيل الإشعارات + لم يُعثر على أي خادم للمحادثات الجماعية فشلت عملية إنشاء مجموعة المحادثة ! الصورة الرمزية للحساب انسخ بصمة OMEMO إلى الحافظة @@ -308,11 +338,14 @@ منح امتيازات الإداره إلغاء امتيازات الإدارة التنحية من مجموعة المحادثة + أزله مِن القناة لا يمكن تغيير انتساب %s الحظر من دخول مجموعة المحادثة حظر الآن لا يمكن تغيير دول %s + إعدادات القناة العمومية سرِّي ، للأعضاء فقط + اجعل القناة تحت الإشراف لست مشتركا في المجموعة تم تعديل خيارات فريق المحادثة ! تعذر تغيير خيارات فريق المحادثة @@ -359,6 +392,7 @@ التي تم استعمالها كثيرا مؤخرا إختر حركة سريعة البحث في جهات الإتصال + البحث في الفواصل المرجعية إبعث برسالة على الخاص لقد غادَر %1$s فريق المحادثة ! إسم المستخدم @@ -413,6 +447,7 @@ تعطيل الإخطارات الإشعارات موقفة ضغط الصورة + تغيير حجم الصور وضغطها دائماً آليا وضع تحسين أداء البطارية مفعّل @@ -426,6 +461,8 @@ خطأ في الأمان : نفاذ غير سليم إلى ملف تعذر العثور على تطبيق يُمكنُ بواسطته مشاركة الرابط شارك الرابط مع ... + وافق ثم واصل + عنوان XMPP الخاص بك سيكون: %s إنشاء حساب إستخدم مزودي الخاص إختر إسم المستخدم @@ -442,6 +479,7 @@ إختر المشاركين جارٍ إنشاء مجموعة المحادثة ... أعد إرسال الدعوة + تعطيل قصير متوسط طويل @@ -558,9 +596,11 @@ لا يمكن تسجيل حسابات على هذا الخادوم إلا عبر موقع الويب فتح موقع الإنترنت تعذر العثور على تطبيق يُمكنه فتح موقع الويب + أظهر الإشعارات العلوية اليوم البارحة التحقق من صحة إسم المضيف بواسطة DNSSEC + الشهادة لا تحتوي على عنوان XMPP جُزْئِيًّا تسجيل فيديو النسخ إلى الحافظة @@ -574,11 +614,18 @@ تفاصيل الشهادة : مرة واحدة السحب إلى أسفل + التمرير إلى أسفل بعد إرسال رسالة تعديل رسالة حالة الحضور تعديل رسالة حالة الحضور تعطيل التعمية تعذر جلب قائمة الأجهزة تعطيله حالًا + المسودة: + التعمية بـ OMEMO + سوف يُستخدَم OMEMO افتراضيا في المحادثات الجديدة. + حجم الخط + نشِط مبدئيًا + معطل مبدئيًا صغير متوسط كبير @@ -590,16 +637,28 @@ مشاركة الموقع إظهار الموقع مشاركة + يرجى الانتظار… + البحث عن رسائل مشاهدة المحادثة نسخ العنوان الإلكتروني + انسخ عنوان الـ XMPP بحث مباشر إسم جهة الإتصال إسم مستعار إسم + إدخال الاسم اختياري + اسم فريق المحادثة + لا يمكن حفظ التسجيل + الخدمة الأمامية + معلومات عن الحالة مشاكل إتّصال رسائل رسائل + إعدادات الإشعار ضغط الفيديو + اعرض الوسائط + اعرض المشارِكين + المشارِكون جودة الفيديو متوسط (360ب) عالي (720ب) @@ -607,10 +666,22 @@ إختار الدولة رقم هاتف تحقق من رقم هاتفك + ليس %s برقم هاتف صالح. + يرجى إدخال رقم هاتفك. + البحث عن الدول + تحقق مِن %s إعادة إرسال الإرسالية القصيرة + يرجى الانتظار (%s) رجوع نعم لا + جارٍ التحقق… + جارٍ طلب الرسالة النصية القصيرة… + خطأ شبكي مجهول. + تعذر الربط بالخادم. + ليس هناك اتصال بالشبكة. + يرجى إعادة المحاولة في غضون %s + تحديث إسمك أدخل إسمك إضغط على زرّ التعديل لضبط إسمك @@ -622,4 +693,31 @@ إفتح بـ... صورة حساب كونفرسايشنز إختيار الحساب + استرجِع نسخة احتياطية + استرجِع + ادخِل عنوان XMPP + أنشئ فريق محادثة + إلتحِق بقناة عمومية + أنشئ فريق محادثة خاص + أنشئ قناة عمومية + اسم القناة + عنوان XMPP + يرجى إدخال اسمٍ للقناة + جارٍ إنشاء القناة العمومية… + لقد التحقت بقناة موجودة سابقا + اسمح لأي كان دعوة الآخرين + يمكن للمالِكين تعديل الموضوع. + إدارة الصلاحيات + البحث عن مشارِكين + حجم الملف كبير جدًا + أرفِق + استكشاف القنوات + البحث عن قنوات + لدي حساب + إضافة حساب موجود + تسجيل حساب جديد + أضفه على أي حال + حَدَث + افتح النسخة الاحتياطية + يرجى إدخال الكلمة السرية للحساب diff --git a/src/quicksy/res/values-ar/strings.xml b/src/quicksy/res/values-ar/strings.xml index b3b1a2755..c58bd0b70 100644 --- a/src/quicksy/res/values-ar/strings.xml +++ b/src/quicksy/res/values-ar/strings.xml @@ -6,4 +6,5 @@ إجعل كلّ جهات إتصالك تعلم أنك تستعمل كويكسي كويكسي يحتاج الإتصال بالمايكروفون صورة حساب كويكسي + إن كويكسي Quicksy غير متوفر في بلدكم. From 6c5b71440f2a0e818b4a8dd2185711fa76ce73a0 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Sep 2019 17:29:34 +0200 Subject: [PATCH 27/36] channel search result long press to show join dialog --- .../ui/ChannelDiscoveryActivity.java | 9 +++++++ .../ui/JoinConferenceDialog.java | 7 ++--- .../ui/StartConversationActivity.java | 27 ++++++++++++++----- src/main/res/menu/channel_item_context.xml | 3 +++ src/main/res/values/strings.xml | 1 + 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java index af96f700a..10bd4ced1 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java @@ -2,8 +2,10 @@ package eu.siacs.conversations.ui; import android.app.AlertDialog; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.databinding.DataBindingUtil; +import android.net.Uri; import android.os.Bundle; import android.support.v7.widget.Toolbar; import android.text.Html; @@ -203,6 +205,13 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O case R.id.share_with: StartConversationActivity.shareAsChannel(this, room.address); return true; + case R.id.open_join_dialog: + final Intent intent = new Intent(this, StartConversationActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra("force_dialog", true); + intent.setData(Uri.parse(String.format("xmpp:%s?join", room.address))); + startActivity(intent); + return true; } } return false; diff --git a/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java b/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java index b9c9b15b1..05dca588d 100644 --- a/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java @@ -4,6 +4,7 @@ import android.app.Activity; import android.app.Dialog; import android.databinding.DataBindingUtil; import android.support.annotation.NonNull; +import android.support.design.widget.TextInputLayout; import android.support.v4.app.DialogFragment; import android.content.Context; import android.content.DialogInterface; @@ -65,9 +66,9 @@ public class JoinConferenceDialog extends DialogFragment implements OnBackendCon builder.setNegativeButton(R.string.cancel, null); AlertDialog dialog = builder.create(); dialog.show(); - dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(view -> mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.jid, binding.bookmark.isChecked())); + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(view -> mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.accountJidLayout, binding.jid, binding.bookmark.isChecked())); binding.jid.setOnEditorActionListener((v, actionId, event) -> { - mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.jid, binding.bookmark.isChecked()); + mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.accountJidLayout, binding.jid, binding.bookmark.isChecked()); return true; }); return dialog; @@ -116,6 +117,6 @@ public class JoinConferenceDialog extends DialogFragment implements OnBackendCon } public interface JoinConferenceDialogListener { - void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, AutoCompleteTextView jid, boolean isBookmarkChecked); + void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, TextInputLayout jidLayout, AutoCompleteTextView jid, boolean isBookmarkChecked); } } diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index d1834e62a..9264d039e 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -15,6 +15,7 @@ import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.design.widget.TextInputLayout; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; @@ -837,6 +838,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne if (uri != null) { Invite invite = new Invite(intent.getData(), intent.getBooleanExtra("scanned", false)); invite.account = intent.getStringExtra("account"); + invite.forceDialog = intent.getBooleanExtra("force_dialog", false); return invite.invite(); } else { return false; @@ -849,7 +851,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne List contacts = xmppConnectionService.findContacts(invite.getJid(), invite.account); if (invite.isAction(XmppUri.ACTION_JOIN)) { Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid()); - if (muc != null) { + if (muc != null && !invite.forceDialog) { switchToConversationDoNotAppend(muc, invite.getBody()); return true; } else { @@ -1004,7 +1006,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } @Override - public void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, AutoCompleteTextView jid, boolean isBookmarkChecked) { + public void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, TextInputLayout layout, AutoCompleteTextView jid, boolean isBookmarkChecked) { if (!xmppConnectionServiceBound) { return; } @@ -1012,17 +1014,26 @@ public class StartConversationActivity extends XmppActivity implements XmppConne if (account == null) { return; } - final Jid conferenceJid; + final String input = jid.getText().toString(); + Jid conferenceJid; try { - conferenceJid = Jid.of(jid.getText().toString()); + conferenceJid = Jid.of(input); } catch (final IllegalArgumentException e) { - jid.setError(getString(R.string.invalid_jid)); - return; + final XmppUri xmppUri = new XmppUri(input); + if (xmppUri.isJidValid() && xmppUri.isAction(XmppUri.ACTION_JOIN)) { + final Editable editable = jid.getEditableText(); + editable.clear(); + editable.append(xmppUri.getJid().toEscapedString()); + conferenceJid = xmppUri.getJid(); + } else { + layout.setError(getString(R.string.invalid_jid)); + return; + } } if (isBookmarkChecked) { if (account.hasBookmarkFor(conferenceJid)) { - jid.setError(getString(R.string.bookmark_already_exists)); + layout.setError(getString(R.string.bookmark_already_exists)); } else { final Bookmark bookmark = new Bookmark(account, conferenceJid.asBareJid()); bookmark.setAutojoin(getBooleanPreference("autojoin", R.bool.autojoin)); @@ -1278,6 +1289,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne public String account; + public boolean forceDialog = false; + public Invite(final Uri uri) { super(uri); } diff --git a/src/main/res/menu/channel_item_context.xml b/src/main/res/menu/channel_item_context.xml index 7d26a42dd..a274053e5 100644 --- a/src/main/res/menu/channel_item_context.xml +++ b/src/main/res/menu/channel_item_context.xml @@ -3,4 +3,7 @@ + \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index cf949b85b..b818fad2e 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -876,4 +876,5 @@ This account has already been setup Please enter the password for this account Unable to perform this action + Join public channel… From e2886098fe1c99047b5b9d53d7c52c28e98bcaec Mon Sep 17 00:00:00 2001 From: Marcel Lippmann Date: Wed, 25 Sep 2019 18:15:36 +0200 Subject: [PATCH 28/36] Correct a small typo in Romanian translation --- src/main/res/values-ro-rRO/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index b73a0fa8b..23135b756 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -413,7 +413,7 @@ Trimit %s Ofer %s Ascunde deconectat - %s tasteaza... + %s tastează... %s s-a oprit din scris %s tastează... %s s-au oprit din scris From c749aa1cae26a12319661034d1ee45c7b5bc3e77 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 25 Sep 2019 21:12:12 +0200 Subject: [PATCH 29/36] bumped okhttp --- build.gradle | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 08fedf691..7abcacc9d 100644 --- a/build.gradle +++ b/build.gradle @@ -63,13 +63,14 @@ dependencies { implementation project(':libs:xmpp-addr') implementation 'org.osmdroid:osmdroid-android:6.1.0' implementation 'org.hsluv:hsluv:0.2' - implementation 'org.conscrypt:conscrypt-android:2.1.0' + implementation 'org.conscrypt:conscrypt-android:2.2.1' implementation 'me.drakeet.support:toastcompat:1.1.0' implementation "com.leinardi.android:speed-dial:2.0.1" implementation 'com.squareup.retrofit2:retrofit:2.6.1' implementation 'com.squareup.retrofit2:converter-gson:2.6.1' + implementation 'com.squareup.okhttp3:okhttp:3.14.3' implementation 'com.google.guava:guava:27.1-android' - quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.10.1' + quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.10.16' } ext { From 4dd4886758603c51e951bc4dd239f43bc3a0288c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 25 Sep 2019 21:19:09 +0200 Subject: [PATCH 30/36] pulled translations from transifex --- src/conversations/res/values-sv/strings.xml | 5 + src/main/res/values-sv/strings.xml | 111 +++++++++++++ src/main/res/values-zh-rCN/strings.xml | 171 +++++++++++++++++--- src/quicksy/res/values-sv/strings.xml | 9 ++ src/quicksy/res/values-zh-rCN/strings.xml | 6 +- 5 files changed, 281 insertions(+), 21 deletions(-) create mode 100644 src/conversations/res/values-sv/strings.xml create mode 100644 src/quicksy/res/values-sv/strings.xml diff --git a/src/conversations/res/values-sv/strings.xml b/src/conversations/res/values-sv/strings.xml new file mode 100644 index 000000000..9212ad109 --- /dev/null +++ b/src/conversations/res/values-sv/strings.xml @@ -0,0 +1,5 @@ + + + Använd conversations.im + Skapa nytt konto + \ No newline at end of file diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index f95042863..a654e7ca6 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -3,7 +3,10 @@ Inställningar Ny konversation Kontoinställningar + Stäng denna konversation Kontaktdetaljer + Gruppchattdetaljer + Kanaldetaljer Säker konversation Lägg till konto Ändra namn @@ -13,18 +16,25 @@ Avblockera kontakt Blockera domän Avblockera domän + Blockera deltagare + Avblockera deltagare Hantera konton Inställningar Dela med konversation Starta konversation + Välj kontakt + Välj kontakter + Dela via konto Blockeringslista just nu 1 min sedan %d min sedan + %d olästa konversationer skickar… Avkrypterar meddelande. Vänta… OpenPGP-krypterat meddelande Nick används redan + Ogiltigt nick Admin Ägare Moderator @@ -36,11 +46,16 @@ Blockera alla kontakter från %s? Avblockera alla kontakter från %s? Kontakt blockerad + Blockerad Vill du ta bort %s som bokmärke? Konversationer associerade med detta bokmärke kommer inte tas bort. Registrera nytt konto på servern Byt lösenord på server Dela med… + Börja konversation + Bjud in kontakt + Bjud in Kontakter + Kontakt Avbryt Sätt Lägg till @@ -66,6 +81,9 @@ Delar filer. Vänta... Rensa historik Rensa konversationshistorik + Är du säker på att du vill ta bort alla meddelanden i denna konversation?\n\nVarning: Detta kommer inte att ta bort kopior av dessa meddelanden på andra enheter eller servrar. + Ta bort fil + Stäng denna konversation efteråt Välj enhet Skicka okrypterat meddelande Skicka meddelande @@ -106,8 +124,10 @@ Bekräfta meddelanden Låt dina kontakter veta när du har mottagit och läst deras meddelanden Gränssnitt + Dålig krypterings-nyckel. Acceptera Ett fel har inträffat + Fel Ditt konto Skicka tillgänglighetsuppdatering Ta emot tillgänglighetsuppdateringar @@ -150,8 +170,11 @@ Är du säker? Om du tar bort ditt konto kommer hela konversationshistoriken att försvinna Spela in röst + XMPP-adress + Blockera XMPP-adress användarnamn@exempel.se Lösenord + Detta är inte en giltig XMPP-adress Slut på minne. Bilden är för stor Vill du lägga till %s i din enhets kontakter? Server-info @@ -186,8 +209,10 @@ Hämtar nycklar... Klar Avkryptera + Bokmärken Sök Fyll i kontakt + Ta bort kontakt Se kontaktdetaljer Blockera kontakt Avblockera kontakt @@ -198,10 +223,13 @@ Spara som bokmärke Ta bort bokmärke Detta bokmärke finns redan + Ämne + Går med i gruppchatt... Lämna Kontakten lade till dig i sin kontaktlista Addera tillbaka %s har läst hit + Alla har läst fram till hit Publicera Tryck på avatarbild för att välja en bild från bildgalleriet Publicerar… @@ -220,6 +248,7 @@ Hoppa över Inaktivera notifieringar Aktivera + Gruppchatten kräver lösenord Fyll i lösenord Begär tillgänglighetsuppdateringar från din kontakt först.\n\nDetta används för att se vilken klient/klienter din kontakt använder. Begär nu @@ -230,6 +259,7 @@ Tillåt att dina kontakter kan ändra sina meddelanden i efterhand Expertinställningar Var försiktig med dem + Om %s Tysta timmar Starttid Sluttid @@ -240,7 +270,10 @@ Mottagna meddelanden markeras med en grön bock om det stöds Färglägg skickaknappen för att indikera kontaktens status Annat + Synkronisera med bokmärken OMEMO-fingeravtryck har kopierats till urklipp! + Resursbegränsning + Gruppchatten stängdes ner använder konto %s Kontrollerar %s på webbserver Du är inte ansluten. Försök igen senare @@ -248,9 +281,14 @@ Kontrollera filstorlek för %1$s på %2$s Meddelandealternativ Citera + Klistra in som citat Kopiera orginal-URL Skicka igen Fil-URL + Kopierade URL till urklipp + Kopierade XMPP-adress till urklipp + Kopierade felmeddelande till urklipp + webbadress Scanna 2D-streckkod Visa 2D-streckkod Visa blockeringslista @@ -259,6 +297,14 @@ Försök igen Håll tjänst i förgrunden Förehindrar operativsystemet att ta ner uppkopplingen + Skapa säkerhetskopia + Säkerhetskopians filer lagras i %s + Skapar filer för säkerhetskopia + Din säkerhetskopia har skapats + Säkerhetskopians filer har lagrats i %s + Återställer säkerhetskopia + Din säkerhetskopia har återställts + Glöm inte att aktivera kontot. Välj fil Tar emot %1$s (%2$d%% klart) Ladda ner %s @@ -272,8 +318,11 @@ filöverföring lyckades inte Filen har blivit borttagen Ingen applikation kunde hittas för att öppna filen + Ingen applikation kunde hittas för att öppna länken + Ingen applikation kunde hittas för att visa kontakten Visa skrivskyddade taggar under kontakter Aktivera notifieringar + Misslyckades skapa gruppchatt! Kontots avatarbild Kopiera OMEMO-fingeravtryck till urklipp Regenerera OMEMO-nyckel @@ -298,6 +347,8 @@ Avancerat läge Bevilja administratörsbehörighet Återkalla administratörsbehörighet + Ta bort från gruppchatt + Ta bort från kanal Kunde inte ändra tillhörigheten för %s Bannlys nu Kunde inte ändra rollen för %s @@ -349,6 +400,7 @@ Senast använd Välj snabbfunktion Skicka privat meddelande + %1$s har lämnat gruppchatten! Användarnamn Användarnamn Inte ett giltigt användanamn @@ -360,8 +412,10 @@ Bind-fel Servern är inte ansvarig för domänen Sönder + Tillgänglighet Status borta när skärmen är av Sätter din tillgänglighet till borta när skrämen är av + \"Stör ej\" i tyst läge Hantera vibrationsläge som tyst läge Utökade anslutningsinställningar Visa val av servernamn och port vid inställning av konto @@ -398,10 +452,14 @@ Delade bild med %s Delade bilder med %s Delade text med %s + Conversations behöver tillgång till extern lagring + Conversations behöver tillgång till kameran Synkronisera med kontakter Notifiera för alla meddelanden Notifieringar deaktiverade Notifieringar pausade + Bildkomprimering + Ändra storlek på och komprimera bilder Alltid Automatiskt Batterioptimeringar aktiverade @@ -417,6 +475,8 @@ Säkerhetsfel: Ogiltig filaccess Ingen applikation kunde hittas för att dela URI Dela URI med... + Godkänn & fortsätt + Din fullständiga XMPP-adress kommer att vara: %s Skapa konto Använd min egen leverantör Välj användarnamn @@ -431,6 +491,7 @@ Registreringfel: Försök igen senare Registreringsfel: Lösenordet är för svagt Välj deltagare + Skapar gruppchatt... Bjud in igen Kort Medium @@ -494,10 +555,35 @@ Verifiera OMEMO-nycklar Lita ej på enhet Är du säker på att du vill ta bort verifieringen av denna enhet?\nDenna enhet och meddelanden som kommer från enheten kommer att markeras som ej pålitliga. + + %d sekund + %d sekunder + + + %d minut + %d minuter + + + %d timme + %d timmar + + + %d dag + %d dagar + + + %d vecka + %d veckor + + + %d månad + %d månader + Automatisk borttagning av meddelanden Ta automatiskt bort meddelanden från denna enhet som är äldre än den konfigurerade tidsramen. Krypterar meddelande Hämtar inte meddelanden på grund av inställningen för borttagning av gamla meddelanden. + Komprimerar video Motsvarande konversationer är stängda. Kontakt blockerad. Notifieringar från främlingar @@ -508,4 +594,29 @@ online just nu Försök dekryptera igen Sessionsfel + Idag + Igår + Certifikatet innehåller ej en XMPP-adress + Spela in video + Meddelande + Godkänn okänt certifikat? + Utkast: + Dela plats + Visa plats + Dela + Kopiera XMPP-adress + Gruppchattens namn + Deltagare + Välj ett land + telefonnummer + Bekräfta ditt telefonnummer + Skapa gruppchatt + Skapa sluten gruppchatt + Kanalnamn + Vänligen ange ett namn på kanalen + Denna kanal finns redan + Du har gått med i en befintlig kanal + Denna slutna gruppchatt har inga deltagare. + Upptäck kanaler + Detta ser ut som en kanaladress diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 889088d43..a66d2fd0e 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -1,33 +1,36 @@ 设置 - 新会话 + 新聊天 管理账户 管理账户 - 关闭对话 + 关闭聊天 联系人详情 群聊详情 频道详情 - 安全对话 + 安全聊天 添加账号 编辑姓名 - 添加到地址薄 - 从列表中删除 - 屏蔽联系人 - 解除联系人屏蔽 - 屏蔽域名 - 解除域名屏蔽 + 添加到联系人 + 从XMPP联系人中删除 + 封禁联系人 + 解封联系人 + 封禁域名 + 解封域名 + 封禁成员 + 解封成员 管理账户 设置 - 分享会话 - 开始会话 + 通过Conversations分享 + 开始聊天 选择联系人 选择联系人 通过帐户分享 - 屏蔽列表 + 封禁列表 刚刚 1分钟前 %d分钟前 + %d条未读消息 正在发送… 解密信息中. 请稍候… OpenPGP 加密的信息 @@ -35,19 +38,19 @@ 无效的用户名 管理员 所有者 - 群主 + 版主 参与者 访客 - 将 %s 从列表中移除? 与该联系人的会话消息不会清除. - 您想阻止%s向您发送消息吗? - 你想解除对 %s 的屏蔽吗,他们将可以发送信息给你? - 屏蔽 %s 中的所有联系人? - 解除对 %s 中所有联系人的屏蔽? - 联系人已屏蔽 + 将 %s 从XMPP联系人中移除? 与该联系人的会话消息不会清除. + 您想封禁%s吗? + 您想解封 %s吗 ? + 封禁 %s 中的所有联系人? + 解封%s 中所有联系人? + 联系人已封禁 已屏蔽 从书签中移除 %s ?相关会话消息不会被清除 . 在服务器上注册新账户 - 在服务器上改变密码 + 在服务器上修改密码 分享…… 开始会话 邀请联系人 @@ -379,9 +382,14 @@ 不能修改 %s 的从属关系 屏蔽群聊 从频道中屏蔽 + %s将被从公共群聊中移除。只有将此用户封禁才能将他从群聊永远移除。 现在屏蔽 不能修改 %s 的角色 + 私密群聊设置 + 公开群聊设置 私密,只有成员可以加入 + 使XMPP地址对所有人可见 + 使群聊受到管理 您尚未参与 群组设置修改成功! 无法更改群组设置 @@ -416,6 +424,8 @@ 无法找到显示位置的应用 位置 会话已关闭 + 离开私密群聊 + 离开公开群聊 不相信系统 CA 所有证书必须人工通过 移除证书 @@ -433,6 +443,7 @@ 最近常用 选择快捷操作 搜索联系人 + 搜索书签 发送私密消息 %1$s 离开了群聊! 用户名 @@ -466,6 +477,7 @@ 需要验证码 输入上图中的文字 证书链不受信任 + XMPP地址与证书不匹配 更新证书 获取 OMEMO 密钥错误! 请用证书验证 OMEMO 密钥! @@ -490,6 +502,7 @@ Conversations 需要外部储存权限 Conversations 需要摄像头权限 同步联系人 + 将服务器端联系人与本地联系人匹配可以显示联系人的全名与头像。\n\n此应用只在本地读取并匹配联系人。\n\n现在应用将请求联系人权限。 为所有信息显示通知 只在被提到时通知 禁用通知 @@ -514,6 +527,9 @@ 安全错误:文件访问权限无效 未找到可以分享此链接的应用 分享链接…… + 同意 & 继续 + 此向导将为您在conversations.im¹上创建一个账户。\n您的联系人可以通过您的XMPP完整地址与您聊天。 + 您的XMPP完整地址将是:%s 创建账户 使用我自己的服务端 输入您的用户名 @@ -641,6 +657,7 @@ 昨天 使用 DNSSEC 来验证主机名 包含已验证的主机名的服务器证书被认为是已验证的 + 证书不包含XMPP地址 部分的 录制视频 复制 @@ -682,6 +699,7 @@ 该设备的消息未加密。 + 解密OMEMO消息失败 撤销 位置分享已停用 固定位置 @@ -699,7 +717,9 @@ GIF 查看对话 分享位置插件 + 不使用内置地图,使用“分享位置”插件 复制web地址 + 复制XMPP地址 用于S3的HTTP文件共享 直接搜索 在“开始对话”屏幕上打开键盘并将光标放在搜索栏中 @@ -726,6 +746,8 @@ 重要性,声音,振动 视频压缩 查看媒体文件 + 查看成员 + 成员 媒体浏览器 文件由于违反安全策略而被删除。 视频质量 @@ -734,4 +756,113 @@ 高(720p) 已取消 你已经在起草一条消息了。 + 功能不支持。 + 无效国家代码 + 选择国家 + 手机号 + 验证手机号 + Quicksy将发送验证码短信(运营商可能收费)。请输入国家代码和手机号: +
%s

。电话号码正确吗?]]>
+ %s不是有效的电话号码 + 请输入手机号。 + 搜索国家 + 验证%s + %s。]]> + 已重新发送6位数验证码短信 + 输入6位数的PIN + 重新发送短信 + 重发短信(%s) + 请稍候(%s) + 返回 + 已自动从剪贴板粘贴验证码 + 请输入6位代码 + 确定放弃注册? + + + 正在验证...... + 请求短信... + 验证码错误。 + 验证码已失效 + 未知网络错误 + 未知服务器应答 + 无法连接服务器。 + 无法建立安全连接。 + 找不到服务器 + 处理请求时出错 + 用户输入无效 + 暂时无法连接。请稍候再试。 + 无网络连接 + 请在%s后重试 + 频率过高 + 尝试次数过多 + 您正在使用旧版应用。 + 更新 + 此号码已在其他设备上登录。 + 请输入您的姓名。这样,对方就能知道您是谁。 + 您的姓名 + 输入姓名 + 点击编辑按钮以编辑用户名。 + 拒绝请求 + 安装Orbot + 启动Orbot + 软件商店未安装 + 此群聊将公开你的XMPP地址 + 电子书 + 原始(未压缩) + 打开方式 + 聊天头像 + 选择账户 + 恢复备份 + 恢复 + 输入%s的密码以恢复备份 + 仅在迁移或丢失原设备时恢复备份。 + 无法恢复备份。 + 无法解密备份。密码是否正确? + 备份与恢复 + 输入XMPP地址 + 创建群聊 + 加入公开群聊 + 创建私密群聊 + 创建公开群聊 + 群聊名称 + XMPP地址 + 请为群聊提供一个名称。 + 请提供XMPP地址。 + 这是一个XMPP地址。请提供一个名称。 + 创建公开群聊 + 群聊已存在 + 您加入了一个已经存在的群聊 + 无法配置群聊 + 允许任何成员修改主题 + 允许任何成员邀请其他人 + 允许任何成员修改主题 + 拥有者可修改话题 + 管理员可修改主题 + 所有者可以邀请其他人 + 允许任何成员邀请其他人 + XMPP地址对管理员可见。 + XMPP地址对所有人可见 + 此公开群聊无成员。邀请成员或使用分享按钮分享地址。 + 此私密群聊无成员 + 管理权限 + 搜索成员 + 文件过大 + 附加 + 发现群聊 + 搜索群聊 + 可能侵犯隐私! + 我已有账户 + 添加已有账户 + 注册新账户 + 这好像是一个域名地址 + 仍然添加 + 这好像是一个群聊地址 + 分享备份文件 + 备份文件 + 事件 + 打开备份 + 选择的文件不是备份文件 + 账户已设置 + 请输入此账户的密码 + 无法执行此操作
diff --git a/src/quicksy/res/values-sv/strings.xml b/src/quicksy/res/values-sv/strings.xml new file mode 100644 index 000000000..5c534245e --- /dev/null +++ b/src/quicksy/res/values-sv/strings.xml @@ -0,0 +1,9 @@ + + + Quicksy har kraschat + Quicksy behöver tillgång till kameran + Berätta för alla dina kontakter när du använder Quicksy + Quicksy behöver tillgång till mikrofonen + Quicksy är inte tillgängligt i ditt land. + Okänt säkerhetsfel. + diff --git a/src/quicksy/res/values-zh-rCN/strings.xml b/src/quicksy/res/values-zh-rCN/strings.xml index b2830539d..f1a4091ef 100644 --- a/src/quicksy/res/values-zh-rCN/strings.xml +++ b/src/quicksy/res/values-zh-rCN/strings.xml @@ -19,4 +19,8 @@ Quicksy需要麦克风权限 此通知类别用于显示表明Quicksy正在运行的永久通知。 Quicksy个人资料图片 - + Quicksy在您的国家无服务。 + 无法确认服务器身份 + 未知安全错误 + 服务器已超时 + From 11736ce48ce4dcc9cd83d2769bf8a39c0d244281 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 26 Sep 2019 23:47:55 +0200 Subject: [PATCH 31/36] make list selection manager work with app compat --- .../ui/widget/ListSelectionManager.java | 354 +++++++++--------- 1 file changed, 177 insertions(+), 177 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/widget/ListSelectionManager.java b/src/main/java/eu/siacs/conversations/ui/widget/ListSelectionManager.java index 1c32fd75a..24f1cac8d 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/ListSelectionManager.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/ListSelectionManager.java @@ -1,8 +1,5 @@ package eu.siacs.conversations.ui.widget; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -13,199 +10,202 @@ import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + public class ListSelectionManager { - private static final int MESSAGE_SEND_RESET = 1; - private static final int MESSAGE_RESET = 2; - private static final int MESSAGE_START_SELECTION = 3; + private static final int MESSAGE_SEND_RESET = 1; + private static final int MESSAGE_RESET = 2; + private static final int MESSAGE_START_SELECTION = 3; + private static final Field FIELD_EDITOR; + private static final Method METHOD_START_SELECTION; + private static final boolean SUPPORTED; + private static final Handler HANDLER = new Handler(Looper.getMainLooper(), new Handler.Callback() { - private static final Handler HANDLER = new Handler(Looper.getMainLooper(), new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_SEND_RESET: { + // Skip one more message queue loop + HANDLER.obtainMessage(MESSAGE_RESET, msg.obj).sendToTarget(); + return true; + } + case MESSAGE_RESET: { + final ListSelectionManager listSelectionManager = (ListSelectionManager) msg.obj; + listSelectionManager.futureSelectionIdentifier = null; + return true; + } + case MESSAGE_START_SELECTION: { + final StartSelectionHolder holder = (StartSelectionHolder) msg.obj; + holder.listSelectionManager.futureSelectionIdentifier = null; + startSelection(holder.textView, holder.start, holder.end); + return true; + } + } + return false; + } + }); - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_SEND_RESET: { - // Skip one more message queue loop - HANDLER.obtainMessage(MESSAGE_RESET, msg.obj).sendToTarget(); - return true; - } - case MESSAGE_RESET: { - final ListSelectionManager listSelectionManager = (ListSelectionManager) msg.obj; - listSelectionManager.futureSelectionIdentifier = null; - return true; - } - case MESSAGE_START_SELECTION: { - final StartSelectionHolder holder = (StartSelectionHolder) msg.obj; - holder.listSelectionManager.futureSelectionIdentifier = null; - startSelection(holder.textView, holder.start, holder.end); - return true; - } - } - return false; - } - }); + static { + Field editor; + try { + editor = TextView.class.getDeclaredField("mEditor"); + editor.setAccessible(true); + } catch (Exception e) { + editor = null; + } + FIELD_EDITOR = editor; + Method startSelection = null; + if (editor != null) { + String[] startSelectionNames = {"startSelectionActionMode", "startSelectionActionModeWithSelection"}; + for (String startSelectionName : startSelectionNames) { + try { + startSelection = editor.getType().getDeclaredMethod(startSelectionName); + startSelection.setAccessible(true); + break; + } catch (Exception e) { + startSelection = null; + } + } + } + METHOD_START_SELECTION = startSelection; + SUPPORTED = FIELD_EDITOR != null && METHOD_START_SELECTION != null; + } - private static class StartSelectionHolder { + private ActionMode selectionActionMode; + private Object selectionIdentifier; + private TextView selectionTextView; + private Object futureSelectionIdentifier; + private int futureSelectionStart; + private int futureSelectionEnd; - public final ListSelectionManager listSelectionManager; - public final TextView textView; - public final int start; - public final int end; + public static boolean isSupported() { + return SUPPORTED; + } - public StartSelectionHolder(ListSelectionManager listSelectionManager, TextView textView, - int start, int end) { - this.listSelectionManager = listSelectionManager; - this.textView = textView; - this.start = start; - this.end = end; - } - } + private static void startSelection(TextView textView, int start, int end) { + final CharSequence text = textView.getText(); + if (SUPPORTED && start >= 0 && end > start && textView.isTextSelectable() && text instanceof Spannable) { + final Spannable spannable = (Spannable) text; + start = Math.min(start, spannable.length()); + end = Math.min(end, spannable.length()); + Selection.setSelection(spannable, start, end); + try { + final Object editor = FIELD_EDITOR != null ? FIELD_EDITOR.get(textView) : textView; + METHOD_START_SELECTION.invoke(editor); + } catch (Exception e) { + } + } + } - private ActionMode selectionActionMode; - private Object selectionIdentifier; - private TextView selectionTextView; + public void onCreate(TextView textView, ActionMode.Callback additionalCallback) { + final CustomCallback callback = new CustomCallback(textView, additionalCallback); + textView.setCustomSelectionActionModeCallback(callback); + } - private Object futureSelectionIdentifier; - private int futureSelectionStart; - private int futureSelectionEnd; + public void onUpdate(TextView textView, Object identifier) { + if (SUPPORTED) { + final ActionMode.Callback callback = textView.getCustomSelectionActionModeCallback(); + if (callback instanceof CustomCallback) { + final CustomCallback customCallback = (CustomCallback) textView.getCustomSelectionActionModeCallback(); + customCallback.identifier = identifier; + if (futureSelectionIdentifier == identifier) { + HANDLER.obtainMessage(MESSAGE_START_SELECTION, new StartSelectionHolder(this, + textView, futureSelectionStart, futureSelectionEnd)).sendToTarget(); + } + } + } + } - public void onCreate(TextView textView, ActionMode.Callback additionalCallback) { - final CustomCallback callback = new CustomCallback(textView, additionalCallback); - textView.setCustomSelectionActionModeCallback(callback); - } + public void onBeforeNotifyDataSetChanged() { + if (SUPPORTED) { + HANDLER.removeMessages(MESSAGE_SEND_RESET); + HANDLER.removeMessages(MESSAGE_RESET); + HANDLER.removeMessages(MESSAGE_START_SELECTION); + if (selectionActionMode != null) { + final CharSequence text = selectionTextView.getText(); + futureSelectionIdentifier = selectionIdentifier; + futureSelectionStart = Selection.getSelectionStart(text); + futureSelectionEnd = Selection.getSelectionEnd(text); + selectionActionMode.finish(); + selectionActionMode = null; + selectionIdentifier = null; + selectionTextView = null; + } + } + } - public void onUpdate(TextView textView, Object identifier) { - if (SUPPORTED) { - CustomCallback callback = (CustomCallback) textView.getCustomSelectionActionModeCallback(); - callback.identifier = identifier; - if (futureSelectionIdentifier == identifier) { - HANDLER.obtainMessage(MESSAGE_START_SELECTION, new StartSelectionHolder(this, - textView, futureSelectionStart, futureSelectionEnd)).sendToTarget(); - } - } - } + public void onAfterNotifyDataSetChanged() { + if (SUPPORTED && futureSelectionIdentifier != null) { + HANDLER.obtainMessage(MESSAGE_SEND_RESET, this).sendToTarget(); + } + } - public void onBeforeNotifyDataSetChanged() { - if (SUPPORTED) { - HANDLER.removeMessages(MESSAGE_SEND_RESET); - HANDLER.removeMessages(MESSAGE_RESET); - HANDLER.removeMessages(MESSAGE_START_SELECTION); - if (selectionActionMode != null) { - final CharSequence text = selectionTextView.getText(); - futureSelectionIdentifier = selectionIdentifier; - futureSelectionStart = Selection.getSelectionStart(text); - futureSelectionEnd = Selection.getSelectionEnd(text); - selectionActionMode.finish(); - selectionActionMode = null; - selectionIdentifier = null; - selectionTextView = null; - } - } - } + private static class StartSelectionHolder { - public void onAfterNotifyDataSetChanged() { - if (SUPPORTED && futureSelectionIdentifier != null) { - HANDLER.obtainMessage(MESSAGE_SEND_RESET, this).sendToTarget(); - } - } + final ListSelectionManager listSelectionManager; + final TextView textView; + public final int start; + public final int end; - private class CustomCallback implements ActionMode.Callback { + StartSelectionHolder(ListSelectionManager listSelectionManager, TextView textView, + int start, int end) { + this.listSelectionManager = listSelectionManager; + this.textView = textView; + this.start = start; + this.end = end; + } + } - private final TextView textView; - private final ActionMode.Callback additionalCallback; - public Object identifier; + private class CustomCallback implements ActionMode.Callback { - public CustomCallback(TextView textView, ActionMode.Callback additionalCallback) { - this.textView = textView; - this.additionalCallback = additionalCallback; - } + private final TextView textView; + private final ActionMode.Callback additionalCallback; + Object identifier; - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - selectionActionMode = mode; - selectionIdentifier = identifier; - selectionTextView = textView; - if (additionalCallback != null) { - additionalCallback.onCreateActionMode(mode, menu); - } - return true; - } + CustomCallback(TextView textView, ActionMode.Callback additionalCallback) { + this.textView = textView; + this.additionalCallback = additionalCallback; + } - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - if (additionalCallback != null) { - additionalCallback.onPrepareActionMode(mode, menu); - } - return true; - } + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + selectionActionMode = mode; + selectionIdentifier = identifier; + selectionTextView = textView; + if (additionalCallback != null) { + additionalCallback.onCreateActionMode(mode, menu); + } + return true; + } - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - if (additionalCallback != null && additionalCallback.onActionItemClicked(mode, item)) { - return true; - } - return false; - } + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + if (additionalCallback != null) { + additionalCallback.onPrepareActionMode(mode, menu); + } + return true; + } - @Override - public void onDestroyActionMode(ActionMode mode) { - if (additionalCallback != null) { - additionalCallback.onDestroyActionMode(mode); - } - if (selectionActionMode == mode) { - selectionActionMode = null; - selectionIdentifier = null; - selectionTextView = null; - } - } - } + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + if (additionalCallback != null && additionalCallback.onActionItemClicked(mode, item)) { + return true; + } + return false; + } - private static final Field FIELD_EDITOR; - private static final Method METHOD_START_SELECTION; - private static final boolean SUPPORTED; - - static { - Field editor; - try { - editor = TextView.class.getDeclaredField("mEditor"); - editor.setAccessible(true); - } catch (Exception e) { - editor = null; - } - FIELD_EDITOR = editor; - Method startSelection = null; - if (editor != null) { - String[] startSelectionNames = {"startSelectionActionMode", "startSelectionActionModeWithSelection"}; - for (String startSelectionName : startSelectionNames) { - try { - startSelection = editor.getType().getDeclaredMethod(startSelectionName); - startSelection.setAccessible(true); - break; - } catch (Exception e) { - startSelection = null; - } - } - } - METHOD_START_SELECTION = startSelection; - SUPPORTED = FIELD_EDITOR != null && METHOD_START_SELECTION != null; - } - - public static boolean isSupported() { - return SUPPORTED; - } - - public static void startSelection(TextView textView, int start, int end) { - final CharSequence text = textView.getText(); - if (SUPPORTED && start >= 0 && end > start && textView.isTextSelectable() && text instanceof Spannable) { - final Spannable spannable = (Spannable) text; - start = Math.min(start, spannable.length()); - end = Math.min(end, spannable.length()); - Selection.setSelection(spannable, start, end); - try { - final Object editor = FIELD_EDITOR != null ? FIELD_EDITOR.get(textView) : textView; - METHOD_START_SELECTION.invoke(editor); - } catch (Exception e) { - } - } - } + @Override + public void onDestroyActionMode(ActionMode mode) { + if (additionalCallback != null) { + additionalCallback.onDestroyActionMode(mode); + } + if (selectionActionMode == mode) { + selectionActionMode = null; + selectionIdentifier = null; + selectionTextView = null; + } + } + } } \ No newline at end of file From a3227caa375002ef07a62710b70ba63c057c8a22 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 27 Sep 2019 16:30:33 +0200 Subject: [PATCH 32/36] pulled translations from transifex --- src/main/res/values-de/strings.xml | 1 + src/main/res/values-gl/strings.xml | 1 + src/main/res/values-pl/strings.xml | 3 ++- src/main/res/values-ro-rRO/strings.xml | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index c99c015b9..206394c78 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -874,4 +874,5 @@ Dieses Konto wurde bereits eingerichtet Bitte gib das Passwort für dieses Konto ein Diese Aktion kann nicht ausgeführt werden + Öffentlichen Channel beitreten... diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index 34e13fb4f..1ad4780d1 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -874,4 +874,5 @@ Esta conta xa foi configurada Introduza o contrasinal de esta conta Non se puido completar a acción + Unirse a canle pública... diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 68ac6fba1..2980b86ea 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -888,8 +888,9 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż Kopia zapasowa Conversations Zdarzenie Otwórz kopię zapasową - Plik który otworzyłeś nie jest plikiem kopii zapasowej Conversations + Plik, który otworzyłeś, nie jest plikiem kopii zapasowej Conversations To konto zostało już ustawione Proszę podać hasło dla tego konta Nie można wykonać tej akcji + Dołącz do publicznego kanału... diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index 23135b756..8aecce6b8 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -884,4 +884,5 @@ sau chiar pierderea mesajelor.\nÎn continuare veți fi rugați să dezactivați Acest cont a fost deja configurat Va rugăm să introduceți parola pentru acest cont Nu se poate realiza această acțiune + Alătură-te unui canal public... From 0fc41d8c826714d9dc2ccd5ce85f038527839d16 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 27 Sep 2019 20:29:22 +0200 Subject: [PATCH 33/36] version bump to 2.5.10 + changelog --- CHANGELOG.md | 3 +++ build.gradle | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bea65ac7a..e713017c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +### Version 2.5.10 +* Fixed crash on Android 8.0 + 8.1 + ### Version 2.5.9 * fixed minor security issues * Share XMPP uri from channel search by long pressing a result diff --git a/build.gradle b/build.gradle index 7abcacc9d..5018cc2d1 100644 --- a/build.gradle +++ b/build.gradle @@ -84,8 +84,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 28 - versionCode 339 - versionName "2.5.9" + versionCode 340 + versionName "2.5.10" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId From a2d521568d77ba23ed495086839454cab26837b9 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 28 Sep 2019 10:35:36 +0200 Subject: [PATCH 34/36] demote okhttp to 3.12 to work with old android --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5018cc2d1..a03ee01fa 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ dependencies { implementation "com.leinardi.android:speed-dial:2.0.1" implementation 'com.squareup.retrofit2:retrofit:2.6.1' implementation 'com.squareup.retrofit2:converter-gson:2.6.1' - implementation 'com.squareup.okhttp3:okhttp:3.14.3' + implementation 'com.squareup.okhttp3:okhttp:3.12.5' implementation 'com.google.guava:guava:27.1-android' quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.10.16' } From 705f31518c9f617448b0f672a3d265bf83099483 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 28 Sep 2019 10:37:21 +0200 Subject: [PATCH 35/36] pulled translations from transifex --- src/main/res/values-zh-rCN/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index a66d2fd0e..af4c7c48d 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -503,6 +503,7 @@ Conversations 需要摄像头权限 同步联系人 将服务器端联系人与本地联系人匹配可以显示联系人的全名与头像。\n\n此应用只在本地读取并匹配联系人。\n\n现在应用将请求联系人权限。 +
我们并不储存这些号码。\n\n更多信息请阅读隐私政策。接下来将请求通讯录权限。]]>
为所有信息显示通知 只在被提到时通知 禁用通知 @@ -851,6 +852,7 @@ 发现群聊 搜索群聊 可能侵犯隐私! + search.jabbercat.org的第三方服务。在探索群聊时,您的IP地址和搜索内容将传送到他们的服务器上。有关更多信息,请参阅他们的隐私政策。]]> 我已有账户 添加已有账户 注册新账户 @@ -865,4 +867,5 @@ 账户已设置 请输入此账户的密码 无法执行此操作 + 加入公开群聊 From 175d9f539e007dbe072a234d4712cf22052e4cb8 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 28 Sep 2019 11:04:08 +0200 Subject: [PATCH 36/36] version bump to 2.5.11 + changelog --- CHANGELOG.md | 5 ++++- build.gradle | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e713017c2..c74a98a73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ # Changelog +### Version 2.5.11 +* Fixed crash on Android <5.0 + ### Version 2.5.10 -* Fixed crash on Android 8.0 + 8.1 +* Fixed crash on Xiaomi devices running Android 8.0 + 8.1 ### Version 2.5.9 * fixed minor security issues diff --git a/build.gradle b/build.gradle index a03ee01fa..34e9cd785 100644 --- a/build.gradle +++ b/build.gradle @@ -84,8 +84,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 28 - versionCode 340 - versionName "2.5.10" + versionCode 341 + versionName "2.5.11" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId