From 0e54cde4bfcfeffe8a6eaaef576b6aace8474c46 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 23 Jul 2021 08:04:36 +0200 Subject: [PATCH 001/145] add omemo media sharing to doap file --- conversations.doap | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/conversations.doap b/conversations.doap index 462d750da608bad3e0eb3149af22d22276430131..93a7df126d7c0db948baeb2ac397cda3815b4750 100644 --- a/conversations.doap +++ b/conversations.doap @@ -453,12 +453,19 @@ 0.2.0 + + + + complete + 0.1.0 + + - 2.5.8 - 2019-09-12 - + 2.9.13 + 2021-05-03 + From 1e1dad780b92254d691cf12a96b283e8e1d87f79 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 28 Jul 2021 16:57:57 +0200 Subject: [PATCH 002/145] add .opus file extension to mime table --- src/main/java/eu/siacs/conversations/utils/MimeUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java index 53e2e1917f5f3eb9565eafb5dfdd19904c51ac4e..b320c0c1178dc597d7533ff20dbd79d6b9f0319a 100644 --- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java @@ -247,6 +247,7 @@ public final class MimeUtils { add("audio/mpeg", "m4a"); add("audio/mpegurl", "m3u"); add("audio/ogg", "oga"); + add("audio/opus", "opus"); add("audio/prs.sid", "sid"); add("audio/x-aiff", "aif"); add("audio/x-aiff", "aiff"); From e528b9f5df59f7b49ae18c73396bd56525493e28 Mon Sep 17 00:00:00 2001 From: Licaon_Kter Date: Wed, 11 Aug 2021 15:42:30 +0000 Subject: [PATCH 003/145] Always show Quote as last action --- src/main/res/menu/message_context.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml index f32505203e92f85efd1c705434f4454d0483955d..f6592c5aff533b8c6c802d2a5fc76e2a439df44f 100644 --- a/src/main/res/menu/message_context.xml +++ b/src/main/res/menu/message_context.xml @@ -20,10 +20,6 @@ android:id="@+id/copy_link" android:title="@string/copy_link" android:visible="false" /> - + - \ No newline at end of file + From 309082a9b39e4dae7570d5f486c9429da0b6d9e5 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Wed, 11 Aug 2021 20:04:19 +0200 Subject: [PATCH 004/145] Fixing xmpp:uri bug in channel details. #4139 --- .../eu/siacs/conversations/ui/ConferenceDetailsActivity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 3d29bb89a8f5f32b91d5d2f3b30512b60346bcee..574035e2c367d9bd6895b3142746282435a5ea11 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -7,6 +7,7 @@ import android.os.Bundle; import android.text.Editable; import android.text.SpannableStringBuilder; import android.text.TextWatcher; +import android.text.method.LinkMovementMethod; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -481,6 +482,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers this.binding.mucSubject.setTextAppearance(this, subject.length() > (hasTitle ? 128 : 196) ? R.style.TextAppearance_Conversations_Body1_Linkified : R.style.TextAppearance_Conversations_Subhead); this.binding.mucSubject.setAutoLinkMask(0); this.binding.mucSubject.setVisibility(View.VISIBLE); + this.binding.mucSubject.setMovementMethod(LinkMovementMethod.getInstance()); } else { this.binding.mucSubject.setVisibility(View.GONE); } From 65a72827bc2187202a371a4ef9ce665e26910c6f Mon Sep 17 00:00:00 2001 From: Millesimus Date: Fri, 20 Aug 2021 21:51:51 +0200 Subject: [PATCH 005/145] New helper to help with quotes. --- .../conversations/ui/util/QuoteHelper.java | 51 +++++++++++++++++++ .../siacs/conversations/utils/UIHelper.java | 28 ++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java diff --git a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..cfd2a2e638598ee02419911cfd840d5e1e54c5ff --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java @@ -0,0 +1,51 @@ +package eu.siacs.conversations.ui.util; + +import eu.siacs.conversations.utils.UIHelper; + +public class QuoteHelper { + + public static boolean isPositionQuoteCharacter(CharSequence body, int pos){ + return body.charAt(pos) == '>'; + } + + public static boolean isPositionFollowedByQuoteChar(CharSequence body, int pos) { + return body.length() > pos + 1 && isPositionQuoteCharacter(body, pos +1 ); + } + + // 'Prequote' means anything we require or can accept in front of a QuoteChar + public static boolean isPositionPrecededByPrequote(CharSequence body, int pos){ + return UIHelper.isPositionPrecededByLineStart(body, pos); + } + + public static boolean isPositionQuoteStart (CharSequence body, int pos){ + return isPositionQuoteCharacter(body, pos) + && isPositionPrecededByPrequote(body, pos) + && (UIHelper.isPositionFollowedByWhitespace(body, pos) + || isPositionFollowedByQuoteChar(body, pos)); + } + + public static boolean bodyContainsQuoteStart (CharSequence body){ + for (int i = 0; i < body.length(); i++){ + if (isPositionQuoteStart(body, i)){ + return true; + } + } + return false; + } + /*public static int getQuoteColors(XmppActivity activity, boolean darkBackground, int quoteDepth){ + int[] colorsLight = R.style.ConversationsTheme_Dark; + int[] colorsDark = Config.QUOTE_COLOR_ARRAY_DARK; + + Collections.rotate(Collections.singletonList(colorsLight), quoteDepth); + Collections.rotate(Collections.singletonList(colorsDark), quoteDepth); + + Arrays.stream(colorsLight).toArray(); + + int quoteColors = darkBackground ? ContextCompat.getColor(activity, colorsLight[quoteDepth-1]) + : ContextCompat.getColor(activity, colorsDark[quoteDepth-1]); + + Collections.rotate + + return quoteColors; + };*/ +} diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 6dcf517a0eb2de935097fc9368c9ebb325b2b8bb..26e6af2fa26ac4c4d608edd34617b837c00226ed 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -372,6 +372,34 @@ public class UIHelper { return input.length() > 256 ? StylingHelper.subSequence(input, 0, 256) : input; } + public static boolean isPositionFollowedByWhitespace(CharSequence body, int pos){ + return Character.isWhitespace(body.charAt(pos + 1)); + } + + public static boolean isPositionPrecededByWhitespace(CharSequence body, int pos){ + return Character.isWhitespace(body.charAt(pos -1 )); + } + + public static boolean isPositionPrecededByBodyStart(CharSequence body, int pos){ + // true if not a single linebreak before current position + for (int i = pos - 1; i >= 0; i--){ + if (body.charAt(i) != ' '){ + return false; + } + } + return true; + } + + public static boolean isPositionPrecededByLineStart(CharSequence body, int pos){ + if (isPositionPrecededByBodyStart(body, pos)){ + return true; + } + if (body.charAt(pos - 1) == '\n'){ + return true; + } + return false; + } + public static boolean isPositionFollowedByQuoteableCharacter(CharSequence body, int pos) { return !isPositionFollowedByNumber(body, pos) && !isPositionFollowedByEmoticon(body, pos) From 74d60d01312161c4e875b12decfbf3bd00d51113 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Fri, 20 Aug 2021 21:53:01 +0200 Subject: [PATCH 006/145] Implement nested quotes through iteration. --- .../java/eu/siacs/conversations/Config.java | 3 + .../ui/adapter/MessageAdapter.java | 84 ++++++++++--------- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 5286abf49ee59383a3072fcb9c6ac42ffba02567..3159d931fb11693f6aaa5184ee3f53c1ddaa1199 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -201,4 +201,7 @@ public final class Config { public final static float LOCATION_FIX_SPACE_DELTA = 10; // m public final static int LOCATION_FIX_SIGNIFICANT_TIME_DELTA = 1000 * 60 * 2; // ms } + + // How deep nested quotes should become. '2' means one quote nested in another. + public static final int QUOTE_MAX_DEPTH = 3; } 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 587e834b333dd2a42ea2901ac6a8442e31cee4db..5c91043430750327da9faf498c4b67b7c5596def 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -59,6 +59,7 @@ import eu.siacs.conversations.ui.text.DividerSpan; import eu.siacs.conversations.ui.text.QuoteSpan; import eu.siacs.conversations.ui.util.AvatarWorkerTask; import eu.siacs.conversations.ui.util.MyLinkify; +import eu.siacs.conversations.ui.util.QuoteHelper; import eu.siacs.conversations.ui.util.ViewUtil; import eu.siacs.conversations.ui.widget.ClickableMovementMethod; import eu.siacs.conversations.utils.CryptoHelper; @@ -359,48 +360,55 @@ public class MessageAdapter extends ArrayAdapter { */ private boolean handleTextQuotes(SpannableStringBuilder body, boolean darkBackground) { boolean startsWithQuote = false; - char previous = '\n'; - int lineStart = -1; - int lineTextStart = -1; - int quoteStart = -1; - for (int i = 0; i <= body.length(); i++) { - char current = body.length() > i ? body.charAt(i) : '\n'; - if (lineStart == -1) { - if (previous == '\n') { - if ((current == '>' && UIHelper.isPositionFollowedByQuoteableCharacter(body, i)) - || current == '\u00bb' && !UIHelper.isPositionFollowedByQuote(body, i)) { - // Line start with quote - lineStart = i; - if (quoteStart == -1) quoteStart = i; - if (i == 0) startsWithQuote = true; - } else if (quoteStart >= 0) { - // Line start without quote, apply spans there - applyQuoteSpan(body, quoteStart, i - 1, darkBackground); - quoteStart = -1; + int quoteDepth = 1; + while (QuoteHelper.bodyContainsQuoteStart(body) && quoteDepth <= Config.QUOTE_MAX_DEPTH) { + char previous = '\n'; + int lineStart = -1; + int lineTextStart = -1; + int quoteStart = -1; + for (int i = 0; i <= body.length(); i++) { + char current = body.length() > i ? body.charAt(i) : '\n'; + if (lineStart == -1) { + if (previous == '\n') { + if ( + (QuoteHelper.isPositionQuoteStart(body, i) + || (current == '\u00bb' && !UIHelper.isPositionFollowedByQuote(body, i) + ))) { + // Line start with quote + lineStart = i; + if (quoteStart == -1) quoteStart = i; + if (i == 0) startsWithQuote = true; + } else if (quoteStart >= 0) { + // Line start without quote, apply spans there + applyQuoteSpan(body, quoteStart, i - 1, darkBackground); + quoteStart = -1; + quoteDepth++; + } } - } - } else { - // Remove extra spaces between > and first character in the line - // > character will be removed too - if (current != ' ' && lineTextStart == -1) { - lineTextStart = i; - } - if (current == '\n') { - body.delete(lineStart, lineTextStart); - i -= lineTextStart - lineStart; - if (i == lineStart) { - // Avoid empty lines because span over empty line can be hidden - body.insert(i++, " "); + } else { + // Remove extra spaces between > and first character in the line + // > character will be removed too + if (current != ' ' && lineTextStart == -1) { + lineTextStart = i; + } + if (current == '\n') { + body.delete(lineStart, lineTextStart); + i -= lineTextStart - lineStart; + if (i == lineStart) { + // Avoid empty lines because span over empty line can be hidden + body.insert(i++, " "); + } + lineStart = -1; + lineTextStart = -1; } - lineStart = -1; - lineTextStart = -1; } + previous = current; + } + if (quoteStart >= 0) { + // Apply spans to finishing open quote + applyQuoteSpan(body, quoteStart, body.length(), darkBackground); + quoteDepth++; } - previous = current; - } - if (quoteStart >= 0) { - // Apply spans to finishing open quote - applyQuoteSpan(body, quoteStart, body.length(), darkBackground); } return startsWithQuote; } From e850900b40e5bafc6ece4303bb6f712b87132758 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Fri, 20 Aug 2021 22:40:06 +0200 Subject: [PATCH 007/145] Quoting quotes, limited by nesting depth. --- .../java/eu/siacs/conversations/Config.java | 4 ++- .../conversations/ui/util/QuoteHelper.java | 30 +++++++++---------- .../conversations/ui/widget/EditMessage.java | 2 +- .../conversations/utils/MessageUtils.java | 3 +- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 3159d931fb11693f6aaa5184ee3f53c1ddaa1199..a8494d68cd60842ed3e63b44fb2b2d84d2f55456 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -202,6 +202,8 @@ public final class Config { public final static int LOCATION_FIX_SIGNIFICANT_TIME_DELTA = 1000 * 60 * 2; // ms } - // How deep nested quotes should become. '2' means one quote nested in another. + // How deep nested quotes should be displayed. '2' means one quote nested in another. public static final int QUOTE_MAX_DEPTH = 3; + // How deep nested quotes should be created on quoting a message. + public static final int QUOTING_MAX_DEPTH = QUOTE_MAX_DEPTH - 1; } diff --git a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java index cfd2a2e638598ee02419911cfd840d5e1e54c5ff..cc539f1ef92419487bbfb4af101f0b8a53e89aa0 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.ui.util; +import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.UIHelper; public class QuoteHelper { @@ -32,20 +33,19 @@ public class QuoteHelper { } return false; } - /*public static int getQuoteColors(XmppActivity activity, boolean darkBackground, int quoteDepth){ - int[] colorsLight = R.style.ConversationsTheme_Dark; - int[] colorsDark = Config.QUOTE_COLOR_ARRAY_DARK; - Collections.rotate(Collections.singletonList(colorsLight), quoteDepth); - Collections.rotate(Collections.singletonList(colorsDark), quoteDepth); - - Arrays.stream(colorsLight).toArray(); - - int quoteColors = darkBackground ? ContextCompat.getColor(activity, colorsLight[quoteDepth-1]) - : ContextCompat.getColor(activity, colorsDark[quoteDepth-1]); - - Collections.rotate - - return quoteColors; - };*/ + public static boolean isNestedTooDeeply (CharSequence line){ + if (isPositionQuoteCharacter(line, 0)) { + int nestingDepth = 1; + for (int i = 1; i < line.length(); i++) { + if (isPositionQuoteCharacter(line, i)) { + nestingDepth++; + } + if (nestingDepth > (Config.QUOTING_MAX_DEPTH)) { + return true; + } + } + } + return false; + } } diff --git a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java index eadd562a709c0fc532f4140de7e92313c5d8ea0f..a4c6e67fd10a75576d65bef6da6b45f902b5f99f 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java @@ -142,7 +142,7 @@ public class EditMessage extends EmojiWrapperEditText { } public void insertAsQuote(String text) { - text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)", "$1> ").replaceAll("\n$", ""); + text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)>", "$1>>").replaceAll("(^|\n)([^>])", "$1> $2").replaceAll("\n$", ""); Editable editable = getEditableText(); int position = getSelectionEnd(); if (position == -1) position = editable.length(); diff --git a/src/main/java/eu/siacs/conversations/utils/MessageUtils.java b/src/main/java/eu/siacs/conversations/utils/MessageUtils.java index 3cb6338dbadfaa0ed0832737d7dae053adaa7a41..c9c029c65b0ee833733e83ff32ee5f2774397ecd 100644 --- a/src/main/java/eu/siacs/conversations/utils/MessageUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MessageUtils.java @@ -39,6 +39,7 @@ import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.http.AesGcmURL; import eu.siacs.conversations.http.URL; +import eu.siacs.conversations.ui.util.QuoteHelper; public class MessageUtils { @@ -69,7 +70,7 @@ public class MessageUtils { continue; } final char c = line.charAt(0); - if (c == '>' && UIHelper.isPositionFollowedByQuoteableCharacter(line, 0) + if (QuoteHelper.isNestedTooDeeply(line) || (c == '\u00bb' && !UIHelper.isPositionFollowedByQuote(line, 0))) { continue; } From c81c8a62b3df6b8eb604ecd0d4792c37360cc1f9 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sat, 21 Aug 2021 17:21:44 +0200 Subject: [PATCH 008/145] Small refactoring for a more intuitive config. --- src/main/java/eu/siacs/conversations/Config.java | 2 +- src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index a8494d68cd60842ed3e63b44fb2b2d84d2f55456..7cbe703464df2867b6a5a3eef2795043a9359117 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -205,5 +205,5 @@ public final class Config { // How deep nested quotes should be displayed. '2' means one quote nested in another. public static final int QUOTE_MAX_DEPTH = 3; // How deep nested quotes should be created on quoting a message. - public static final int QUOTING_MAX_DEPTH = QUOTE_MAX_DEPTH - 1; + public static final int QUOTING_MAX_DEPTH = QUOTE_MAX_DEPTH; } diff --git a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java index cc539f1ef92419487bbfb4af101f0b8a53e89aa0..2502f3682bc4b29f37f5e071338f5083d49d8335 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java @@ -41,7 +41,7 @@ public class QuoteHelper { if (isPositionQuoteCharacter(line, i)) { nestingDepth++; } - if (nestingDepth > (Config.QUOTING_MAX_DEPTH)) { + if (nestingDepth > (Config.QUOTING_MAX_DEPTH - 1)) { return true; } } From 3921f3a940d6e2b50f293013f42115711cec9955 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sat, 21 Aug 2021 17:25:06 +0200 Subject: [PATCH 009/145] QUOTING_MAX_DEPTH=1 for transitory compatibility with older versions. QUOTE_MAX_DEPTH=7 for performance testing and hiding of a rerendering bug occuring when two adjacent messages are merged. --- src/main/java/eu/siacs/conversations/Config.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 7cbe703464df2867b6a5a3eef2795043a9359117..c89ce81bb1a0906dd7066e516dda8e8938ff7894 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -203,7 +203,7 @@ public final class Config { } // How deep nested quotes should be displayed. '2' means one quote nested in another. - public static final int QUOTE_MAX_DEPTH = 3; + public static final int QUOTE_MAX_DEPTH = 7; // How deep nested quotes should be created on quoting a message. - public static final int QUOTING_MAX_DEPTH = QUOTE_MAX_DEPTH; + public static final int QUOTING_MAX_DEPTH = 1; } From 748443cd4efc604df6c4b916d341e6ef94b44afb Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sat, 21 Aug 2021 17:42:01 +0200 Subject: [PATCH 010/145] Fixing message preview. --- src/main/java/eu/siacs/conversations/utils/UIHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 26e6af2fa26ac4c4d608edd34617b837c00226ed..c293bcd127154994a676de862d7c48de05d89fc0 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -32,6 +32,7 @@ import eu.siacs.conversations.entities.Presence; import eu.siacs.conversations.entities.RtpSessionStatus; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.services.ExportBackupService; +import eu.siacs.conversations.ui.util.QuoteHelper; import eu.siacs.conversations.xmpp.Jid; public class UIHelper { @@ -328,7 +329,7 @@ public class UIHelper { continue; } char first = l.charAt(0); - if ((first != '>' || !isPositionFollowedByQuoteableCharacter(l, 0)) && first != '\u00bb') { + if ((!QuoteHelper.isPositionQuoteStart(l, 0)) && first != '\u00bb') { CharSequence line = CharSequenceUtils.trim(l); if (line.length() == 0) { continue; From a0bca08997cba7952cba49328ba09d71a6e8a1ba Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sun, 22 Aug 2021 08:53:47 +0200 Subject: [PATCH 011/145] Rewrite QuoteHelper to integrate French quotes logics. Also reallow QuoteChars not followed by whitespace as indicated in XEP-0393. --- .../conversations/ui/util/QuoteHelper.java | 61 +++++++++++++++++-- .../siacs/conversations/utils/UIHelper.java | 35 +---------- 2 files changed, 59 insertions(+), 37 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java index 2502f3682bc4b29f37f5e071338f5083d49d8335..4e4617cab78c9d7ee99c175ca2833e5602dea4e4 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java @@ -5,8 +5,32 @@ import eu.siacs.conversations.utils.UIHelper; public class QuoteHelper { + + public static final char QUOTE_CHAR = '>'; + public static final char QUOTE_END_CHAR = '<'; // used for one check, not for actual quoting + public static final char QUOTE_ALT_CHAR = '»'; + public static final char QUOTE_ALT_END_CHAR = '«'; + public static boolean isPositionQuoteCharacter(CharSequence body, int pos){ - return body.charAt(pos) == '>'; + // second part of logical check actually goes against the logic indicated in the method name, since it also checks for context + // but it's very useful + return body.charAt(pos) == QUOTE_CHAR || isPositionAltQuoteStart(body, pos); + } + + public static boolean isPositionQuoteEndCharacter(CharSequence body, int pos){ + return body.charAt(pos) == QUOTE_END_CHAR; + } + + public static boolean isPositionAltQuoteCharacter (CharSequence body, int pos){ + return body.charAt(pos) == QUOTE_ALT_CHAR; + } + + public static boolean isPositionAltQuoteEndCharacter(CharSequence body, int pos){ + return body.charAt(pos) == QUOTE_ALT_END_CHAR; + } + + public static boolean isPositionAltQuoteStart(CharSequence body, int pos){ + return isPositionAltQuoteCharacter(body, pos) && !isPositionFollowedByAltQuoteEnd(body, pos); } public static boolean isPositionFollowedByQuoteChar(CharSequence body, int pos) { @@ -19,10 +43,10 @@ public class QuoteHelper { } public static boolean isPositionQuoteStart (CharSequence body, int pos){ - return isPositionQuoteCharacter(body, pos) + return (isPositionQuoteCharacter(body, pos) && isPositionPrecededByPrequote(body, pos) - && (UIHelper.isPositionFollowedByWhitespace(body, pos) - || isPositionFollowedByQuoteChar(body, pos)); + && (UIHelper.isPositionFollowedByQuoteableCharacter(body, pos) + || isPositionFollowedByQuoteChar(body, pos))); } public static boolean bodyContainsQuoteStart (CharSequence body){ @@ -34,6 +58,24 @@ public class QuoteHelper { return false; } + public static boolean isPositionFollowedByAltQuoteEnd(CharSequence body, int pos) { + if (body.length() <= pos + 1 || Character.isWhitespace(body.charAt(pos + 1))) { + return false; + } + boolean previousWasWhitespace = false; + for (int i = pos + 1; i < body.length(); i++) { + char c = body.charAt(i); + if (c == '\n' || isPositionAltQuoteCharacter(body, i)) { + return false; + } else if (isPositionAltQuoteEndCharacter(body, i) && !previousWasWhitespace) { + return true; + } else { + previousWasWhitespace = Character.isWhitespace(c); + } + } + return false; + } + public static boolean isNestedTooDeeply (CharSequence line){ if (isPositionQuoteCharacter(line, 0)) { int nestingDepth = 1; @@ -48,4 +90,13 @@ public class QuoteHelper { } return false; } -} + + public static String replaceAltQuoteCharsInText(String text){ + for (int i = 0; i < text.length(); i++){ + if (isPositionAltQuoteStart(text, i)){ + text = text.substring(0, i) + QUOTE_CHAR + text.substring(i + 1); + } + } + return text; + } +} \ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index c293bcd127154994a676de862d7c48de05d89fc0..4c2c0b5855039658368b535ddceedcc766800c07 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -329,7 +329,7 @@ public class UIHelper { continue; } char first = l.charAt(0); - if ((!QuoteHelper.isPositionQuoteStart(l, 0)) && first != '\u00bb') { + if ((!QuoteHelper.isPositionQuoteStart(l, 0))) { CharSequence line = CharSequenceUtils.trim(l); if (line.length() == 0) { continue; @@ -373,14 +373,6 @@ public class UIHelper { return input.length() > 256 ? StylingHelper.subSequence(input, 0, 256) : input; } - public static boolean isPositionFollowedByWhitespace(CharSequence body, int pos){ - return Character.isWhitespace(body.charAt(pos + 1)); - } - - public static boolean isPositionPrecededByWhitespace(CharSequence body, int pos){ - return Character.isWhitespace(body.charAt(pos -1 )); - } - public static boolean isPositionPrecededByBodyStart(CharSequence body, int pos){ // true if not a single linebreak before current position for (int i = pos - 1; i >= 0; i--){ @@ -395,10 +387,7 @@ public class UIHelper { if (isPositionPrecededByBodyStart(body, pos)){ return true; } - if (body.charAt(pos - 1) == '\n'){ - return true; - } - return false; + return body.charAt(pos - 1) == '\n'; } public static boolean isPositionFollowedByQuoteableCharacter(CharSequence body, int pos) { @@ -442,31 +431,13 @@ public class UIHelper { final char c = body.charAt(i); if (Character.isWhitespace(c)) { return false; - } else if (c == '<' || c == '>') { + } else if (QuoteHelper.isPositionQuoteCharacter(body, pos) || QuoteHelper.isPositionQuoteEndCharacter(body, pos)) { return body.length() == i + 1 || Character.isWhitespace(body.charAt(i + 1)); } } return false; } - public static boolean isPositionFollowedByQuote(CharSequence body, int pos) { - if (body.length() <= pos + 1 || Character.isWhitespace(body.charAt(pos + 1))) { - return false; - } - boolean previousWasWhitespace = false; - for (int i = pos + 1; i < body.length(); i++) { - char c = body.charAt(i); - if (c == '\n' || c == '»') { - return false; - } else if (c == '«' && !previousWasWhitespace) { - return true; - } else { - previousWasWhitespace = Character.isWhitespace(c); - } - } - return false; - } - public static String getDisplayName(MucOptions.User user) { Contact contact = user.getContact(); if (contact != null) { From 2db2ca95ceef7f1a50d50097f95b0d9efd9a79b6 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sun, 22 Aug 2021 08:55:46 +0200 Subject: [PATCH 012/145] Move differentiation between XEP-0393 quotes and French quotes to QuoteHelper. --- .../eu/siacs/conversations/ui/adapter/MessageAdapter.java | 5 ++--- src/main/java/eu/siacs/conversations/utils/MessageUtils.java | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) 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 5c91043430750327da9faf498c4b67b7c5596def..dd0bdc5893c343e5f2de35b2faf4e94329fb8e05 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -371,9 +371,8 @@ public class MessageAdapter extends ArrayAdapter { if (lineStart == -1) { if (previous == '\n') { if ( - (QuoteHelper.isPositionQuoteStart(body, i) - || (current == '\u00bb' && !UIHelper.isPositionFollowedByQuote(body, i) - ))) { + QuoteHelper.isPositionQuoteStart(body, i) + ) { // Line start with quote lineStart = i; if (quoteStart == -1) quoteStart = i; diff --git a/src/main/java/eu/siacs/conversations/utils/MessageUtils.java b/src/main/java/eu/siacs/conversations/utils/MessageUtils.java index c9c029c65b0ee833733e83ff32ee5f2774397ecd..0a11cd720412476334f94f8ae127b89a11f4ddfd 100644 --- a/src/main/java/eu/siacs/conversations/utils/MessageUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MessageUtils.java @@ -70,8 +70,7 @@ public class MessageUtils { continue; } final char c = line.charAt(0); - if (QuoteHelper.isNestedTooDeeply(line) - || (c == '\u00bb' && !UIHelper.isPositionFollowedByQuote(line, 0))) { + if (QuoteHelper.isNestedTooDeeply(line)) { continue; } if (builder.length() != 0) { From a0529a4e1e79734e41e3bbbf630d7460fea24286 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sun, 22 Aug 2021 08:56:32 +0200 Subject: [PATCH 013/145] On quoting, translate French quotes to XEP-0393 quotes. --- .../java/eu/siacs/conversations/ui/widget/EditMessage.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java index a4c6e67fd10a75576d65bef6da6b45f902b5f99f..3471ffcfa489741a023ce06a9f1eebce8c978947 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java @@ -24,6 +24,7 @@ import java.util.concurrent.Executors; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.ui.util.QuoteHelper; public class EditMessage extends EmojiWrapperEditText { @@ -142,7 +143,8 @@ public class EditMessage extends EmojiWrapperEditText { } public void insertAsQuote(String text) { - text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)>", "$1>>").replaceAll("(^|\n)([^>])", "$1> $2").replaceAll("\n$", ""); + text = QuoteHelper.replaceAltQuoteCharsInText(text); + text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)(" + QuoteHelper.QUOTE_CHAR + ")", "$1$2$2").replaceAll("(^|\n)([^>" + QuoteHelper.QUOTE_CHAR + "])", "$1> $2").replaceAll("\n$", ""); Editable editable = getEditableText(); int position = getSelectionEnd(); if (position == -1) position = editable.length(); From 955a6f3fe13e0e2783ba529bcb31dcb413d1568b Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sun, 22 Aug 2021 17:45:23 +0200 Subject: [PATCH 014/145] Bugfix for 6cc06bcb98acc05c7677c642adf8ded90ffc8372. --- .../java/eu/siacs/conversations/ui/adapter/MessageAdapter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 dd0bdc5893c343e5f2de35b2faf4e94329fb8e05..0eb8a10fbeeb77f9dc13ac21a8eb87dd0e172785 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -381,7 +381,6 @@ public class MessageAdapter extends ArrayAdapter { // Line start without quote, apply spans there applyQuoteSpan(body, quoteStart, i - 1, darkBackground); quoteStart = -1; - quoteDepth++; } } } else { @@ -406,8 +405,8 @@ public class MessageAdapter extends ArrayAdapter { if (quoteStart >= 0) { // Apply spans to finishing open quote applyQuoteSpan(body, quoteStart, body.length(), darkBackground); - quoteDepth++; } + quoteDepth++; } return startsWithQuote; } From b6fe1898e7717af67bfc98fe6f05dc4a44820e28 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Mon, 23 Aug 2021 13:09:21 +0200 Subject: [PATCH 015/145] Minor duplication fix. --- src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java index 3471ffcfa489741a023ce06a9f1eebce8c978947..eba833c9bd7014db4cccb5bf27178e9c8010718b 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java @@ -144,7 +144,7 @@ public class EditMessage extends EmojiWrapperEditText { public void insertAsQuote(String text) { text = QuoteHelper.replaceAltQuoteCharsInText(text); - text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)(" + QuoteHelper.QUOTE_CHAR + ")", "$1$2$2").replaceAll("(^|\n)([^>" + QuoteHelper.QUOTE_CHAR + "])", "$1> $2").replaceAll("\n$", ""); + text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)(" + QuoteHelper.QUOTE_CHAR + ")", "$1$2$2").replaceAll("(^|\n)([^" + QuoteHelper.QUOTE_CHAR + "])", "$1> $2").replaceAll("\n$", ""); Editable editable = getEditableText(); int position = getSelectionEnd(); if (position == -1) position = editable.length(); From ef8f10cc1327c839b24d56d0db30ea25311b5e76 Mon Sep 17 00:00:00 2001 From: Maximilian Weiler <16721506+maweil@users.noreply.github.com> Date: Tue, 27 Jul 2021 21:27:09 +0200 Subject: [PATCH 016/145] Optionally prevent taking screenshots - Add setting to prevent screenshots - Enforce using FLAG_SECURE in onResume for each activity --- .../siacs/conversations/ui/AboutActivity.java | 8 ++++++++ .../ui/ConversationActivity.java | 9 +++++++++ .../conversations/ui/LocationActivity.java | 3 +++ .../conversations/ui/MemorizingActivity.java | 4 ++++ .../conversations/ui/RecordingActivity.java | 8 ++++++++ .../siacs/conversations/ui/ScanActivity.java | 3 +++ .../conversations/ui/SettingsActivity.java | 10 ++++++++++ .../siacs/conversations/ui/XmppActivity.java | 4 +++- .../conversations/ui/util/SettingsUtils.java | 20 +++++++++++++++++++ src/main/res/values/defaults.xml | 1 + src/main/res/values/strings.xml | 3 +++ src/main/res/xml/preferences.xml | 7 +++++++ 12 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/main/java/eu/siacs/conversations/ui/util/SettingsUtils.java diff --git a/src/main/java/eu/siacs/conversations/ui/AboutActivity.java b/src/main/java/eu/siacs/conversations/ui/AboutActivity.java index 3c705e639fa23e932fcc18fbb13721cad6b0e7ca..f79a65597d6cce1985b3f3502b3a92f5b5218a8a 100644 --- a/src/main/java/eu/siacs/conversations/ui/AboutActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/AboutActivity.java @@ -1,16 +1,24 @@ package eu.siacs.conversations.ui; import android.os.Bundle; +import android.preference.PreferenceManager; import androidx.appcompat.app.AppCompatActivity; import eu.siacs.conversations.R; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.ThemeHelper; import static eu.siacs.conversations.ui.XmppActivity.configureActionBar; public class AboutActivity extends AppCompatActivity { + @Override + protected void onResume(){ + super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 8e23da5bc0e4f9ecebd7738830db2d9894922ccc..04361a5da5e325c0c2f73ff7ac868c3883e2d069 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -2,10 +2,13 @@ package eu.siacs.conversations.ui; import android.content.Intent; import android.os.Bundle; +import android.preference.PreferenceManager; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import eu.siacs.conversations.ui.util.SettingsUtils; + public class ConversationActivity extends AppCompatActivity { @Override @@ -14,4 +17,10 @@ public class ConversationActivity extends AppCompatActivity { startActivity(new Intent(this, ConversationsActivity.class)); finish(); } + + @Override + protected void onResume(){ + super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/LocationActivity.java b/src/main/java/eu/siacs/conversations/ui/LocationActivity.java index ed08fa0d24aee40a9f9e9dd80c1372cd056d5a0f..2627e0e59ea44cc4ad3be7b4434d88af67691fad 100644 --- a/src/main/java/eu/siacs/conversations/ui/LocationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/LocationActivity.java @@ -39,6 +39,7 @@ import eu.siacs.conversations.services.QuickConversationsService; import eu.siacs.conversations.ui.util.LocationHelper; import eu.siacs.conversations.ui.widget.Marker; import eu.siacs.conversations.ui.widget.MyLocation; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.ThemeHelper; public abstract class LocationActivity extends ActionBarActivity implements LocationListener { @@ -68,6 +69,7 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca } } + protected void updateLocationMarkers() { clearMarkers(); } @@ -222,6 +224,7 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca @Override protected void onResume() { super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); Configuration.getInstance().load(this, getPreferences()); map.onResume(); this.setMyLoc(null); diff --git a/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java b/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java index 7f8a55a727d527bd74cff977ed51ff61e613168e..123f57fbba3b05be9948d9c9d6057e92803170ca 100644 --- a/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java @@ -29,6 +29,7 @@ import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; +import android.preference.PreferenceManager; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; @@ -39,6 +40,7 @@ import java.util.logging.Logger; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.MTMDecision; import eu.siacs.conversations.services.MemorizingTrustManager; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.ThemeHelper; public class MemorizingActivity extends AppCompatActivity implements OnClickListener, OnCancelListener { @@ -61,6 +63,8 @@ public class MemorizingActivity extends AppCompatActivity implements OnClickList @Override public void onResume() { super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); + Intent i = getIntent(); decisionId = i.getIntExtra(MemorizingTrustManager.DECISION_INTENT_ID, MTMDecision.DECISION_INVALID); int titleId = i.getIntExtra(MemorizingTrustManager.DECISION_TITLE_ID, R.string.mtm_accept_cert); diff --git a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java index 5e4c900487fcc5f9152c8652931fc07d23cd0288..aa4e15c067b3b82954930a706e6c5089bc9745c8 100644 --- a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java @@ -9,6 +9,7 @@ import android.os.Bundle; import android.os.FileObserver; import android.os.Handler; import android.os.SystemClock; +import android.preference.PreferenceManager; import android.util.Log; import android.view.View; import android.view.WindowManager; @@ -28,6 +29,7 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityRecordingBinding; import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.ThemeHelper; import eu.siacs.conversations.utils.TimeFrameUtils; @@ -66,6 +68,12 @@ public class RecordingActivity extends Activity implements View.OnClickListener getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } + @Override + protected void onResume(){ + super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); + } + @Override protected void onStart() { super.onStart(); diff --git a/src/main/java/eu/siacs/conversations/ui/ScanActivity.java b/src/main/java/eu/siacs/conversations/ui/ScanActivity.java index cebd19bc3dd068a3545fa5b623ef486ade9c2af7..3e7eddc64190b8b3795daeb0ffe1207531b4e71f 100644 --- a/src/main/java/eu/siacs/conversations/ui/ScanActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ScanActivity.java @@ -33,6 +33,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.os.Vibrator; +import android.preference.PreferenceManager; import android.util.Log; import android.view.KeyEvent; import android.view.Surface; @@ -61,6 +62,7 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.ui.service.CameraManager; import eu.siacs.conversations.ui.widget.ScannerView; +import eu.siacs.conversations.ui.util.SettingsUtils; /** * @author Andreas Schildbach @@ -181,6 +183,7 @@ public final class ScanActivity extends Activity implements SurfaceTextureListen @Override protected void onResume() { super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); maybeOpenCamera(); } diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java index 68cc429216591b845751ffeaac786df6755331b3..7f4e59d1a2e31f565361fcfb0fafaa259b9785e0 100644 --- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java @@ -40,6 +40,7 @@ import eu.siacs.conversations.services.MemorizingTrustManager; import eu.siacs.conversations.services.QuickConversationsService; import eu.siacs.conversations.ui.util.StyledAttributes; import eu.siacs.conversations.utils.GeoHelper; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.TimeFrameUtils; import eu.siacs.conversations.xmpp.Jid; @@ -57,8 +58,10 @@ public class SettingsActivity extends XmppActivity implements public static final String THEME = "theme"; public static final String SHOW_DYNAMIC_TAGS = "show_dynamic_tags"; public static final String OMEMO_SETTING = "omemo"; + public static final String PREVENT_SCREENSHOTS = "prevent_screenshots"; public static final int REQUEST_CREATE_BACKUP = 0xbf8701; + private SettingsFragment mSettingsFragment; @Override @@ -393,8 +396,15 @@ public class SettingsActivity extends XmppActivity implements if (this.mTheme != theme) { recreate(); } + } else if(name.equals(PREVENT_SCREENSHOTS)){ + SettingsUtils.applyScreenshotPreventionSetting(this); } + } + @Override + public void onResume(){ + super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); } @Override diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 021ec4a5e34f6c636143f6e19b612d2e42456e7c..4b5382b444cf70a1781386916fd99b6b42a99800 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -80,6 +80,7 @@ import eu.siacs.conversations.ui.util.PresenceSelector; import eu.siacs.conversations.ui.util.SoftKeyboardUtils; import eu.siacs.conversations.utils.AccountUtils; import eu.siacs.conversations.utils.ExceptionHelper; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.ThemeHelper; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; @@ -819,8 +820,9 @@ public abstract class XmppActivity extends ActionBarActivity { } @Override - public void onResume() { + protected void onResume(){ super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); } protected int findTheme() { diff --git a/src/main/java/eu/siacs/conversations/ui/util/SettingsUtils.java b/src/main/java/eu/siacs/conversations/ui/util/SettingsUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..ae99e094335798a7e3d7af52558e660dc0ce7b37 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/util/SettingsUtils.java @@ -0,0 +1,20 @@ +package eu.siacs.conversations.ui.util; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.view.Window; +import android.view.WindowManager; + +public class SettingsUtils { + public static void applyScreenshotPreventionSetting(Activity activity){ + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); + boolean preventScreenshots = preferences.getBoolean("prevent_screenshots", false); + Window activityWindow = activity.getWindow(); + if(preventScreenshots){ + activityWindow.addFlags(WindowManager.LayoutParams.FLAG_SECURE); + } else { + activityWindow.clearFlags(WindowManager.LayoutParams.FLAG_SECURE); + } + } +} diff --git a/src/main/res/values/defaults.xml b/src/main/res/values/defaults.xml index a031a9149a4c2a2330cf5cc5698b7985a54ddd73..60085d0f9bb0c32636d8d429f15c429c1b43464b 100644 --- a/src/main/res/values/defaults.xml +++ b/src/main/res/values/defaults.xml @@ -44,4 +44,5 @@ false 360 JABBER_NETWORK + false diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 9f3ba84acd886ed014e8832def43e93a99031982..5d8c32e54cbdf5cce3302d91ad43650e7366e4c5 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -130,6 +130,8 @@ By sending in stack traces you are helping the development Confirm Messages Let your contacts know when you have received and read their messages + Prevent Screenshots + Prevent taking screenshots of this app and hide its content in the app switcher UI OpenKeychain produced an error. Bad key for encryption. @@ -966,4 +968,5 @@ The backup has been started. You’ll get a notification once it has been completed. Unable to enable video. Plain text document + diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index 99f9e83abb09cc3839dc0ffafbdcd39a985819c9..91b07210c55fd00bce03a02562b94dc77714ec76 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -32,6 +32,13 @@ android:key="last_activity" android:summary="@string/pref_broadcast_last_activity_summary" android:title="@string/pref_broadcast_last_activity" /> + + + Date: Tue, 27 Jul 2021 21:51:55 +0200 Subject: [PATCH 017/145] Remove unused import --- src/main/java/eu/siacs/conversations/ui/AboutActivity.java | 1 - .../java/eu/siacs/conversations/ui/ConversationActivity.java | 1 - .../java/eu/siacs/conversations/ui/MemorizingActivity.java | 1 - src/main/java/eu/siacs/conversations/ui/RecordingActivity.java | 1 - src/main/java/eu/siacs/conversations/ui/ScanActivity.java | 3 +-- 5 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/AboutActivity.java b/src/main/java/eu/siacs/conversations/ui/AboutActivity.java index f79a65597d6cce1985b3f3502b3a92f5b5218a8a..917512a029c26348c8283bbbfabe27d4c65f4cde 100644 --- a/src/main/java/eu/siacs/conversations/ui/AboutActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/AboutActivity.java @@ -1,7 +1,6 @@ package eu.siacs.conversations.ui; import android.os.Bundle; -import android.preference.PreferenceManager; import androidx.appcompat.app.AppCompatActivity; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 04361a5da5e325c0c2f73ff7ac868c3883e2d069..2d9fe91ae1c5301bc0f60159b1fddb123a0aa7a0 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -2,7 +2,6 @@ package eu.siacs.conversations.ui; import android.content.Intent; import android.os.Bundle; -import android.preference.PreferenceManager; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; diff --git a/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java b/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java index 123f57fbba3b05be9948d9c9d6057e92803170ca..23f3c82d8b0585bd62c88e3e0be0d15c961b9a6c 100644 --- a/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java @@ -29,7 +29,6 @@ import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; -import android.preference.PreferenceManager; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; diff --git a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java index aa4e15c067b3b82954930a706e6c5089bc9745c8..6146c4ae72a1131793ff649b45551062203b6fb8 100644 --- a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java @@ -9,7 +9,6 @@ import android.os.Bundle; import android.os.FileObserver; import android.os.Handler; import android.os.SystemClock; -import android.preference.PreferenceManager; import android.util.Log; import android.view.View; import android.view.WindowManager; diff --git a/src/main/java/eu/siacs/conversations/ui/ScanActivity.java b/src/main/java/eu/siacs/conversations/ui/ScanActivity.java index 3e7eddc64190b8b3795daeb0ffe1207531b4e71f..95505647d2dc5240c2a5932667ed0eb833830a6c 100644 --- a/src/main/java/eu/siacs/conversations/ui/ScanActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ScanActivity.java @@ -33,7 +33,6 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.os.Vibrator; -import android.preference.PreferenceManager; import android.util.Log; import android.view.KeyEvent; import android.view.Surface; @@ -61,8 +60,8 @@ import java.util.Map; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.ui.service.CameraManager; -import eu.siacs.conversations.ui.widget.ScannerView; import eu.siacs.conversations.ui.util.SettingsUtils; +import eu.siacs.conversations.ui.widget.ScannerView; /** * @author Andreas Schildbach From b00b8996d5fd8cec3cdee31732ce52ac7879e35e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 10:13:03 +0200 Subject: [PATCH 018/145] bump gradle version and agp --- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 855500be61a02225f35f3b67d38093557887102c..3f5a2f35911b68b32bc54f3f65ee82daf743fd5d 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.android.tools.build:gradle:7.0.1' } } @@ -111,7 +111,7 @@ android { } configurations { - compile.exclude group: 'org.jetbrains' , module:'annotations' + implementation.exclude group: 'org.jetbrains' , module:'annotations' } dataBinding { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ea1e4b836718ba6cbc554edd76b03575ce9356a1..84fa7550afd4b0101dcc7202c1d85d6c9aef6280 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-6.7.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip From 339ee8f6ea4c213cfcacd47a6410fbcc0f727a81 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 11:15:50 +0200 Subject: [PATCH 019/145] bump libwebrtc version to m92 --- .travis.yml | 22 ---------------------- build.gradle | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 664d3cf553231dc3793610b5b35e33e93286963d..0000000000000000000000000000000000000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: android -jdk: - - oraclejdk8 -android: - components: - - platform-tools - - tools - - build-tools-28.0.3 - - extra-google-google_play_services - licenses: - - '.+' -before_script: - - mkdir libs - - wget -O libs/libwebrtc-m90.aar https://gultsch.de/files/libwebrtc-m90.aar -script: - - ./gradlew assembleQuicksyFreeCompatDebug - - ./gradlew assembleQuicksyFreeSystemDebug - - ./gradlew assembleConversationsFreeCompatDebug - - ./gradlew assembleConversationsFreeSystemDebug - -before_install: - - yes | sdkmanager "platforms;android-28" diff --git a/build.gradle b/build.gradle index 3f5a2f35911b68b32bc54f3f65ee82daf743fd5d..9937179e84825e20b82098f3ae024b169df65b9e 100644 --- a/build.gradle +++ b/build.gradle @@ -78,7 +78,7 @@ dependencies { implementation 'com.google.guava:guava:30.1.1-android' quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.18' - implementation fileTree(include: ['libwebrtc-m90.aar'], dir: 'libs') + implementation fileTree(include: ['libwebrtc-m92.aar'], dir: 'libs') } ext { From caefec2fbfe87465e55fc36e9d881d9bcd9c7ea5 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 09:19:08 +0000 Subject: [PATCH 020/145] Create android.yml --- .github/workflows/android.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/android.yml diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000000000000000000000000000000000000..97859f167c8d5eeaab20a57a02f1a2c7a18d7cf8 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,32 @@ +name: Android CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build Quicksy (Compat) + run: ./gradlew assembleQuicksyFreeCompatDebug + - name: Build Quicksy (System) + run: ./gradlew assembleQuicksyFreeSystemDebug + - name: Build Conversations (Compat) + run: ./gradlew assembleConversationsFreeCompatDebug + - name: Build Conversations (System) + run: ./gradlew assembleConversationsFreeSystemDebug + From 4e90c0dbbb090dd014a10601caca246d4dca219a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 09:21:17 +0000 Subject: [PATCH 021/145] Update android.yml to download webrtc --- .github/workflows/android.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 97859f167c8d5eeaab20a57a02f1a2c7a18d7cf8..2c0861ffb3e3822a8a27dbe1936a698e6cecc3f0 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -18,7 +18,8 @@ jobs: with: java-version: '11' distribution: 'adopt' - + - name: Download WebRTC + run: mkdir libs && wget -O libs/libwebrtc-m92.aar https://gultsch.de/files/libwebrtc-m92.aar - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build Quicksy (Compat) From bf3c1d573be81e7646d56f435c9a4640dcdeb323 Mon Sep 17 00:00:00 2001 From: Licaon_Kter Date: Tue, 24 Aug 2021 10:15:13 +0000 Subject: [PATCH 022/145] Avoid description repetition screenshots --- src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 5d8c32e54cbdf5cce3302d91ad43650e7366e4c5..6a3815073c600b36fff7a7cec79a618818532c6c 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -131,7 +131,7 @@ Confirm Messages Let your contacts know when you have received and read their messages Prevent Screenshots - Prevent taking screenshots of this app and hide its content in the app switcher + Hide app contents in the app switcher and block screenshots UI OpenKeychain produced an error. Bad key for encryption. From 208c9d91db5cace8641db21dbc0bf38fb4574096 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 13:02:31 +0200 Subject: [PATCH 023/145] dexOptions is no longer used in agp7 --- build.gradle | 6 ------ 1 file changed, 6 deletions(-) diff --git a/build.gradle b/build.gradle index 9937179e84825e20b82098f3ae024b169df65b9e..7720d848ad966cbaaea042f1f30e7bf3af33eaf1 100644 --- a/build.gradle +++ b/build.gradle @@ -118,12 +118,6 @@ android { enabled true } - dexOptions { - // Skip pre-dexing when running on Travis CI or when disabled via -Dpre-dex=false. - preDexLibraries = preDexEnabled && !travisBuild - jumboMode true - } - compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 From e6d8bee035d4ad86edf2589349d42e0e6594b602 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 13:33:19 +0200 Subject: [PATCH 024/145] stop agp7 complaining about missing proguard rules --- proguard-rules.pro | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/proguard-rules.pro b/proguard-rules.pro index c8b4089c521d0fcfe7d73553998dfa1500f5bed0..7e4d7d31d6553d006e1d1008e5c4fd90a1004bb1 100644 --- a/proguard-rules.pro +++ b/proguard-rules.pro @@ -26,6 +26,15 @@ -dontwarn java.lang.** -dontwarn javax.lang.** +-dontwarn com.android.org.conscrypt.SSLParametersImpl +-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl +-dontwarn org.bouncycastle.jsse.BCSSLParameters +-dontwarn org.bouncycastle.jsse.BCSSLSocket +-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider +-dontwarn org.openjsse.javax.net.ssl.SSLParameters +-dontwarn org.openjsse.javax.net.ssl.SSLSocket +-dontwarn org.openjsse.net.ssl.OpenJSSE + -keepclassmembers class eu.siacs.conversations.http.services.** { !transient ; } From 88d7ddf1248909f2b2dc940fee7f48342d650ae6 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 14:40:12 +0200 Subject: [PATCH 025/145] PIP aspect ratio should match video aspect ratio. fixes #4077 --- .../conversations/ui/RtpSessionActivity.java | 27 +++++++++-- .../conversations/ui/util/Rationals.java | 26 ++++++++++ .../ui/widget/SurfaceViewRenderer.java | 48 +++++++++++++++++++ src/main/res/layout/activity_rtp_session.xml | 4 +- 4 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/ui/util/Rationals.java create mode 100644 src/main/java/eu/siacs/conversations/ui/widget/SurfaceViewRenderer.java diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index ad016a3b35602bd7427b6eccdf5d35ba8e07cf88..96aa00db048d534ea749a7e3db94b0fdab8af3bb 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -1,5 +1,8 @@ package eu.siacs.conversations.ui; +import static java.util.Arrays.asList; +import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied; + import android.Manifest; import android.annotation.SuppressLint; import android.app.PictureInPictureParams; @@ -55,6 +58,7 @@ import eu.siacs.conversations.services.AppRTCAudioManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.util.AvatarWorkerTask; import eu.siacs.conversations.ui.util.MainThreadExecutor; +import eu.siacs.conversations.ui.util.Rationals; import eu.siacs.conversations.utils.PermissionUtils; import eu.siacs.conversations.utils.TimeFrameUtils; import eu.siacs.conversations.xml.Namespace; @@ -65,10 +69,7 @@ import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; -import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied; -import static java.util.Arrays.asList; - -public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate { +public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate, eu.siacs.conversations.ui.widget.SurfaceViewRenderer.OnAspectRatioChanged { public static final String EXTRA_WITH = "with"; public static final String EXTRA_SESSION_ID = "session_id"; @@ -446,12 +447,14 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe public void onStart() { super.onStart(); mHandler.postDelayed(mTickExecutor, CALL_DURATION_UPDATE_INTERVAL); + this.binding.remoteVideo.setOnAspectRatioChanged(this); } @Override public void onStop() { mHandler.removeCallbacks(mTickExecutor); binding.remoteVideo.release(); + binding.remoteVideo.setOnAspectRatioChanged(null); binding.localVideo.release(); final WeakReference weakReference = this.rtpConnectionReference; final JingleRtpConnection jingleRtpConnection = weakReference == null ? null : weakReference.get(); @@ -515,9 +518,12 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe @RequiresApi(api = Build.VERSION_CODES.O) private void startPictureInPicture() { try { + final Rational rational = this.binding.remoteVideo.getAspectRatio(); + final Rational clippedRational = Rationals.clip(rational); + Log.d(Config.LOGTAG, "suggested rational " + rational + ". clipped to " + clippedRational); enterPictureInPictureMode( new PictureInPictureParams.Builder() - .setAspectRatio(new Rational(10, 16)) + .setAspectRatio(clippedRational) .build() ); } catch (final IllegalStateException e) { @@ -526,6 +532,17 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe } } + @Override + public void onAspectRatioChanged(final Rational rational) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPicture()) { + final Rational clippedRational = Rationals.clip(rational); + Log.d(Config.LOGTAG, "suggested rational after aspect ratio change " + rational + ". clipped to " + clippedRational); + setPictureInPictureParams(new PictureInPictureParams.Builder() + .setAspectRatio(clippedRational) + .build()); + } + } + private boolean deviceSupportsPictureInPicture() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE); diff --git a/src/main/java/eu/siacs/conversations/ui/util/Rationals.java b/src/main/java/eu/siacs/conversations/ui/util/Rationals.java new file mode 100644 index 0000000000000000000000000000000000000000..31155cd6ee8d038e4866f1e9d1374ec118aaa635 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/util/Rationals.java @@ -0,0 +1,26 @@ +package eu.siacs.conversations.ui.util; + +import android.util.Rational; + +public final class Rationals { + + //between 2.39:1 and 1:2.39 (inclusive). + private static final Rational MIN = new Rational(100,239); + private static final Rational MAX = new Rational(239,100); + + private Rationals() { + + } + + + public static Rational clip(final Rational input) { + if (input.compareTo(MIN) < 0) { + return MIN; + } + if (input.compareTo(MAX) > 0) { + return MAX; + } + return input; + } + +} diff --git a/src/main/java/eu/siacs/conversations/ui/widget/SurfaceViewRenderer.java b/src/main/java/eu/siacs/conversations/ui/widget/SurfaceViewRenderer.java new file mode 100644 index 0000000000000000000000000000000000000000..06f6040764a68300c9d26350d86e54334aef1c6e --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/widget/SurfaceViewRenderer.java @@ -0,0 +1,48 @@ +package eu.siacs.conversations.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Rational; + +import eu.siacs.conversations.Config; + +public class SurfaceViewRenderer extends org.webrtc.SurfaceViewRenderer { + + private Rational aspectRatio = new Rational(1,1); + + private OnAspectRatioChanged onAspectRatioChanged; + + public SurfaceViewRenderer(Context context) { + super(context); + } + + public SurfaceViewRenderer(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void onFrameResolutionChanged(int videoWidth, int videoHeight, int rotation) { + super.onFrameResolutionChanged(videoWidth, videoHeight, rotation); + final int rotatedWidth = rotation != 0 && rotation != 180 ? videoHeight : videoWidth; + final int rotatedHeight = rotation != 0 && rotation != 180 ? videoWidth : videoHeight; + final Rational currentRational = this.aspectRatio; + this.aspectRatio = new Rational(rotatedWidth, rotatedHeight); + Log.d(Config.LOGTAG,"onFrameResolutionChanged("+rotatedWidth+","+rotatedHeight+","+aspectRatio+")"); + if (currentRational.equals(this.aspectRatio) || onAspectRatioChanged == null) { + return; + } + onAspectRatioChanged.onAspectRatioChanged(this.aspectRatio); + } + + public void setOnAspectRatioChanged(final OnAspectRatioChanged onAspectRatioChanged) { + this.onAspectRatioChanged = onAspectRatioChanged; + } + + public Rational getAspectRatio() { + return this.aspectRatio; + } + + public interface OnAspectRatioChanged { + void onAspectRatioChanged(final Rational rational); + } +} diff --git a/src/main/res/layout/activity_rtp_session.xml b/src/main/res/layout/activity_rtp_session.xml index 26fa4d496b95e85ce3278d3bb20182ac3c10de20..0bdca47760059988ea8b33915154d7466e20fb39 100644 --- a/src/main/res/layout/activity_rtp_session.xml +++ b/src/main/res/layout/activity_rtp_session.xml @@ -98,13 +98,13 @@ android:gravity="center" android:visibility="gone"> - - Date: Tue, 24 Aug 2021 14:42:50 +0200 Subject: [PATCH 026/145] reset affiliation when inviting someone not currently in group. fixes #4146 --- .../services/XmppConnectionService.java | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 759a0d727d1d9b8617e3693e2eb652b1ceac8464..be6c6ce01ff5f2f4ca090d364001f17b45fcc9a7 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -471,7 +471,6 @@ public class XmppConnectionService extends Service { private OpenPgpServiceConnection pgpServiceConnection; private PgpEngine mPgpEngine = null; private WakeLock wakeLock; - private PowerManager pm; private LruCache mBitmapCache; private final BroadcastReceiver mInternalEventReceiver = new InternalEventReceiver(); private final BroadcastReceiver mInternalScreenEventReceiver = new InternalEventReceiver(); @@ -1172,7 +1171,7 @@ public class XmppConnectionService extends Service { this.pgpServiceConnection.bindToService(); } - this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + final PowerManager pm = ContextCompat.getSystemService(this, PowerManager.class); this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Conversations:Service"); toggleForegroundService(); @@ -3337,35 +3336,26 @@ public class XmppConnectionService extends Service { public void changeAffiliationInConference(final Conversation conference, Jid user, final MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) { final Jid jid = user.asBareJid(); - IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString()); - sendIqPacket(conference.getAccount(), request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - conference.getMucOptions().changeAffiliation(jid, affiliation); - getAvatarService().clear(conference); + final IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString()); + sendIqPacket(conference.getAccount(), request, (account, response) -> { + if (response.getType() == IqPacket.TYPE.RESULT) { + conference.getMucOptions().changeAffiliation(jid, affiliation); + getAvatarService().clear(conference); + if (callback != null) { callback.onAffiliationChangedSuccessful(jid); } else { - callback.onAffiliationChangeFailed(jid, R.string.could_not_change_affiliation); + Log.d(Config.LOGTAG, "changed affiliation of " + user + " to " + affiliation); } + } else if (callback != null) { + callback.onAffiliationChangeFailed(jid, R.string.could_not_change_affiliation); + } else { + Log.d(Config.LOGTAG, "unable to change affiliation"); } }); } - public void changeAffiliationsInConference(final Conversation conference, MucOptions.Affiliation before, MucOptions.Affiliation after) { - List jids = new ArrayList<>(); - for (MucOptions.User user : conference.getMucOptions().getUsers()) { - if (user.getAffiliation() == before && user.getRealJid() != null) { - jids.add(user.getRealJid()); - } - } - IqPacket request = this.mIqGenerator.changeAffiliation(conference, jids, after.toString()); - sendIqPacket(conference.getAccount(), request, mDefaultIqHandler); - } - public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role) { IqPacket request = this.mIqGenerator.changeRole(conference, nick, role.toString()); - Log.d(Config.LOGTAG, request.toString()); sendIqPacket(conference.getAccount(), request, (account, packet) -> { if (packet.getType() != IqPacket.TYPE.RESULT) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + " unable to change role of " + nick); @@ -3928,9 +3918,13 @@ public class XmppConnectionService extends Service { new Thread(() -> reconnectAccount(account, false, true)).start(); } - public void invite(Conversation conversation, Jid contact) { + public void invite(final Conversation conversation, final Jid contact) { Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": inviting " + contact + " to " + conversation.getJid().asBareJid()); - MessagePacket packet = mMessageGenerator.invite(conversation, contact); + final MucOptions.User user = conversation.getMucOptions().findUserByRealJid(contact.asBareJid()); + if (user == null || user.getAffiliation() == MucOptions.Affiliation.OUTCAST) { + changeAffiliationInConference(conversation, contact, MucOptions.Affiliation.NONE, null); + } + final MessagePacket packet = mMessageGenerator.invite(conversation, contact); sendMessagePacket(conversation.getAccount(), packet); } From 0495470ca8412cea7eb55e72b199d3aa10523058 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 15:32:10 +0200 Subject: [PATCH 027/145] pulled translations from transifex --- src/conversations/res/values-ar/strings.xml | 16 +- src/conversations/res/values-el/strings.xml | 2 +- src/conversations/res/values-ja/strings.xml | 14 +- src/conversations/res/values-sk/strings.xml | 14 + src/conversations/res/values-sv/strings.xml | 5 +- src/main/res/values-ar/strings.xml | 16 +- src/main/res/values-bn-rIN/strings.xml | 21 + src/main/res/values-cs/strings.xml | 13 + src/main/res/values-da-rDK/strings.xml | 3 + src/main/res/values-de/strings.xml | 6 +- src/main/res/values-el/strings.xml | 53 +-- src/main/res/values-es/strings.xml | 9 + src/main/res/values-fr/strings.xml | 5 + src/main/res/values-gl/strings.xml | 24 +- src/main/res/values-it/strings.xml | 3 + src/main/res/values-ja/strings.xml | 67 ++-- src/main/res/values-ml/strings.xml | 44 +++ src/main/res/values-pl/strings.xml | 5 +- src/main/res/values-pt-rBR/strings.xml | 2 +- src/main/res/values-ro-rRO/strings.xml | 11 +- src/main/res/values-ru/strings.xml | 3 + src/main/res/values-sk/strings.xml | 174 ++++++++ src/main/res/values-sr/strings.xml | 18 + src/main/res/values-sv/strings.xml | 27 +- src/main/res/values-tr-rTR/strings.xml | 4 + src/main/res/values-vi/strings.xml | 415 +++++++++++++++++++- src/main/res/values-zh-rCN/strings.xml | 2 +- src/quicksy/res/values-ar/strings.xml | 8 +- 28 files changed, 896 insertions(+), 88 deletions(-) create mode 100644 src/conversations/res/values-sk/strings.xml diff --git a/src/conversations/res/values-ar/strings.xml b/src/conversations/res/values-ar/strings.xml index 1e820a9ccff02fac4d7188668374088eede8f605..7d44818a72f347631896cbc0b4d223c58e499a20 100644 --- a/src/conversations/res/values-ar/strings.xml +++ b/src/conversations/res/values-ar/strings.xml @@ -3,4 +3,18 @@ اختر مزود خدمة XMPP الخاص بك استخدِم conversations.im أنشئ حسابًا جديدًا - \ No newline at end of file + هل تملك حساب XMPP؟؟ قد يكون ذلك ممكنا لو كنت تستعمل خدمة XMPP أخرى أو إستعملت تطبيق كونفرسايشنز سابقا. أو يمكنك صنع حساب XMPP جديد الآن. +ملاحظة: بعض خدمات البريد الإلكتروني تقدم حسابات XMPP. + XMPP هي خدمة مستقلة للتواصل بشبكة الرسائل المباشرة. يمكنك إستعمال هذه الخدمة مع أي خادم XMPP تختاره. +سعيا لراحتك جعلنا خلق حساب في كونفيرسايشنز سهلا مع مقدم خدمة خاص بالإستعمال مع كونفيرسايشنز. + لقد تمت دعوتك لـ %1$s. سيتم دلّك على طريقة صنع حساب. +عندما تختار %1$sكمقدّم خدمة سيصبح من الممكن لك التواصل مع مستعملين من أي خادم آخر عن طريق إعطائهم عنوانك الكامل على XMPP. + تمّت دعوتك إلى %1$s. تم إختيار إسم مستخدم خاص بك. سيتم قيادتك عبر طريقة صنع حساب. +سيمكنك التواصل مع مستخدمين من مزودين آخرين عبر إعطائهم كامل عنوانك XMPP. + سيرفر دعوتك + لم يتم التقاط الكود بطريقة جيّدة + إضغط على زر مشاركة لترسل إلى المتصل بك دعوة إلى %1$s. + إذا كان المتصل بك قريبا منك، يمكنه فحص الكود بالأسفل ليقبل دعوتك. + إنظم %1$s وتحدّث معي: %2$s + شارك إستدعاء مع... + \ No newline at end of file diff --git a/src/conversations/res/values-el/strings.xml b/src/conversations/res/values-el/strings.xml index 9ee6d96ae4fc53127a634e68087cf304cbd2a333..7c87e66a3eda03b9ec9a2e3de978b02d85a43eae 100644 --- a/src/conversations/res/values-el/strings.xml +++ b/src/conversations/res/values-el/strings.xml @@ -10,7 +10,7 @@ Η πρόσκλησή σας στον διακομιστή Λάθος μορφοποίηση κώδικα παροχής Πατήστε το πλήκτρο διαμοιρασμού για να στείλετε στην επαφή σας μια πρόσκληση στο %1$s. - Αν η επαφή σας βρίσκεται κοντά σας, μπορεί επίσης να σκανάρει τον κωδικό παρακάτω για να αποδεχτεί την πρόσκλησή σας. + Αν η επαφή σας βρίσκεται κοντά σας, μπορεί επίσης να σαρώσει τον κωδικό παρακάτω για να αποδεχτεί την πρόσκλησή σας. Μπείτε στο %1$s και συνομιλήστε μαζί μου: %2$s Διαμοιρασμός πρόσκλησης με... \ No newline at end of file diff --git a/src/conversations/res/values-ja/strings.xml b/src/conversations/res/values-ja/strings.xml index 2a9af807d2e8278dcedc50cc507ee81e575f780c..0ab18b6dee9118748b48424dc162c5f4e783c70c 100644 --- a/src/conversations/res/values-ja/strings.xml +++ b/src/conversations/res/values-ja/strings.xml @@ -1,12 +1,12 @@ - XMPPプロバイダーを選択してください - conversations.imを利用する - アカウントを作成 - XMPPアカウントをお持ちですか?既にほかのXMPPクライアントを利用しているか、Conversationsを利用したことがある場合はこちら。初めての方は、今すぐ新しいXMPPアカウントを作成できます。\nヒント: eメールのプロバイダーがXMPPアカウントも提供している場合があります。 - XMPPは、プロバイダーに依存しないインスタントメッセージのプロトコルです。XMPPサーバーならどこでも、このクライアントを使用することができます。\nよろしければ、Conversationsに最適化されたプロバイダーconversations.im¹で簡単にアカウントを作成することもできます。 - %1$sへ招待されました。アカウント作成手順をご案内します。 \n%1$sをプロバイダーに選択してほかのプロバイダーのユーザーと会話するには、XMPPのフルアドレスを相手にお知らせください。 - %1$sへ招待されました。ユーザーネームは既に選択されています。アカウント作成手順をご案内します。 \nほかのプロバイダーのユーザーと会話するには、XMPPのフルアドレスを相手にお知らせください。 + XMPP プロバイダーを選択してください + conversations.im を利用する + 新しいアカウントを作成 + XMPP アカウントをお持ちですか?既にほかの XMPP クライアントを利用しているか、 Conversations を利用したことがある場合はこちら。初めての方は、今すぐ新しい XMPP アカウントを作成できます。\nヒント: e メールのプロバイダーが XMPP アカウントも提供している場合があります。 + XMPP は、プロバイダーに依存しないインスタントメッセージのプロトコルです。 XMPP サーバーならどこでも、このクライアントを使用することができます。\nよろしければ、 Conversations に最適化されたプロバイダー conversations.im¹ で簡単にアカウントを作成することもできます。 + %1$s へ招待されました。アカウント作成手順をご案内します。 \n%1$s をプロバイダーに選択してほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。 + %1$s へ招待されました。ユーザー名は既に選択されています。アカウント作成手順をご案内します。 \nほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。 サーバーの招待 仮コードの書式が不正です 共有ボタンを叩いて、連絡先の %1$s に招待を送信する。 diff --git a/src/conversations/res/values-sk/strings.xml b/src/conversations/res/values-sk/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..4897be11a6fe4adbb4b12b41c94d0dc87017bfcc --- /dev/null +++ b/src/conversations/res/values-sk/strings.xml @@ -0,0 +1,14 @@ + + + Vyberte si svojho XMPP poskytovateľa + Použiť conversations.im + Vytvoriť nové konto + Máte už svoje XMPP konto? Môže to tak byť v prípade, že už používate iného klienta XMPP alebo ste predtým používali Conversations. Ak nie, môžete si vytvoriť nové XMPP konto práve teraz.\nHint: Niektorí poskytovatelia emailu zároveň poskytujú aj XMPP kontá. + XMPP je sieť pre okamžité správy nezávislá od poskytovateľa. Tohto klienta môžete používať s akýmkoľvek XMPP serverom, ktorý si vyberiete..\nAvšak pre vaše pohodlie sme zjednodušili vytvorenie konta na conversations.im¹; poskytovateľ špeciálne vhodný na používanie s Conversations. + Boli ste pozvaný do %1$s. Prevedieme vás procesom vytvorenia konta..\nPo výbere %1$s ako poskytovateľa, budete môcť komunikovať s užívateľmi iných poskytovateľov tak, že im dáte vašu úplnú XMPP adresu. + Boli ste pozvaný do %1$s . Užívateľské meno vám už bolo vopred vybrané. Prevedieme vás procesom vytvorenia konta..\nBudete môcť komunikovať s užívateľmi iných poskytovateľov tak, že im dáte vašu úplnú XMPP adresu. + Ťuknite na tlačidlo zdieľať na odoslanie pozvánky do %1$s vášmu kontaktu. + Ak je váš kontakt blízko, na prijatie vašej pozvánky si môže nasnímať kód nižšie. + Pripojte sa k %1$sa rozprávajte sa so mnou: %2$s + Zdieľať pozvánku s... + \ No newline at end of file diff --git a/src/conversations/res/values-sv/strings.xml b/src/conversations/res/values-sv/strings.xml index 9212ad109e6208468e8615436a48a240778c0803..e898b56434324ef4b5a1d41cbddbcd3c7cd05fb3 100644 --- a/src/conversations/res/values-sv/strings.xml +++ b/src/conversations/res/values-sv/strings.xml @@ -1,5 +1,8 @@ + Välj din XMPP leverantör Använd conversations.im Skapa nytt konto - \ No newline at end of file + Din server inbjudan + Dela inbjudan med... + \ 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 4dd5d52482e389c51363109adcfd6f8f0c4bc113..197b23a2657801549a9ca3190332e0c6244cb3b5 100644 --- a/src/main/res/values-ar/strings.xml +++ b/src/main/res/values-ar/strings.xml @@ -4,6 +4,7 @@ محادثة جديدة إدارة الحسابات إدارة الحساب + أغلق المحادثة بيانات جهة الإتصال تفاصيل مجموعة المحادثة تفاصيل القناة @@ -27,7 +28,7 @@ قائمة المحجوبين الآن منذ 1 دقيقة - دقائق %d منذ + منذ %d دقائق ارسال حل شيفرة الرسالة. الرجاء الإنتظار ... رسالة مشفرة عبر OpenPGP @@ -38,12 +39,14 @@ مشرف مشترك زائر + هل تريد حذف %sمن قائمة إتصالك؟ المحادثات مع هذا الشخص لن تحذف. هل ترغب في حجب %s من ارسال الرسائل لك? هل ترغب في انهاء حجب %s والسماح له بمراسلتك? هل تريد حجب جميع جهات الإتصال من %s? الغاء حجب جميع جهات الإتصال من %s? جهة الاتصال محجوبه محجوب + هل تريد حذف %sمن قائمة المفضلة؟ المحادثات مع هذا المفضل لن تحذف. تسجيل حساب جديد في سيرفر تغيير كلمة المرور في سيرفر مشاركة مع @@ -61,11 +64,16 @@ الغاء حجب حفظ موافق + %1$sتعطّل ارسال الآن لا تسألني ثانية + لا يمكن الإتصال بالحساب + لايمكن الإتصال بحسابات متعددة ارفاق ملف اضافة جهة اتصال فشل التسليم + الإستعداد لإرسال الصورة + الإستعداد لإرسال الصور جاري إرسال الملفات. الرجاء الإنتظار ... حذف سجل المحفوظات حذف سجل المحفوظات للمحادثة @@ -77,6 +85,7 @@ إرسال رسالة مشفرة عبر OMEMO إبعث رسالة مشفَّرة بـ أومي مو OMEMO إرسال رسالة مشفرة عبر OpenPGP + إسم مستخدم جديد تحت الإستعمال إرسال بدون تشفير فشل فك التشفير. ربما ليس لديك المفتاح الخاص الصحيح. OpenKeychain @@ -96,6 +105,7 @@ إهتز عند وصول رسالة جديدة إشعار ضوئي التنبيه الصوتي + تنبيه صوتي فترة السماح متقدم لا ترسل تقارير أخطاء @@ -112,6 +122,7 @@ اختيار صورة التقاط صورة الملف الذي حددته ليس صورة + لا يمكن تحويل ملف الصورة الملف غير موجود غير معروف معطلٌ موقتاً @@ -145,6 +156,7 @@ احجب عنوان XMPP username@example.com كلمة السر + الذاكرة مليئة، صورة كبيرة جدا هل تود إضافة %s إلى سجل عناوينك ؟ معلومات عن المضيف XEP-0313: إدارة أرشيف الرسائل @@ -159,6 +171,7 @@ متاح غير متاح آخر ظهور الآن + آخر مشاهدة منذ دقيقة آخر ظهور منذ %d دقيقة آخر ظهور منذ %d ساعة آخر ظهور منذ %d يوم @@ -642,4 +655,5 @@ افتح النسخة الاحتياطية يرجى إدخال الكلمة السرية للحساب مشغول + خيارات أخرى diff --git a/src/main/res/values-bn-rIN/strings.xml b/src/main/res/values-bn-rIN/strings.xml index cd8dbf1871f41f7bc586482ee370578fe58f996c..bd426b737a968a48aa4ec5f7391761337bbeb759 100644 --- a/src/main/res/values-bn-rIN/strings.xml +++ b/src/main/res/values-bn-rIN/strings.xml @@ -46,6 +46,9 @@ নির্ধারক অংশগ্রহণকারী অতিথি + আপনি কি আপনার পরিচিতি তালিকা থেকে %s-কে অপসারণ করতে চান? এই যোগাযোগের সাথে কথোপকথনগুলি সরানো হবে না। + %s-কে বার্তা পাঠানো থেকে ব্লক করতে চান? + আপনি কি %s-কে আনব্লক করতে চান এবং তাদের আপনাকে বার্তা পাঠানোর অনুমতি দিতে চান? ব্যক্তিটিকে ব্লক্ করা হয়েছে ব্লক্ করা আছে সার্ভারে একটি নতুন অ্যকাউন্ট খোলা যাক @@ -90,8 +93,26 @@ OMEMO সাঙ্কেতিক বার্তা পাঠানো হোক v\\OMEMO সাঙ্কেতিক বার্তা পাঠানো হোক OpenPGP সাঙ্কেতিক বার্তা পাঠানো হোক + নতুন নাম ব্যবহার করা হচ্ছে + এনক্রিপ্ট না করেই পাঠানো হোক + ডিক্রিপ্ট করা যায়নি। হয়তো আপনার কাছে সঠিক Private Key নেই। + রিস্টার্ট্ + ইনস্টল্ + OpenKeychain ইনস্টল্ করতে হবে + প্রস্তাব দেওয়া হচ্ছে... + অপেক্ষা করা হচ্ছে... + কোনো OpenPGP Key খুঁজে পাওয়া যায়নি + বুকমার্ক করা যেগুলি + খোঁজা যাক এই ব্যক্তিকে ব্লক্ করা যাক ব্লকটা সরিয়ে ফেলা যাক পরিচিত ব্যক্তি না, থাক। + পরিচিত ব্যক্তিদের মধ্যে খোঁজা যাক + বার্তাগুলির মধ্যে খোঁজা যাক + সরাসরিভাবেই খোঁজা যাক + পাবলিক চ্যানেলে যোগ দেওয়া যাক + ব্যক্তিগত গ্রুপ চ্যাট তৈরি করুন + পাবলিক চ্যানেল তৈরি করা যাক + বর্তমান চ্যানেলগুলির মধ্যে থেকে খোঁজা যাক diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 684632474eb62d9794c919e1e11194a7700a523a..d0ddfa60471dde0e8ef8d4fa309e95b6aeb5ea6d 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -156,6 +156,7 @@ Soubor nenalezen Obecná I/O chyba. Že by již nebylo volné místo? Aplikace, kterou jste použil(a) k výběru obrázku, neposkytla dostatečná oprávnění ke čtení souboru.\n\nPoužijte jiného správce souborů k výběru obrázku. + Aplikace kterou jste použili pro nasdílení tohoto souboru nemá dostatečná oprávnění. Neznámý Dočasně vypnuto Online @@ -170,6 +171,7 @@ Registrace není podporována serverem Chybný registrační token Vyjednávání TLS selhalo + Doménu nelze ověřit Porušení podmínek Nekompatibilní server Chyba přenosu @@ -406,6 +408,7 @@ Nebylo možné změnit nastavení skupinového chatu Nikdy Než opět změním + Posunout Odpovědět Označit jako přečtené Vstup @@ -434,6 +437,8 @@ Nebyla nalezena aplikace pro zobrazení pozice Pozice Conversation zavřena + Opustil(a) soukromý skupinový chat + Opustil(a) veřejný kanál Nedůvěřovat systémovým CA Všechny certifikáty musí být schváleny ručně Odstranit certifikáty @@ -466,6 +471,7 @@ Stažení selhalo: Nelze zapsat soubor Tor síť není dostupná Bind chyba + Server není zodpovědný za tuto doménu Rozbité Dostupnost Pryč při uzamčení zařízení @@ -495,6 +501,7 @@ Vedení všech připojení po Tor síti vyžaduje aplikaci Orbot Hostname Port + Server nebo .onion adresa Toto není platné číslo portu Toto není platné hostname %1$d z %2$d účtů připojeno @@ -533,6 +540,7 @@ Odeslat opravenou zprávu Tento osobní otisk byl již bezpečně ověřen. Ťuknutím na \"Hotovo\" pouze potvrzujete, že %s je členem tohoto skupinového chatu. Tento účet byl vypnut + Bezpečnostní chyba: Neplatný přístup k souboru Nebyla nalezena aplikace umožňující sdílení URI Sdílet URI s…
Po zadání Vašeho telefonního čísla Vám Quicksy automaticky—na základě čísel ve Vašem telefonním seznamu—navrhne možné kontakty.

Přihlášením se do služby potvrzujete souhlas s našimi zásadami pro ochranu osobních údajů.]]>
@@ -744,6 +752,7 @@ Použít Plugin pro sdílení pozice namísto interní mapy Kopírovat webovou adresu Kopírovat XMPP adresu + HTTP sdílení souborů pro S3 Přímé vyhledávání Na úvodní obrazovce otevřít klávesnici a umístit kurzor do vyhledávacího pole Avatar skupinového chatu @@ -883,6 +892,7 @@ Toto vypadá jako adresa kanálu Sdílet soubory zálohy Záloha Conversations + Událost Otevřít zálohu Soubor, který jste zvolili, není soubor zálohy Conversations Tento účet byl již nastaven @@ -932,6 +942,7 @@ Nebylo možné přepnout kameru Připnout nahoru Odepnout shora + GPX trasa Nebylo možné opravit zprávu Všechny konverzace Tato konverzace @@ -961,7 +972,9 @@ Více možností Nenalezena žádná aplikace Pozvat do Conversations + Nelze načíst pozvánku Server nepodporuje vytváření pozvánek Žádný z aktivních účtů tuto funkci nepodporuje Zálohování zahájeno. Budete upozorněni, jakmile bude záloha hotova. + Nelze povolit video. diff --git a/src/main/res/values-da-rDK/strings.xml b/src/main/res/values-da-rDK/strings.xml index 9129da103004f4cef8f296a155f24a80fa950b5e..bbcd082f012f82265ef57b282c04e76c3fe28f48 100644 --- a/src/main/res/values-da-rDK/strings.xml +++ b/src/main/res/values-da-rDK/strings.xml @@ -150,6 +150,7 @@ Fil ikke fundet General I/O fejl. Måske er du kørt tør for lagerplads? Appen du brugte til at vælge dette billede havde ikke tilstrækkelig tilladelse til at læse filen.\n\nBrug en anden filmanager til at vælge et billede. + Appen du brugte til at dele denne fil har ikke givet nok tilladelser. Ukendt Midlertidigt deaktiveret Online @@ -164,6 +165,7 @@ Registrering er ikke understøttet af server Ugyldig registreringstoken TLS forhandling mislykkedes + Domæne kan ikke verificeres Brud på retningslinjer Inkompatibel server Strømfejl @@ -960,4 +962,5 @@ Server understøtter ikke generering af invitationer Ingen aktive konti understøtter denne funktion Sikkerhedskopieringen er startet. Du får en notifikation, når den er afsluttet. + Kunne ikke aktivere video. diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index fe9d6d6428ec304da65be9a4594feb9dbd38ec7c..782b5825fa3a85e1073934a187e4eec73471731b 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -76,7 +76,7 @@ Jetzt abschicken Nie mehr nachfragen Verbindung zum Konto konnte nicht hergestellt werden - Verbindung zu mehreren Konto konnte nicht hergestellt werden + Verbindung zu mehreren Konten konnte nicht hergestellt werden Antippen, um deine Konten zu verwalten Datei auswählen Diesen fehlenden Kontakt zu deiner Kontaktliste hinzufügen? @@ -371,7 +371,7 @@ Aktuelles Passwort Neues Passwort Passwort kann nicht leer sein - Alle Konten aktiveren + Alle Konten aktivieren Alle Konten abschalten Aktion durchführen mit Keine Zugehörigkeit @@ -963,4 +963,4 @@ Keine aktiven Konten unterstützen diese Funktion Das Backup wurde gestartet. Du bekommst eine Benachrichtigung sobald es fertig ist. Video kann nicht aktiviert werden. - + diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml index 57527772c66d585389550f14504895fd83261c0f..16a2a8742540ffc61d74c4a7377cbd7baadd6a4c 100644 --- a/src/main/res/values-el/strings.xml +++ b/src/main/res/values-el/strings.xml @@ -53,7 +53,7 @@ Άρση αποκλεισμού όλων των επαφών από το %s; Η επαφή αποκλείστηκε Αποκλεισμένος - Θέλετε να αφαιρέσετε το %s ως σελιδοδείκτη; Οι συζητήσεις που σχετίζονται με αυτόν τον σελιδοδείκτη δεν θα αφαιρεθούν. + Θέλετε να αφαιρέσετε το %s από σελιδοδείκτη; Οι συζητήσεις που σχετίζονται με αυτόν τον σελιδοδείκτη δεν θα αφαιρεθούν. Εγγραφή νέου λογαριασμού στον διακομιστή Αλλαγή συνθηματικού στον διακομιστή Διαμοιρασμός με... @@ -89,7 +89,7 @@ Καθαρισμός ιστορικού Συζήτησης Θέλετε να διαγράψετε όλα τα μηνύματα αυτής της συζήτησης;\n\nΠροσοχή: Αυτή η ενέργεια δεν θα επηρεάσει μηνύματα που είναι αποθηκευμένα σε άλλες συσκευές ή εξυπηρετητές. Διαγραφή αρχείου - Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το αρχείο;\n\nΠροσοχή Αυτή η ενέργεια δεν θα διαγράψει αντίγραφα αυτού του αρχείου που είναι αποθηκευμένα σε άλλες συσκευές ή εξυπηρετητές. + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το αρχείο;\n\nΠροσοχή: Αυτή η ενέργεια δεν θα διαγράψει αντίγραφα αυτού του αρχείου που είναι αποθηκευμένα σε άλλες συσκευές ή εξυπηρετητές. Κλείσιμο της συζήτησης αμέσως μετά Επιλογή συσκευής Αποστολή μη κρυπτογραφημένου μηνύματος @@ -114,7 +114,7 @@ Δεν ήταν δυνατή η κρυπτογράφηση του μηνύματός σας γιατί οι επαφές σας δεν ανακοινώνουν το δημόσιο κλειδί τους.\n\nΠαρακαλώ ζητήστε από τις επαφές σας να εγκαταστήσουν το OpenPGP. Γενικά Αποδοχή αρχείων - Αυτόματη αποδοχή αρχείων μικρότερα από... + Αυτόματη αποδοχή αρχείων μικρότερων από... Συνημμένα Ειδοποίηση Δόνηση @@ -124,6 +124,7 @@ Κουδούνισμα Ήχος ειδοποίησης Ήχος ειδοποίησης για νέα μηνύματα + Ήχος κουδουνίσματος για εισερχόμενες κλήσεις Περίοδος Χάριτος Ο χρόνος σίγασης ειδοποιήσεων αφότου ανιχνευθεί δραστηριότητα σε μια από τις άλλες συσκευές σας. Για προχωρημένους @@ -133,7 +134,7 @@ Επιτρέψτε στις επαφές σας να γνωρίζουν όταν έχετε λάβει και διαβάσει τα μηνύματά τους Διεπαφή χρήστη Το OpenKeychain ανέφερε κάποιο σφάλμα. - Σφάλμα στο κλειδί κρυπτογράφησης + Σφάλμα στο κλειδί κρυπτογράφησης. Αποδοχή Έχει συμβεί κάποιο σφάλμα Σφάλμα @@ -149,6 +150,7 @@ Το αρχείο δεν βρέθηκε Γενικό σφάλμα εισόδου/εξόδου. Ίσως δεν έχετε ελεύθερο χώρο αποθήκευσης; Η εφαρμογή που χρησιμοποιήσατε για να επιλέξετε αυτή την εικόνα δεν παραχώρησε αρκετά δικαιώματα για την ανάγνωση του αρχείου.\n\nΧρησιμοποιήστε διαφορετικό διαχειριστή αρχείων για να επιλέξετε μια εικόνα + Η εφαρμογή που χρησιμοποιήσατε για να διαμοιραστείτε αυτό το αρχείο δεν παρείχε αρκετά δικαιώματα. Άγνωστο Προσωρινά απενεργοποιημένο Σε σύνδεση @@ -163,6 +165,7 @@ Ο διακομιστής δεν υποστηρίζει εγγραφή Άκυρο κουπόνι εγγραφής Αποτυχία διαπραγμάτευσης TLS + Ο τομέας δεν είναι επαληθεύσιμος Παραβίαση κανονισμού Μη συμβατός διακομιστής Σφάλμα μετάδοσης @@ -204,11 +207,11 @@ μη διαθέσιμος Ελλειπείς ανακοινώσεις δημοσίων κλειδιών συνδέθηκε τελευταία φορά μόλις τώρα - τελευταία σύνδεση πριν από 1 λεπτό + τελευταία σύνδεση πριν από ένα λεπτό τελευταία σύνδεση πριν από %d λεπτά - τελευταία σύνδεση πριν από 1 ώρα + τελευταία σύνδεση πριν από μία ώρα τελευταία σύνδεση πριν από %d ώρες - τελευταία σύνδεση πριν από 1 μέρα + τελευταία σύνδεση πριν από μία μέρα τελευταία σύνδεση πριν από %d μέρες Κρυπτογραφημένο μήνυμα. Παρακαλώ εγκαταστήστε το OpenKeychain για αποκρυπτογράφηση. Βρέθηκαν νέα μηνύματα κρυπτογραφημένα με OpenPGP @@ -217,7 +220,7 @@ v\\Αποτύπωμα OMEMO Αποτύπωμα OMEMO (πηγή μηνύματος) v\\Αποτύπωμα OMEMO (πηγή μηνύματος) - \'Αλλες συσκευές + Άλλες συσκευές Επαλήθευση των αποτυπωμάτων OMEMO Μεταφόρτωση κλειδιών... Έγινε @@ -327,7 +330,7 @@ Δημιουργία αντιγράφων ασφαλείας Το αντίγραφο ασφαλείας σας έχει δημιουργηθεί Τα αρχεία του αντιγράφου ασφαλείας έχουν αποθηκευτεί στο %s - Επαναφορά αντιγράφου ασφαλείας + Γίνεται επαναφορά αντιγράφου ασφαλείας Έχει γίνει επαναφορά του αντιγράφου ασφαλείας σας Μην παραλείψετε να ενεργοποιήσετε τον λογαριασμό. Επιλογή αρχείου @@ -338,7 +341,7 @@ Άνοιγμα του %s αποστολή (ολοκλήρωση %1$d%%) Προετοιμασία του αρχείου για διαμοιρασμό - %s προσφέρθηκε για μεταφόρτωση + Το %s προσφέρθηκε για μεταφόρτωση Ακύρωση μετάδοσης ο διαμοιρασμός του αρχείου απέτυχε η μεταφορά αρχείου ακυρώθηκε @@ -418,10 +421,10 @@ Αποστολή του %s Προσφορά του %s Απόκρυψη των εκτός σύνδεσης - Η επαφή %s πληκτρολογεί... - Η επαφή %s σταμάτησε να πληκτρολογεί - Οι επαφές %s πληκτρολογούν... - Η επαφές %s σταμάτησαν να πληκτρολογούν + Ο/Η %s πληκτρολογεί... + Ο/Η %s σταμάτησε να πληκτρολογεί + Οι %s πληκτρολογούν... + Οι %s σταμάτησαν να πληκτρολογούν Ειδοποιήσεις πληκτρολόγησης Επιτρέψτε στις επαφές σας να γνωρίζουν πότε γράφετε μηνύματα προς αυτές Αποστολή τοποθεσίας @@ -451,7 +454,7 @@ Αναζήτηση επαφών Αναζήτηση σελιδοδεικτών Αποστολή ιδιωτικού μηνύματος - Η επαφή %1$s αποχώρησε από την ομαδική συζήτηση + Ο/Η %1$s αποχώρησε από την ομαδική συζήτηση Όνομα χρήστη Όνομα χρήστη Αυτό δεν είναι έγκυρο όνομα χρήστη @@ -464,6 +467,8 @@ Ο διακομιστής δεν είναι υπεύθυνος για αυτόν τον τομέα Χαλασμένος Διαθεσιμότητα + Εκτός χρήσης όταν η οθόνη είναι κλειδωμένη + Εμφάνιση παρουσίας ως εκτός χρήσης όταν η συσκευή κλειδώνεται Απασχολημένος/η όταν βρίσκεται σε σιωπηρή λειτουργία Σημειώνει την παρουσία σας ως Απασχολημένος/η όταν η συσκευή είναι σε κατάσταση σιωπής Χρήση της κατάστασης δόνησης ως σιωπηρή κατάσταση @@ -501,7 +506,7 @@ Φόρτωση περισσότερων μηνυμάτων Το αρχείο διαμοιράστηκε με την επαφή %s Η εικόνα διαμοιράστηκε με την επαφή %s - Η εικόνες διαμοιράστηκαν με την επαφή %s + Οι εικόνες διαμοιράστηκαν με την επαφή %s Το κείμενο διαμοιράστηκε με την επαφή %s Απόδοση δικαιώματος στο %1$s για πρόσβαση στον εξωτερικό αποθηκευτικό χώρο Απόδοση δικαιώματος στο %1$s για πρόσβαση στην φωτογραφική μηχανή @@ -577,7 +582,7 @@ Απόδοση δικαιώματος χρήσης Internet Εγώ Η επαφή ζητά συνδρομή σε υπηρεσία παρουσίας - Επιτρέπω + Να επιτραπεί Δεν υπάρχει δικαίωμα για πρόσβαση στο %s Δεν βρέθηκε ο απομακρυσμένος διακομιστής Λήξη χρόνου για τον απομακρυσμένο διακομιστή @@ -833,7 +838,7 @@ Μην χρησιμοποιείτε τη λειτουργία επαναφοράς αντιγράφων ασφαλείας για να κλωνοποιήσετε (ταυτόχρονη εκτέλεση) μια εγκατάσταση. Η επαναφορά αντιγράφου ασφαλείας προσφέρεται μόνο για μεταφορές ή σε περίπτωση που έχετε χάσει την αρχική συσκευή. Αδυναμία επαναφοράς αντιγράφου ασφαλείας. Αδυναμία αποκρυπτογράφησης του αντιγράφου ασφαλείας. Είναι ο κωδικός σωστός; - Δημιουργία & Επαναφορά αντιγράφων ασφαλείας + Δημιουργία & Επαναφορά Εισάγετε τη διεύθυνση XMPP Δημιουργία ομαδικής συζήτησης Είσοδος σε δημόσιο κανάλι @@ -893,8 +898,8 @@ Παρακαλώ ενεργοποιήστε έναν λογαριασμό Νέα κλήση Εισερχόμενη κλήση - Εισερχόμενη κλήση βίντεο - Σύνδεση + Εισερχόμενη βιντεοκλήση + Γίνεται σύνδεση Συνδέθηκε Αποδοχή κλήσης Τερματισμός κλήσης @@ -909,7 +914,7 @@ Αποτυχία εφαρμογής Τερματισμός κλήσης Κλήση σε εξέλιξη - Κλήση βίντεο σε εξέλιξη + Βιντεοκλήση σε εξέλιξη Απενεργοποίηστε το Tor για να κάνετε κλήσεις Εισερχόμενη κλήση Εισερχόμενη κλήση · %s @@ -918,7 +923,7 @@ Εξερχόμενη κλήση · %s Αναπάντηση κλήση Κλήση ήχου - Κλήση βίντεο + Βιντεοκλήση Βοήθεια Εναλλαγή στη συζήτηση Το μικρόφωνο δεν είναι διαθέσιμο @@ -949,11 +954,13 @@ Κάποιο μήνυμα δεν ήταν δυνατό να παραδοθεί Κάποια μηνύματα δεν ήταν δυνατό να παραδοθούν - Αποτυχημένες διανομές + Αποτυχημένες παραδόσεις Περισσότερες επιλογές Δεν βρέθηκε εφαρμογή Πρόσκληση στο Conversations Αδυναμία ανάγνωσης πρόσκλησης Ο διακομιστής δεν υποστηρίζει την δημιουργία προσκλήσεων Κανένας από τους ενεργούς λογαριασμούς δεν υποστηρίζει αυτό το χαρακτηριστικό + Το αντίγραφο ασφαλείας δημιουργείται. Θα λάβετε ειδοποίηση όταν ολοκληρωθεί. + Αδυναμία ενεργοποίησης βίντεο. diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 5be2d0e909ef4a456a671e325dec5e505c677382..1383e8b9821c0434ed507497e72da9b6c370a975 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -124,6 +124,7 @@ Tono de llamada Sonido de notificación Sonido de notificación para nuevos mensajes + Tono para las nuevas llamadas Periodo de gracia El periodo de tiempo en el que las notificaciones están silenciadas tras detectar actividad en otro de tus dispositivos. Avanzado @@ -149,6 +150,7 @@ Archivo no encontrado Error general. ¿Es posible que no tengas espacio en disco? La aplicación usaste para seleccionar esta imagen no proporcionó suficientes permisos para leer el archivo.\n\nUtiliza un explorador de archivos diferente para seleccionar la imagen + La aplicación que has utilizado para compartir este archivo no presentó permisos suficientes Desconocido Deshabilitado temporalmente Conectado @@ -163,6 +165,7 @@ El servidor no soporta registros Token de registro inválido Error de negociación TLS + Dominio no verificable Policy violation Servidor incompatible Error de flujo @@ -215,6 +218,8 @@ OpenPGP Key ID Huella digital OMEMO Huella digital v\\OMEMO + Huella digital OMEMO (origen del mensaje) + Huella digital v\\OMEMO (origen del mensaje) Otros dispositivos Huellas digitales OMEMO de confianza Buscando claves... @@ -462,6 +467,8 @@ El servidor no es responsable de este dominio Error Disponibilidad + Ausente cuando el dispositivo esté bloqueado + Mostrar como Ausente cuando el dispositivo esté bloqueado Ocupado en modo silencio Mostrar como Ocupado cuando el dispositivo esté en modo silencio Modo vibración como modo silencio @@ -954,4 +961,6 @@ No se ha podido leer la invitación El servidor no soporta la creación de invitaciones Ninguna cuenta activa soporta esta característica + La copia de seguridad ha empezado. Recibirás una notificación cuando se haya completado. + No se ha podido habilitar el vídeo. diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index cb8669df9f08c08d228a8341f8aaad15ae01978b..256baacab56fdad1fd7e25b3acc26e201f447fbe 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -124,6 +124,7 @@ Sonnerie Son des notifications Son de notification pour les nouveaux messages + Sonnerie d\'appel entrant Période sans notification La durée pendant laquelle les notifications sont désactivées après la détection d\'une activité sur l\'un de vos autres appareils. Avancé @@ -149,6 +150,7 @@ Impossible de trouver le fichier Erreur générale d\'E/S. Avez-vous encore de l\'espace libre ? L\'application utilisée ne donne pas la permission de lire l\'image.\n\nUtilisez une autre application pour choisir une image. + L\'app avec laquelle vous avez partagé ce fichier n\'a pas fourni assez de permissions. Inconnu Désactivé temporairement En ligne @@ -163,6 +165,7 @@ Inscription non supportée par le serveur Jeton d’inscription invalide La négociation TLS a échoué + Domaine non vérifiable Violation de politique Serveur incompatible Erreur de flux @@ -215,6 +218,7 @@ ID de clé OpenPGP Empreinte OMEMO v\\Empreinte OMEMO + Empreinte OMEMO (origine du message) Autres appareils Faire confiance aux empreintes OMEMO Récupération des clés… @@ -462,6 +466,7 @@ Le serveur n\'est pas responsable pour ce domaine Détraqué Disponibilité + Absent quand l\'appareil est verrouillé Occupé en mode silence Occupé lorsque l\'appareil est en mode silencieux Indisponible en mode vibreur diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index f7c151661a7d5957e036f7493bad6db1e2be52ae..2baea5f1699e358b95851cbe86e2c90e63e76a77 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -85,12 +85,12 @@ Preparándose para enviar a imaxe Preparándose para enviar imaxes Compartindo ficheiros. Por favor agarde... - Limpar historial - Limpar historial de conversa + Baleirar historial + Eliminar historial da conversa ¿Queres eliminar as mensaxes desta conversa?\n\nAviso: Esto non lle afecta as mensaxes gardadas noutros dispositivos ou servidores. Eliminar ficheiro Está segura de querer eliminar este ficheiro?\n\nAviso: Esto non eliminará as copias de este ficheiro que están gardadas en outros dispositivos ou servidores. - Pechar esta conversa a posteriori + Pechar a conversa tras baleirar Escoller dispositivo Enviar mensaxe non cifrada Enviar mensaxe @@ -144,7 +144,7 @@ Solicitar actualizacións de presenza Seleccionar imaxe Facer foto - Por defecto otorgar peticiones de suscripción + Por defecto conceder solicitudes de subscrición O arquivo seleccionado non é unha imaxe Non se puido converter o ficheiro de imaxe Arquivo non atopado @@ -252,7 +252,7 @@ Saír Contacto engadido a túa lista de contactos Volver a engadir - %s leeu ate este punto + %s leu ata este punto %s leu ate este punto %1$s + %2$d outras leron ata este punto Todas leron ate este punto @@ -349,8 +349,8 @@ Non se atopou unha app para abrir o ficheiro Non se atopou app para abrir a ligazón Non se atopou app para ver o contacto - Etiquetas dinámicas - Mostrar etiquetas de só lectura baixo os contactos + Información do estado + Mostra o estado debaixo do nome do contacto Habilitar notificacións Non se atopou ningún servidor de conversa en grupo Non se puido crear a conversa en grupo @@ -614,11 +614,11 @@ Non confiables Código de barras 2D non válido Baleirar o cartafol da caché (utilizado pola cámara) - Limpar caché + Baleirar caché Baleirar almacenaxe privada Baleirar a almacenaxe privada onde se gardan os ficheiros (poderán volver a descargarse desde o servidor) Seguín esta ligazón desde unha fonte de confianza - Vai verificar as chaves OMEMO de %1$s despois de pulsar na ligazón. Esto só é seguro si sigueu esta ligazón desde unha fonte de confianza onde só %2$s a podería ter publicado. + Vas verificar as chaves OMEMO de %1$s despois de premer na ligazón. Esto só é seguro se seguiches esta ligazón desde unha fonte de confianza onde só %2$s a podería ter publicado. Validar chaves OMEMO Mostrar inactivos Amagar inactivos @@ -707,8 +707,8 @@ OMEMO utilizarase por defecto para as novas conversas. OMEMO terá que ser activado explícitamente para novas conversacións. Crear acceso directo - Tamaño da fonte - O tamaño de fonte relativo a esta app + Tamaño da letra + O tamaño relativo da letra que utiliza a app. Activado por defecto Desactivado por defecto Pequena @@ -963,4 +963,4 @@ Ningunha conta activa soporta esta función Comezou a creación da copia de apoio. Recibirás unha notificación cando remate. Non se puido activar o vídeo. - + diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 5fc619622fe577e6bafbfde01afb7eea1a703eb4..9271b113dba5be836c6c105b61d146fe14da59bc 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -150,6 +150,7 @@ File non trovato Errore di I/O generico. Forse hai esaurito lo spazio? L’app che hai usato per selezionare questa immagine non ha fornito autorizzazioni sufficienti per leggere il file.\n\nUsa un gestore di file differente per scegliere un’immagine + L\'app che hai usato per condividere questo file non ha fornito autorizzazioni sufficienti. Sconosciuto Disattivato temporaneamente Online @@ -164,6 +165,7 @@ Registrazione non supportata dal server Token di registrazione non valido Negoziazione TLS fallita + Dominio non verificabile Violazione della policy Server non compatibile Errore di stream @@ -960,4 +962,5 @@ Il server non supporta la generazione di inviti Nessun account attivo supporta questa funzione Il backup è iniziato. Riceverai una notifica una volta completato. + Impossibile attivare il video. diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 83b981881341d77136cce1564915feb496c222fd..f1a3daa2f5966c3a61c6ff0e762492b2a36c67ab 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -27,8 +27,8 @@ アカウントで共有 ブロック一覧 ちょうど今 - 1 分前 - %d 分前 + 1分前 + %d分前 未読%d件 @@ -40,7 +40,7 @@ 正しくないニックネームです 管理者 所有者 - モデレーター + 調停者 参加者 訪問者 連絡先名簿から %s を削除しますか? この連絡先との会話は削除されません。 @@ -147,6 +147,7 @@ ファイルが見つかりません 一般的な I/O エラー。おそらく空き容量がなくなっていませんか? あなたが画像の選択のために使用したアプリは、読み取りに必要なアクセス権がありません。\n\n別のファイルマネージャを使用して、画像を選択してください。 + このファイルを共有するために使用したアプリに、十分な許可が与えられていませんでした。 不明 一時的に無効 オンライン @@ -161,6 +162,7 @@ サーバーが登録をサポートしていません トークンが無効です TLS ネゴシエーションに失敗しました + 検証不可能なドメイン ポリシー違反 互換性のないサーバー ストリーム エラー @@ -189,7 +191,7 @@ %s をお使いのアドレス帳に追加しますか? サーバー情報 XEP-0313: メッセージ アーカイブ管理 - XEP-0280: メッセージ カーボン + XEP-0280: メッセージ複写 XEP-0352: クライアント状態表示 XEP-0191: ブロッキング コマンド XEP-0237: 名簿バージョニング @@ -202,12 +204,12 @@ 利用不可 公開鍵の告知がありません ちょうど今会いました - 1 分前に会いました - %d 分前に会いました - 1 時間前に会いました - %d 時間前に会いました - 1 日前に会いました - %d 日前に会いました + 1分前に会いました + %d分前に会いました + 1時間前に会いました + %d時間前に会いました + 1日前に会いました + %d日前に会いました 暗号化されたメッセージです。復号するには OpenKeychain をインストールしてください。 新しい OpenPGP 暗号化されたメッセージが見つかりました OpenPGP 鍵 ID @@ -298,8 +300,8 @@ %s 上でホストされた HTTP ホストの %s を確認中 接続されていません。後でもう一度お試しください - %s サイズを確認 - %2$s で %1$s のサイズを確認 + %s の大きさを確認 + %2$s で %1$s の大きさを確認 メッセージオプション 引用 引用として貼り付け @@ -310,8 +312,8 @@ XMPP アドレスをクリップボードにコピーしました エラーメッセージをクリップボードにコピーしました ウェブアドレス - 2D バーコードをスキャン - 2D バーコードを表示 + 二次元バーコードをスキャン + 二次元バーコードを表示 ブロック一覧を表示 アカウントの詳細 確認 @@ -494,12 +496,12 @@ %d メッセージ - さらにメッセージをロード + さらにメッセージを読み込む %s でファイル共有 %s で画像共有 %s で画像共有 %s でテキスト共有 - %1$s に外部ストレージへのアクセス権を付与 + %1$s に外部ストレージへのアクセス権を付与してください %1$s にカメラへのアクセス権を付与 連絡先と同期 %1$s はあなたのアドレス帳にアクセスして、あなたのXMPP 連絡先名簿と照合する権限を求めています。\nこれにより、連絡先のフルネームとアバターが表示されます。\n\n%1$s は、あなたのサーバーに何かをアップロードすることなく、あなたのアドレス帳を読み込んでローカルに照合するだけです。 @@ -528,7 +530,7 @@ …で URI を共有
電話番号を入力して登録すると、アドレス帳に登録されている電話番号をもとに、Quicksyが自動的に連絡先を提案します。

登録すると、我々のプライバシーポリシーに同意することになります。]]>
同意して続行 - conversations.im のアカウント作成のための指南が設定されています。¹\nconversations.im をプロバイダーとして選択した場合、あなたの完全なXMPPアドレスを与えることで、他のプロバイダーのユーザーと連絡をとることができます。 + conversations.im 上にアカウントを作成する設定の指南です。¹\nconversations.im をプロバイダーとして選択した場合、あなたの完全な XMPP アドレスを他のプロバイダーのユーザーに示すことで、その人と連絡をとることができます。 あなたの完全なXMPPアドレスは: %s アカウントを作成 独自のプロバイダーを使用する @@ -538,8 +540,8 @@ ステータスメッセージ いつでもチャットできます オンライン - 離席中 - 利用不可 + 離席 + 不在 取込中 安全なパスワードが生成されました お使いのデバイスは電池最適化の停止をサポートしていません @@ -553,13 +555,13 @@ ブロードキャストを使用 - Conversations を使用するとき、連絡先に知らせましょう + Conversations を使用するときに、連絡先に知らせましょう プライバシー テーマ カラーパレットの選択 自動 - ライト - ダーク + + 緑の背景 受信したメッセージに緑の背景を使用します OpenKeychain に接続できません @@ -603,11 +605,11 @@ 認証されていない連絡先からの新規デバイスを信頼するが、認証されている連絡先からの新規デバイスについては手動での確認を求める。 OMEMO 鍵を盲目的に信用していた。つまり、他の人かもしれないし、誰かが盗聴しているかもしれない。 信頼されていない - 不正な 2D バーコード - キャッシュフォルダーをクリアします (カメラアプリで使用) - キャッシュをクリア - プライベートストレージをクリア - ファイルが保存されているプライベートストレージをクリアします (サーバーから再ダウンロードできます) + 不正な二次元バーコード + キャッシュフォルダを消去します (カメラアプリで使用) + キャッシュを消去 + プライベートストレージを消去 + ファイルが保存されているプライベートストレージを消去します (サーバーから再ダウンロードできます) 信頼できるソースからこのリンクをたどりました リンクをクリックした後、%1$s の OMEMO 鍵を検証しようとしています。 これは、%2$s がこのリンクを公開した、信頼できるソースからこのリンクをたどった場合にのみ安全です。 OMEMO 鍵を検証 @@ -693,7 +695,7 @@ 新しい会話をするためには、OMEMOを明示的にオンにする必要があります。 ショートカットを作成 フォントの大きさ - このアプリで使用される相対フォントサイズ + このアプリで使用される相対的なフォントの大きさ デフォルトでオン デフォルトでオフ @@ -849,11 +851,11 @@ 参加者を検索 ファイルが大きすぎます 添付 - 談話室発見 + 談話室を発見 談話室を検索 プライバシー侵害の可能性あり! - search.jabber.networkを利用します。

この機能を使うと、あなたののIPアドレスや検索キーワードがそのサービスに送信されます。詳しくは、プライバシーポリシーをご覧ください。]]>
- 私は既にアカウントを持っています + search.jabber.networkを利用します。

この機能を使うと、あなたののIPアドレスや検索キーワードがそのサービスに送信されます。詳しくは、プライバシーポリシーをご覧ください。]]>
+ 既にアカウントを持っています 存在するアカウントを追加 新しいアカウントを登録 これはドメインアドレスのようです @@ -873,7 +875,7 @@ jabber.network ローカルサーバー ほとんどのユーザーは、公開されている XMPP エコシステム全体からより良い提案を得るために、‘jabber.network’を選択するはずです。 - 談話室発見方法 + 談話室の発見方法 アカウントを有効にしてください 通話をする 通話着信 @@ -937,4 +939,5 @@ サーバーは招待をサポートしていません この機能をサポートするアクティブなアカウントがありません バックアップを開始しました。 バックアップが完了すると通知が届きます。 + 映像を有効化できません。 diff --git a/src/main/res/values-ml/strings.xml b/src/main/res/values-ml/strings.xml index e4e82c214a54824f7cc923351f83a5432a7098a6..7adca9d89d252a4945f75279628366118facb29a 100644 --- a/src/main/res/values-ml/strings.xml +++ b/src/main/res/values-ml/strings.xml @@ -86,6 +86,7 @@ ഫയലുകൾ സ്വീകരിക്കൂ അറ്റാച്ചുമെന്റുകൾ അറിയിപ്പ് + വൈബ്രേറ്റ് ചെയ്യൂ LED അറിയിപ്പ് റിംഗ്‌ടോൺ അറിയിപ്പ് ശബ്‌ദം @@ -106,8 +107,11 @@ ഓൺലൈൻ ഓഫ്‌ലൈൻ സെർവർ കണ്ടെത്തിയില്ല + കണക്റ്റിവിറ്റി ഇല്ല + രജിസ്ട്രേഷൻ പരാജയപ്പെട്ടു ഉപയോക്തൃനാമം ഇതിനകം നിലവിലുണ്ട് രജിസ്ട്രേഷൻ പൂർത്തിയായി + നയ ലംഘനം സുരക്ഷിതമല്ലാത്ത OTR OpenPGP @@ -152,6 +156,7 @@ തിരഞ്ഞെടുക്കൂ കോൺ‌ടാക്റ്റ് ഇതിനകം നിലവിലുണ്ട് ചേരുക + അടയാളക്കുറിപ്പായി സംരക്ഷിക്കൂ ഗ്രൂപ്പ് ചാറ്റ് നശിപ്പിക്കൂ ചാനൽ നശിപ്പിക്കൂ വിഷയം @@ -166,6 +171,7 @@ അടുത്തത് സെഷൻ സ്ഥാപിച്ചു ഒഴിവാക്കൂ + പ്രാപ്തമാക്കൂ ഗ്രൂപ്പ് ചാറ്റിന് രഹസ്യവാക്ക് ആവശ്യമാണ് രഹസ്യവാക്ക് നൽകുക ഇപ്പോൾ അഭ്യർത്ഥിക്കുക @@ -175,6 +181,7 @@ %s-നെ കുറിച്ച് ആരംഭ സമയം മറ്റുള്ളവ + ഈ ഗ്രൂപ്പ് ചാറ്റിൽ നിന്ന് നിങ്ങളെ നിരോധിച്ചിരിക്കുന്നു നിങ്ങളെ ഗ്രൂപ്പ് ചാറ്റിൽ നിന്ന് പുറത്താക്കി %s അക്കൗണ്ട് ഉപയോഗിക്കുന്നു %s-ന്റെ വലുപ്പം പരിശോധിക്കൂ @@ -214,6 +221,7 @@ ചാനലിൽ നിന്ന് നിരോധിക്കൂ ഇപ്പോൾ നിരോധിക്കൂ സ്വകാര്യ, അംഗങ്ങൾ മാത്രം + നിങ്ങൾ പങ്കെടുക്കുന്നില്ല മറുപടി വായിച്ചതായി കാണിക്കൂ എന്റെർ കീ അയയ്ക്കും @@ -222,6 +230,10 @@ ചിത്രം %s അയയ്ക്കുന്നു %s ടൈപ്പുചെയ്യുന്നു… + %s ടൈപ്പുചെയ്യുന്നു… + ലൊക്കേഷൻ അയയ്‌ക്കുക + ലൊക്കേഷൻ കാണിക്കൂ + ലൊക്കേഷൻ റദ്ദാക്കൂ സമീപകാലത്ത് ഉപയോഗിച്ചത് കോൺ‌ടാക്റ്റുകൾ തിരയുക @@ -250,25 +262,40 @@ ഓൺലൈൻ ലഭ്യമല്ല തിരക്കിലാണ് + പങ്കെടുക്കുന്നവരെ തിരഞ്ഞെടുക്കുക + ഗ്രൂപ്പ് ചാറ്റ് സൃഷ്ടിക്കുന്നു… വീണ്ടും ക്ഷണിക്കൂ + സ്വകാര്യത + രൂപഭംഗി + കമ്പ്യൂട്ടർ + മൊബൈൽ ഫോൺ ഞാൻ അനുവദിക്കൂ + + %d മാസം + %d മാസം + മുഴുവൻ മേഖലയും തടയുക ഇപ്പോൾ സജീവം വെബ്സൈറ്റ് തുറക്കൂ ഇന്ന് ഇന്നലെ + ക്ലിപ്പ്ബോർഡിലേയ്ക്ക് പകർത്തുക സന്ദേശം ഒരിക്കൽ പങ്കിടുക സന്ദേശങ്ങൾ തിരയുക GIF + വിളിപ്പേര് പേര് + ഗ്രൂപ്പ് ചാറ്റിന്റെ പേര് സന്ദേശങ്ങൾ കോളുകൾ സന്ദേശങ്ങൾ നിശബ്‌ദ സന്ദേശങ്ങൾ പങ്കെടുക്കുന്നവർ + റദ്ദാക്കി + രാജ്യ കോഡ് തെറ്റാണ് ഒരു രാജ്യം തിരഞ്ഞെടുക്കൂ ഫോൺ നമ്പർ നിങ്ങളുടെ ഫോൺ നമ്പർ ഉറപ്പാക്കൂ @@ -283,8 +310,25 @@ നിങ്ങളുടെ പേര് നൽകുക അക്കൗണ്ട് തിരഞ്ഞെടുക്കൂ XMPP വിലാസം നൽകുക + ഗ്രൂപ്പ് ചാറ്റ് സൃഷ്ടിക്കുക + പൊതു ചാനലിൽ ചേരുക XMPP വിലാസം + ഫയൽ വളരെ വലുതാണ് + ചാനലുകൾ തിരയുക നിലവിലുള്ള അക്കൗണ്ട് ചേർക്കുക + ഈ പ്രവർത്തനം നടത്താൻ കഴിഞ്ഞില്ല + പൊതു ചാനലിൽ ചേരുക… + jabber.network + കോൾ സ്വീകരിക്കുന്നു + കോൾ അവസാനിപ്പിക്കുന്നു + ഇൻകമിംഗ് കോൾ സഹായം + നിങ്ങളുടെ മൈക്രോഫോൺ ലഭ്യമല്ല GPX ട്രാക്ക് + എല്ലാ സംഭാഷണങ്ങളും + ഈ സംഭാഷണം + നിങ്ങളുടെ അവതാർ + കൂടുതൽ ഓപ്ഷനുകൾ + Conversations-ലേക്ക് ക്ഷണിക്കുക + ബാക്കപ്പ് ആരംഭിച്ചു. അത് പൂർത്തിയായിക്കഴിഞ്ഞാൽ നിങ്ങൾക്ക് ഒരു അറിയിപ്പ് ലഭിക്കും. diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 04bee88883934a176eea3f8ed9245b8e3d9f9a86..eef1ba92bce7121c70200b99beb83ef7c5edf059 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -156,6 +156,7 @@ Nie odnaleziono pliku Ogólny błąd wejścia/wyjścia Aplikacja użyta do wyboru obrazu nie zezwoliła na odczyt pliku.\n\nWybierz obraz przy użyciu innego menedżera plików + Aplikacja której użyłeś do udostępnienia pliku nie dostarczyła odpowiednich uprawnień. Nieznany Tymczasowo wyłączono Połączono @@ -170,6 +171,7 @@ Ten serwer nie wspiera rejestracji Nieprawidłowy żeton rejestracji Nie powiodła się negocjacja TLS + Nie można zweryfikować tej domeny Naruszenie zasad Serwer niekompatybilny Błąd strumienia @@ -924,7 +926,7 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż Połączony Akceptowanie połączenia Kończenie połączenia - Odbierz + Połącz Odrzuć Wyszukiwanie urządzeń Dzwonienie @@ -987,4 +989,5 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż Serwer nie wspiera tworzenia zaproszeń Nie ma aktywnych kont wspierających tę funkcję Tworzenie kopii zapasowej się rozpoczęło. Dostaniesz powiadomienie kiedy się zakończy. + Nie można włączyć wideo. diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index f638735f875aba4e1edc2dd97db05eb163b689b8..ffc3c0682718673100c96b54971a7cdbc8384db6 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -963,4 +963,4 @@ Nenhuma conta ativa suporta esse recurso O backup foi iniciado. Você receberá uma notificação assim que ele for concluído. Não foi possível habilitar o vídeo. - + diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index abc6a99afa154882d7fa4464a29412a790b3fbcb..d3ddbe6ca0c634ebba90654413c215f6cfe9b702 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -135,6 +135,8 @@ Trimițând date despre erori ajutați la continuarea dezvoltării aplicației Confirmă mesajele Contactele sunt notificate atunci când ați primit un mesaj și l-ați citit + Previne captura ecranului + Ascunde conținutul în managerul de aplicații și blochează captura de ecran Opțiuni interfață OpenKeychain a raportat o eroare. Cheie invalidă pentru criptare. @@ -153,6 +155,7 @@ Fișierul nu a fost găsit Eroare I/O generala. Poate ați rămas fără spațiu liber? Aplicația folosită pentru selecția acestei imagini nu a oferit destule permisiuni pentru a putea citii fișierul.\n\nFolosiți un alt manager de fișiere pentru a alege o imagine + Aplicația pe care ați folosit-o pentru a partaja acest fișier nu a furnizat suficiente permisiuni. Necunoscut Dezactivat temporar Conectat @@ -167,6 +170,7 @@ Serverul nu permite înregistrarea Simbol de înregistrare invalid Negociere TLS eşuată + Domeniul nu se poate verifica Încălcare condiții furnizare serviciu Server incompatibil Eroare de date @@ -415,6 +419,7 @@ audio video imagine + grafic vectorial document PDF Aplicație Android Contact @@ -921,6 +926,7 @@ Conexiune pierdută Apel anulat Eroare de aplicație + Problemă la verificare Închide Apel în curs Apel video în curs @@ -973,4 +979,7 @@ Serverul nu suportă generarea de invitații Nici un cont activ nu suporta această caracteristică Se creează copia de siguranță. Veți primi o notificare când acesta este completă. - + Nu s-a putut activa camera video. + Document text + + diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index d2b0c26e783de7619139635efaf155fea01e57ec..e1405feedbc4e4bb25dde925614e035f471ee6fb 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -156,6 +156,7 @@ Файл не найден Общая ошибка ввода/вывода. Возможно, на устройстве недостаточно свободного места? У приложения, которым вы выбрали это изображение, недостаточно прав, чтобы прочитать этот файл.\n\nПожалуйста, используйте другой файловый менеджер, чтобы выбрать это изображение. + Приложение, которое вы использовали для публикации этого файла, не предоставило достаточно разрешений. Неизвестен Временно отключён В сети @@ -170,6 +171,7 @@ Сервер не поддерживает возможность регистрации Неправильный токен регистрации Не удалось согласовать TLS + Домен не поддается проверке Нарушение правил Несовместимый сервер Ошибка потока @@ -986,4 +988,5 @@ Сервер не поддерживает создание приглашений Ни один активный аккаунт не поддерживает эту функцию Резервное копирование было начато. Вы получите уведомление, как только оно будет завершено. + Невозможно включить видео. diff --git a/src/main/res/values-sk/strings.xml b/src/main/res/values-sk/strings.xml index 6fb10af6b48efbc0748a20ff7eb83d163128a9cf..792420be4e166e0c5900c599401c4ea1813eca63 100644 --- a/src/main/res/values-sk/strings.xml +++ b/src/main/res/values-sk/strings.xml @@ -3,38 +3,69 @@ Nastavenia Nová konverzácia Nastavenie účtov + Nastaviť účet + Zavrieť rozhovor Detaily kontaktu + Detaily skupinového rozhovoru + Detaily kanála Pridať účet Upraviť meno + Pridať do kontaktov Vymazať zo zoznamu Zablokovať kontakt Odblokovať kontakt Zablokovať doménu Odblokovať doménu + Zablokovať účastníka + Odblokovať účastníka Nastavenie účtov Nastavenia Zdieľať s konverzáciou Začať konverzáciu + Vybrať Kontakt + Vyberte Kontakty + Zdieľať cez účet Zablokovať zoznam práve teraz pred 1 minútou pred %d minútami + + %dneprečítaný rozhovor + + + %dneprečítaných rozhovorov + + + %dneprečítaných rozhovorov + + + %dneprečítaných rozhovorov + + posielam... Dešifrujem správu. Čakajte, prosím… + OpenPGP šifrovaná správa Prezývka už existuje + Chybná prezývka Administrátor Vlastník Moderátor Účastník Návštevník + Chcete vymazať %sz vašich kontaktov? Rozhovory s týmto kontaktom nebudú zmazané. Chceli by ste zablokovať prijímanie správ od %s? Chceli by ste odblokovať %s a povoliť prijímanie správ? Zablokovať všetky kontakty od %s? Odblokovať všetky kontakty od %s? Kontakt zablokovaný + Zablokovaný + Chcete vymazať %sako záložku? Rozhovory s touto záložkou nebudú zmazané. Registrovať nový účet na serveri Zmeniť heslo na serveri Zdieľať s + Začať rozhovor + Pozvať kontakt + Pozvať Kontakty Kontakt Zrušiť @@ -46,33 +77,70 @@ Odblokovať Uložiť OK + %1$ssa zrútila + Pomocou vášho XMPP konta nám pošlite záznam o zlyhaní, ktorý nám pomôže vo vývoji %1$s. Poslať teraz Nepýtať sa znova + Nedá sa pripojiť k účtu + Nedá sa pripojiť k viacerým kontám + Ťapnite na správu vášho účtu Priložiť súbor + Pridať tento chýbajúci kontakt do vašich kontaktov? Pridať kontakt doručenie zlyhalo + Pripravujem odoslanie obrázka + Pripravujem odoslanie obrázkov + Zdieľam súbory. Prosím čakajte... Vymazať históriu Vymazať históriu konverzácií + Chcete vymazať všetky správy v tomto rozhovore?\n\nUpozornenie:Nebude to mať vplyv na správy uložené na ostatných zariadeniach alebo serveroch. + Zmazať súbor + Ste si istý, že chcete tento súbor zmazať?\n\nUpozornenie:Nevymažú sa kópie súborov, ktoré sú uložené na ostatných zariadeniach alebo serveroch. + Potom zavrieť tento rozhovor + Zvoliť zariadenie Poslať nezašifrovanú správu + Poslať správu + Poslať správu na %s Poslať OMEMO šifrovanú správu + Poslať v\\OMEMO šifrovanú správu Poslať OpenPGP šifrovanú správu + Používa sa nová prezývka Poslať nešifrované Zašifrovanie zlyhalo. Možno nemáte správny privátny kľúč. OpenKeychain Reštartovať Inštalovať + Prosím, nainštalujte OpenKeychain ponúka… čakám… Nenašiel sa žiadny OpenPGP kľúč + Nepodarilo sa zašifrovať vašu správu, pretože váš kontakt nezverejňuje jeho verejný kľúč.\n\nPožiadajte prosím váš kontakt, aby si nastavil OpenPGP. Nenašli sa žiadne OpenPGP kľúče + Nemôžem zašifrovať Vašu správu, pretože Vaše kontakty neoznamujú ich verejné kľúče.\n\nPoproste ich, aby si nastavili OpenPGP. Všeobecné Prijať súbory Automaticky prijať súbory menšie ako… + Prílohy + Oznámenie Vibrovať + Vibrovať, keď príde nová správa + LED notifikácia + Blikať notifikačným svetlom, keď príde nová správa + Zvonenie + Zvuk oznámenia + Zvuk oznámenia nových správ + Zvonenie pre prichádzajúce hovory + Ochranná doba + Pokročilé Neodosielať detaily o zlyhaní aplikácie + Keď pošlete detaily o dôvode zlyhania, pomáhate vývoju Potvrdzovať správy + Dajte vedieť svojim kontaktom, keď prijmete a prečítate si správy + Prostredie + Nesprávny kľúč na šifrovanie. Prijať Došlo k chybe + Chyba Váš účet Zasielať zmeny stavu Prijímať zmeny stavu @@ -81,8 +149,10 @@ Odfotiť Aktívne povoliť vyžiadanie zmeny stavu Vybraný súbor nie je obrázok + Nemohol som konvertovať obrázkový súbor Súbor sa nenašiel Všeobecná I/O chyba. Možno už nie je voľné miesto? + Aplikácia, ktorú ste použili na zdieľanie tohto súboru neposkytla dostatočné povolenia. Neznámy Dočasne vypnutý Online @@ -94,6 +164,10 @@ Registrácia zlyhala Užívateľské meno už existuje Registrácia ukončená + Registrácia nie je podporovaná serverom. + Neplatný registračný token + Doména sa nedá overiť + Porušenie pravidiel Nekompatibilný server Nezašifrovaný OTR @@ -106,8 +180,13 @@ Povoliť účet Ste si istý? Nahrať hlas + XMPP adresa + Zablokovať adresu XMPP meno@priklad.com Heslo + Toto nie je platná XMPP adresa + Nedostatok pamäte. Obrázok príliš veľký + Chcete pridať do vašich kontaktov %s? Informácie o serveri XEP-0313: MAM XEP-0280: Message Carbons @@ -115,38 +194,65 @@ XEP-0191: Blocking Command XEP-0237: Roster Versioning XEP-0198: Stream Management + XEP-0215: Zistenie externej služby XEP-0163: PEP (Avatars / OMEMO) XEP-0363: HTTP File Upload + XEP-0357: Oznámenia dostupný nedostupný Chýba oznámenie o verejnom kľúči práve prihlásený + naposledy videný pred minútou naposledy prihlásený pred %d minútami + naposledy videný pred hodinou naposledy prihlásený pred %d hodinami + naposledy videný včera naposledy prihlásený pred %d dňami OMEMO identifikátor + v\\OMEMO odtlačok + OMEMO odtlačok (pôvod správy) + v\\OMEMO odtlačok (pôvod správy) Ostatné zariadenia Dôverovať OMEMO identifikátoru + Načítavam kľúče... Dokončený Dešifrovať + Záložky Hľadať + Zmazať kontakt Zobraziť detaily kontaktu Zablokovať kontakt Odblokovať kontakt Vytvoriť + Vybrať Kontakt už existuje Vstúpiť + channel@conference.example.com/nick + channel@conference.example.com Uložiť ako záložku Vymazať záložku + Vymazať skupinový rozhovor + Vymazať kanál + Nemohol som vymazať skupinový rozhovor + Nemohol som vymazať kanál + Upraviť predmet skupinového rozhovoru + Téma + Pripájam skupinový rozhovor... Odísť Kontakt pridaný do zoznamu Znova pridať %s dočítal až potiaľ + %sdočítal potiaľto + %1$s+%2$dostatní dočítali potiaľto + Každý dočítal potiaľto Zverejniť + Ťuknite na avatar pre vybranie obrázka z galérie Zverejňujem… Server odmietol toto zverejnenie + Nemôžem skonvertovať váš obrázok Nepodarilo sa uložiť avatar na disk (Dlho podržať pre obnovenie pôvodného stavu) + Váš server nepodporuje zverejnenie avatarov súkromná správa pre %s Odoslať súkromnú správu %s @@ -159,26 +265,50 @@ Vložiť heslo Ihneď vyžiadať Ignorovať + Bezpečnosť + Povoliť úpravu správy + Povoliť vašim kontaktom spätne upraviť ich správy + Nastavenia pre skúsených S týmto narábajte veľmi opatrne, prosím + O %s Tichý režim Čas začiatku Čas konca Povoliť tichý režim Upozornenia budú počas tichého režimu stlmené Ďalší + Synchronizovať so záložkami + Automaticky sa pripojiť k skupinovému rozhovoru, ak to hovorí záložka + OMEMO odtlačok skopírovaný do schránky + Ste zakázaný na tomto skupinovom rozhovore + Skupinový rozhovor len pre členov + Skupinový rozhovor bol zastavený + Už viac nie ste v tomto skupinovom rozhovore Používa sa účet %s + Hostovaný na %s Overiť %s na HTTP host Nie ste pripojený. Skúste to neskôr Overiť %s veľkosť + Skontrolujte %1$sveľkosť na %2$s Možnosti správy + Citovať + Vložiť ako citát Skopírovať originálny URL Poslať znova URL súbor + URL skopírovaná do schránky + XMPP adresa skopírovaná do schránky + Správa o chybe skopírovaná do schránky + web adresa + Snímať 2D Bar kód + Ukázať 2D Bar kód Zobraziť zoznam blokovaných Detaily účtu Potvrdiť Skúste znova Zamedzí operačnému systému ukončiť pripojenie + Vytvoriť zálohu + Vaša záloha bola obnovená Vybrať súbor Prijímam %1$s (%2$d%% ukončený) Stiahnuť %s @@ -191,6 +321,9 @@ Povoliť upozornenia Avatar účtu Skopírovať OMEMO identifikátor do schránky + Regenerovať OMEMO kľúč + Vymazať zariadenia + Ste si istý, že chcete odstrániť všetky ostatné zariadenia z OMEMO oznámenia? Keď sa nabudúce vaše zariadenia pripoja, znova sa samé ohlásia, ale nemusia prijať správy odoslané medzitým. Načítať históriu zo serveru Na serveri nie je žiadna ďalšia história Aktualizujem... @@ -257,7 +390,48 @@ Užívateľské meno Toto nie je platné užívateľské meno Obnoviť certifikát + Chyba pri načítaní OMEMO kľúča! + kľúč OMEMO overený certifikátom! + Pripojiť cez Tor + %1$dz%2$dúčtov pripojených + (Žiadne aktivované účty) Online + Vymazať OMEMO identifikátory + Re-generuje vaše kľúče OMEMO. Všetky vaše kontakty vás budú musieť znova overiť. Použite to ako poslednú možnosť. + Overili ste všetky kľúče OMEMO vo vašom vlastníctve. + Overiť kľúče OMEMO + online práve teraz Správa skopírovaná do schránky + OMEMO šifrovanie + OMEMO bude vždy používané pre individuálne a súkromné skupinové rozhovory. + OMEMO bude predvolene zapnuté pre všetky rozhovory. + Veľkosť písma + Nepodarilo sa dešifrovať OMEMO správu. Zobraziť polohu + Hovory + Prichádzajúce hovory + Prebiehajúce hovory + Nastavenia oznámení prichádzajúcich hovorov + Obnoviť zálohu + Priložiť + Prichádzajúci hovor + Prichádzajúci video hovor + Prijímam hovor + Ukončujem hovor + Vyhľadávanie zariadení + Zvoní + Nedá sa pripojiť hovor + Prebiehajúci hovor + Prebiehajúci video hovor + Prichádzajúci hovor + Prichádzajúci hovor - %s + Zmeškaný hovor - %s + Odchádzajúci hovor + Odchádzajúci hovor - %s + Zmeškaný hovor + Hlasový hovor + Video hovor + Naraz môžete mať iba jeden hovor. + Vrátiť sa do prebiehajúceho hovoru + Zašifrované s OMEMO diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml index 623bb6a949059db3337a6e5a2f6040d5b31fdfd4..cd39b936972887c555397175ae352c4961472d9d 100644 --- a/src/main/res/values-sr/strings.xml +++ b/src/main/res/values-sr/strings.xml @@ -111,7 +111,9 @@ нудим… чекам… Нема ОпенПГП кључа + Није могуће шифровати вашу поруку јер контакт није објавио свој јавни кључ.\n\nЗамолите контакт да подеси ОпенПГП. Нема ОпенПГП кључева + Није могуће шифровати вашу поруку јер контакти нису објавили своје јавне кључеве.\n\nЗамолите контакте да подесе ОпенПГП. Опште Прихватај фајлове Аутоматски прихватај фајлове мање од… @@ -128,6 +130,7 @@ Период одгоде Напредно Никад не шаљи извештаје о паду + Слањем извештаја рада помажете развоју апликације. Потврди поруке Обзнаните контактима када примите и прочитате њихове поруке Сучеље @@ -147,6 +150,8 @@ Не могу преобратити датотеку фотографије Фајл није нађен Општа У/И грешка. Можда вам је нестало простора у складишту? + Апликација из које делите ову слику не даје дозволу довољну да се датотека учита.\n\nПоделите слику другим претраживачем датотека. + Апликација из које делите овај садржај не даје довољну дозволу. Непознато Привремено искључен На вези @@ -179,6 +184,7 @@ ОпенПГП кључ је објављен. Укључи налог Да ли сте сигурни? + Брисањем налога бришете и целу историју ваших разговора. Сними глас ИксМПП адреса Блокирај ИксМПП адресу @@ -237,6 +243,8 @@ Обриши обележивач Уклони групно ћаскање Уклони канал + Да ли сигурно жеите да уклоните ово групно ћаскање?\n\nУпозорење: Групно ћаскање ће бити потпуно обрисано са сервера. + Да ли сигурно жеите да уклоните овај јавни канал?\n\nУпозорење: Канал ће бити потпуно обрисан са сервера. Не могу уклонити групно ћаскање Не могу уклонити канал Уреди предмет групног ћаскања @@ -269,8 +277,10 @@ Укључи Групно ћаскање захтева лозинку Унесите лозинку + Најпре захтевајте ажурирање присутности од вашег контакта.\n\nОво ће омогућити да се одреди који клијент ваш контакт користи. Захтевај одмах Занемари + Упозорење: Слањем овога без обостраног ажурирања присутности може изазвати неочекиване проблеме.\n\nИдите у „Детаљи контакта” да потврдите вашу претплату за присутност. Безбедност Дозволи исправљање порука Дозвољава вашим контактима да ретроактивно уређују њихове поруке @@ -349,6 +359,7 @@ Копирај ОМЕМО отисак на клипборд Поново генериши ОМЕМО кључ Очисти уређаје + Нема употребљивих кључева за овај контакт.\nПроверите да ли сте одобрили узајамно ажурирање присутности. Нешто је пошло по злу Добављам историјат са сервера Нема више историјата на серверу @@ -515,6 +526,7 @@ Исправи поруку Пошаљи исправљену поруку Искључили сте овај налог + Безбедносна грешка: неисправан приступ датотеци! Нема апликације за дељење ресурса Подели везу помоћу… Сложи се и настави @@ -523,6 +535,7 @@ Користићу сопствени провајдер Одредите ваше корисничко име Ручно мењај доступност + Поставите присутност при измени ваше поруке стања. Порука стања Слободан за ћаскање На вези @@ -564,12 +577,17 @@ Дозволи Нема дозвола за приступ %s Удаљени сервер није нађен + Удаљени сервер се не одазива + Не могу да ажурирам налог + Пријавите ову ИксМПП адресу због нежељених порука. Обриши ОМЕМО идентитете Обриши изабране кључеве Морате бити повезани да бисте објавили ваш аватар. Прикажи поруку грешке Порука грешке Чувар протока укључен + Ваш уређај не подржава искључење „Уштеде података” за %1$s. + Не могу да направим привремену датотеку Овај уређај је оверен. Копирај отисак Оверени отисци diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index 595a6a3455e6bb7aa5a704e92e311f77ea2176e9..96b77ff575162f534fa1c03de767e6fd5d689a0a 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -4,6 +4,7 @@ Ny konversation Kontoinställningar Hantera konto + Stäng konversation Kontaktdetaljer Gruppchattdetaljer Kanaldetaljer @@ -28,6 +29,13 @@ just nu 1 min sedan %d min sedan + + %d oläst konversation + + + %d olästa konversationer + + skickar… Avkrypterar meddelande. Vänta… OpenPGP-krypterat meddelande @@ -105,12 +113,16 @@ LED notifieringar Blinka med notifieringsljuset när ett meddelande tagits emot Meddelandesignal + Aviseringsljud + Aviseringsljud för nya meddelande + Ringsignal för inkommande samtal Notifieringsfrist Avancerat Skicka aldrig krasch-rapporter Bekräfta meddelanden Låt dina kontakter veta när du har mottagit och läst deras meddelanden Gränssnitt + OpenKeychain genererade ett fel. Dålig krypterings-nyckel. Acceptera Ett fel har inträffat @@ -136,6 +148,7 @@ Registreringsfel Användarnamn används redan Registrering klar + Registrering stöds ej av server TLS-förhandling misslyckades Kränkning av policy Inkompatibel server @@ -173,9 +186,14 @@ otillgänglig Annonsering om publik nyckel saknas senast sedd just nu + senast sedd för en minut sedan senast sedd %d minuter sedan + senast sedd för en timme sedan senast sedd %d timmar sedan + senast sedd för en dag sedan senast sedd %d dagar sedan + Krypterat meddelande. Installera OpenKeychain för att dekryptera meddelandet. + Nytt OpenPGP krypterat meddelande hittades OpenPGP-nyckel-ID OMEMO-fingeravtryck v\\OMEMO-fingeravtryck @@ -500,7 +518,7 @@ Krypterar meddelande Hämtar inte meddelanden på grund av inställningen för borttagning av gamla meddelanden. Komprimerar video - Motsvarande konversationer är stängda. + Korresponderande konversationer är stängda. Kontakt blockerad. Notifieringar från främlingar Mottagna meddelanden från främlingar @@ -531,6 +549,7 @@ Visa plats Dela Var god dröj... + Söka i meddelanden GIF Kopiera XMPP-adress Smeknamn @@ -557,7 +576,10 @@ e-bok Öppna med... Välj konto + Återställa säkerhetskopiering + Återställa Ange ditt lösenord till kontot %s för att återställa säkerhetskopian. + Det gick inte att återställa säkerhetskopian. Skapa gruppchatt Skapa sluten gruppchatt Kanalnamn @@ -584,4 +606,7 @@ Om Aktivera ett konto Upptagen + Fäst flik till toppen + Ta bort flik från toppen + Fler alternativ diff --git a/src/main/res/values-tr-rTR/strings.xml b/src/main/res/values-tr-rTR/strings.xml index 3460ee2934b2527d15d5ae2f04ecada1f723275f..4f0084189198bc0dde733f5135a5745c8a832dae 100644 --- a/src/main/res/values-tr-rTR/strings.xml +++ b/src/main/res/values-tr-rTR/strings.xml @@ -150,6 +150,7 @@ Dosya bulunamadı Genel G/Ç hatası. Depolama yeri kalmamış olabilir mi? Bu görüntüyü seçmekte kullandığınız uygulama, dosyanın okunması için yeterli izinleri sağlayamadı. Görüntüyü seçmek için farklı bir dosya yöneticisi kullan. + Bu dosyayı paylaşmakta kullandığınız uygulama yeterince yetki sağlamamaktadır. Bilinmeyen Geçici olarak devre dışı Çevrim içi @@ -164,6 +165,7 @@ Hesap, sunucu tarafından desteklenmiyor. Geçersiz hesak simgesi TLS uzlaşması başarısız + Alan adı doğrulanamıyor Politika ihlali Sunucu uyuşmazlığı Akış hatası @@ -959,4 +961,6 @@ Davet iletilemedi Sunucu, davet oluşturulmasını desteklemiyor Bu özelliği destekleyen aktif bir hesap yok + Yedekleme başlatıldı. Tamamlandığı zaman bir bildirim alacaksınız. + Video etkinleştirilemedi diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml index deebac8f8a79ac086533ee8e1b510ec38bbebc71..31cd4d7355ba5fb272e68505e2be6761b3e2f23e 100644 --- a/src/main/res/values-vi/strings.xml +++ b/src/main/res/values-vi/strings.xml @@ -504,7 +504,7 @@ Đã chia sẻ các hình ảnh với %s Đã chia sẻ văn bản với %s Cấp quyền truy cập bộ nhớ cho %1$s - Cấp quyền truy cập camera cho %1$s + Cấp quyền truy cập máy ảnh cho %1$s Đồng bộ với danh bạ %1$s muốn quyền truy cập sổ địa chỉ của bạn để nối nó với danh sách liên hệ XMPP của bạn.\nViệc này sẽ hiển thị họ tên và ảnh đại diện của các liên hệ của bạn.\n\n%1$s sẽ chỉ đọc sổ địa chỉ của bạn và nối nó một cách cục bộ mà không tải gì cả lên máy chủ của bạn.
Chúng tôi sẽ không lưu trữ bản sao của các số điện thoại đó.\n\nĐể biết thêm thông tin hãy đọc chính sách riêng tư của chúng tôi.

Bây giờ bạn sẽ được hỏi cấp quyền truy cập danh bạ.]]>
@@ -530,11 +530,424 @@ Lỗi bảo mật: Truy cập tệp không hợp lệ! Không tìm thấy ứng dụng nào để chia sẻ URI Chia sẻ URI với... +
Bạn đăng ký bằng số điện thoại của bạn và Quicksy sẽ tự động—dựa trên những số điện thoại trong sổ địa chỉ của bạn—đề xuất các liên hệ có thể có cho bạn.

Bằng cách đăng ký, bạn đồng ý với chính sách riêng tư của chúng tôi.]]>
Đồng ý và tiếp tục + Một hướng dẫn đã được thiết lập cho việc tạo tài khoản trên conversations.im.¹\nKhi chọn conversations.im làm nhà cung cấp, bạn sẽ có thể giao tiếp với những người dùng của các nhà cung cấp khác bằng cách đưa cho họ địa chỉ XMPP đầy đủ của bạn. Địa chỉ XMPP đầy đủ của bạn sẽ là: %s Tạo tài khoản + Dùng nhà cung cấp của tôi + Hãy chọn tên người dùng + Quản lý tính khả dụng thủ công + Đặt tính khả dụng của bạn khi chỉnh sửa thông báo trạng thái của bạn. + Thông báo trạng thái + Rảnh để trò chuyện Trực tuyến + Vắng mặt + Không khả dụng + Bận + Một mật khẩu bảo mật đã được tạo + Thiết bị của bạn không hỗ trợ tắt tối ưu hoá pin + Đăng ký thất bại: Hãy thử lại sau + Đăng ký thất bại: Mật khẩu quá yếu + Chọn các thành viên + Tạo nhóm chat... + Mời lại Tắt + Ngắn + Vừa + Dài + Sử dụng truyền phát + Cho các liên hệ của bạn biết bạn dùng Conversations + Riêng tư + Chủ đề + Chọn bộ màu sáng + Tự động + Sáng + Tối + Nền xanh lá cây + Dùng nền xanh lá cây cho tin nhắn nhận được + Không thể kết nối với OpenKeychain + Thiết bị này không còn được dùng nữa + Máy tính + Điện thoại di động + Máy tính bảng + Trình duyệt web + Bảng điều khiển + Yêu cầu thanh toán + Cho phép sử dụng Internet + Tôi + Liên hệ yêu cầu đăng ký sự có mặt + Cho phép + Không có quyền truy cập%s + Không tìm thấy máy chủ trên mạng + Hết thời gian chờ cho máy chủ trên mạng + Không thể cập nhật tài khoản + Báo cáo địa chỉ XMPP này vì spam. + Xoá các danh tính OMEMO + Tái tạo lại các mã khoá OMEMO của bạn. Tất cả các liên hệ của bạn sẽ phải xác minh lại bạn. Chỉ sử dụng việc này làm giải pháp cuối cùng. + Xoá các mã khoá đã chọn + Bạn cần phải có kết nối để xuất bản ảnh đại diện của bạn. + Hiện thông báo lỗi + Thông báo lỗi + Trình tiết kiệm dữ liệu đang bật + Hệ điều hành của bạn đang giới hạn %1$s truy cập Internet trong nền. Để nhận các thông báo tin nhắn mới, bạn nên cho phép %1$s truy cập không giới hạn khi \"Trình tiết kiệm dữ liệu\" đang bật.\n%1$s vẫn sẽ nỗ lực tiết kiệm dữ liệu khi có thể. + Thiết bị của bạn không hỗ trợ việc tắt Trình tiết kiệm dữ liệu cho %1$s. + Không thể tạo tệp tạm + Thiết bị này đã được xác thực + Sao chép mã vân tay + Bạn đã xác minh tất cả mã khoá OMEMO mà bạn đang sở hữu + Mã vạch không chứa mã vân tay cho cuộc trò chuyện này. + Mã vân tay đã xác minh + Sử dụng máy ảnh để quét mã vạch của liên hệ + Vui lòng đợi để lấy các mã khoá + Chia sẻ dưới dạng mã vạch + Chia sẻ dưới dạng URI XMPP + Chia sẻ dưới dạng liên kết HTTP + Tin tưởng mù quáng trước khi xác minh + Tin tưởng các thiết bị mới từ các liên hệ chưa xác minh, nhưng hỏi xác nhận thủ công các thiết bị mới từ các liên hệ đã xác minh. + Các mã khoá OMEMO đã tin tưởng mù quáng, có nghĩa là họ có thể là một ai đó khác hoặc ai đó có thể đã can thiệp. + Chưa tin tưởng + Mã vạch 2D không hợp lệ + Dọn dẹp thư mục bộ nhớ tạm (được ứng dụng máy ảnh sử dụng) + Dọn dẹp bộ nhớ tạm + Dọn dẹp bộ nhớ riêng + Dọn dẹp bộ nhớ riêng nơi các tệp được giữ (Chúng có thể được tải xuống lại từ máy chủ) + Tôi đã đi theo liên kết này từ một nguồn được tin tưởng + Bạn sắp xác minh các mã khoá OMEMO của %1$s sau khi nhấn vào một liên kết. Việc này chỉ là bảo mật nếu bạn đã đi theo liên kết này từ một nguồn được tin tưởng, nơi chỉ có %2$s có thể đã xuất bản liên kết này. + Xác minh các mã khoá OMEMO + Hiện không hoạt động + Ẩn không hoạt động + Huỷ tin tưởng thiết bị + Bạn có chắc bạn muốn bỏ xác minh thiết bị này không?\nThiết bị này và các tin nhắn từ nỏ sẽ được đánh dấu là \"Chưa tin tưởng\". + + %d giây + + + %d phút + + + %d giờ + + + %d ngày + + + %d tuần + + + %d tháng + + Tự động xoá tin nhắn + Tự động xoá các tin nhắn cũ hơn phạm vi thời gian được thiết lập khỏi thiết bị. + Đang mã hoá tin nhắn + Không lấy tin nhắn do khoảng thời gian giữ lại cục bộ. + Đang nén video + Đã đóng các cuộc hội thoại tương ứng. + Đã chặn liên hệ. + Thông báo từ người lạ + Thông báo về các tin nhắn và cuộc gọi được nhận từ những người lạ. + Đã nhận tin nhắn từ người lạ + Chặn người lạ + Chặn toàn bộ miền + trực tuyến ngay lúc này + Thử giải mã lại + Lỗi phiên làm việc + Cơ chế SASL đã bị hạ cấp + Máy chủ yêu cầu đăng ký trên trang web + Mở trang web + Không tìm thấy ứng dụng nào để mở trang web + Thông báo gây chú ý + Hiện thông báo gây chú ý + Hôm nay + Hôm qua + Xác thực tên máy chủ bằng DNSSEC + Các chứng chỉ máy chủ chứa tên miền được xác thực được coi là đã xác minh + Chứng chỉ không chứa địa chỉ XMPP hợp lệ + một phần + Ghi video + Sao chép vào bộ nhớ tạm Đã chép tin nhắn vào clipboard + Tin nhắn + Tin nhắn riêng tư bị tắt + Ứng dụng được bảo vệ + Để tiếp tục nhận các thông báo, kể cả khi màn hình đã tắt, bạn cần thêm Conversations vào danh sách các ứng dụng được bảo vệ. + Chấp nhận chứng chỉ không xác định? + Chứng chỉ máy chủ này không được một người có quyền chứng chỉ đã biết ký. + Chấp nhận tên máy chủ không khớp? + Máy chủ không thể xác thực với tư cách \"%s\". Chứng chỉ chỉ hợp lệ cho: + Bạn có muốn vẫn kết nối không? + Chi tiết chứng chỉ: + Một lần + Trình quét mã QR cần quyền truy cập máy ảnh + Cuộn xuống dưới cùng + Cuộn xuống sau khi gửi một tin nhắn + Chỉnh sửa thông báo trạng thái + Chỉnh sửa thông báo trạng thái + Tắt mã hoá + %1$s không thể gửi tin nhắn được mã hoá đến %2$s. Điều này có thể là do liên hệ của bạn sử dụng một máy chủ hoặc ứng dụng khách lỗi thời không thể xử lý OMEMO. + Không thể lấy danh sách thiết bị + Không thể lấy mã khoá mã hoá + Gợi ý: Trong một số trường hợp, điều này có thể được sửa bằng cách thêm lẫn nhau vào danh sách liên hệ của bạn. + Bạn có chắc bạn muốn tắt mã hoá OMEMO cho cuộc hội thoại này không?\nViệc này sẽ cho phép quản trị viên máy chủ đọc các tin nhắn của bạn, nhưng việc này có thể là cách duy nhất để giao tiếp với những người sử dụng các ứng dụng khách lỗi thời. + Tắt ngay + Bản nháp: + Mã hoá OMEMO + OMEMO sẽ luôn được sử dụng cho các cuộc trò chuyện nhóm một đối một và riêng tư. + OMEMO sẽ được sử dụng theo mặc định cho các cuộc hội thoại mới. + OMEMO sẽ phải được bật một cách rõ ràng cho các cuộc hội thoại mới. + Tạo lối tắt + Cỡ chữ + Cỡ chữ tương đối được sử dụng trong ứng dụng. + Bật theo mặc định + Tắt theo mặc định + Nhỏ + Trung bình + Lớn + Tin nhắn đã không được mã hoá cho thiết bị này. + Giải mã tin nhắn OMEMO thất bại. + hoàn tác + Chia sẻ vị trí bị tắt + Cố định vị trí + Bỏ cố định vị trí + Sao chép vị trí + Chia sẻ vị trí + Hướng + Chia sẻ vị trí Hiện vị trí + Chia sẻ + Không thể bắt đầu ghi lại + Vui lòng đợi... + Cấp quyền truy cập micro cho %1$s + Tìm kiếm tin nhắn + GIF + Xem cuộc hội thoại + Chia sẻ plugin vị trí + Sử dụng plugin chia sẻ vị trí thay vì bản đồ được tích hợp + Sao chép địa chỉ web + Sao chép địa chỉ XMPP + Chia sẻ tệp HTTP cho S3 + Tìm kiếm trực tiếp + Tại màn hình \'Bắt đầu cuộc hội thoại\', mở bàn phím và đặt con trỏ trong trường tìm kiếm + Ảnh đại diện cuộc trò chuyện nhóm + Máy chủ không hỗ trợ ảnh đại diện cuộc trò chuyện nhóm + Chỉ có chủ sở hữu mới có thể thay đổi ảnh đại diện cuộc trò chuyện nhóm + Tên liên hệ + Biệt danh + Tên + Việc cung cấp tên là không bắt buộc + Tên cuộc trò chuyện nhóm + Cuộc trò chuyện nhóm này đã bị phá huỷ + Không thể lưu bản ghi + Dịch vụ ở trước + Hạng mục thông báo này được sử dụng để hiển thị một thông báo vĩnh viễn chỉ ra rằng %1$s đang chạy. + Thông tin trạng thái + Vấn đề kết nối + Hạng mục thông báo này được sử dụng để hiển thị một thông báo trong trường hợp có vấn đề khi kết nối đến một tài khoản. + Tin nhắn + Cuộc gọi + Tin nhắn + Cuộc gọi đến + Cuộc gọi đang diễn ra + Tin nhắn im lặng + Nhóm thông báo này được sử dụng để hiển thị các thông báo không nên phát ra tiếng động. Ví dụ là khi đang hoạt động trên một thiết bị khác (thời gian ân hạn). + Gửi đi thất bại + Cài đặt thông báo tin nhắn + Cài đặt thông báo cuộc gọi đến + Sự quan trọng, âm thanh, rung + Nén video + Xem phương tiện + Thành viên + Trình duyệt phương tiện + Tệp đã bị bỏ vì vi phạm bảo mật. + Chất lượng video + Chất lượng thấp hơn có nghĩa là tệp nhỏ hơn + Trung bình (360p) + Cao (720p) + đã huỷ + Bạn đã đang tạo bản nháp một tin nhắn rồi. + Tính năng chưa được thêm + Mã quốc gia không hợp lệ + Chọn quốc gia + số điện thoại + Xác minh số điện thoại của bạn + Quicksy sẽ gửi một tin nhắn SMS (có thể áp dụng phí nhà mạng) để xác minh số điện thoại của bạn. Hãy nhập mã quốc gia và số điện thoại của bạn: +
%s

Điều này có ổn không, hay bạn muốn chỉnh sửa số điện thoại?]]>
+ %s không phải là số điện thoại hợp lệ. + Vui lòng nhập số điện thoại của bạn. + Tìm kiếm quốc gia + Xác minh %s + %s.]]> + Chúng tôi đã gửi một SMS khác có mã 6 chữ số cho bạn. + Vui lòng nhập mã PIN 6 chữ số ở dưới. + Gửi lại SMS + Gửi lại SMS (%s) + Vui lòng đợi (%s) + quay lại + Đã tự động dán mã PIN có thể có từ bộ nhớ tạm. + Vui lòng nhập mã PIN 6 chữ số. + Bạn có chắc bạn muốn huỷ quá trình đăng ký không? + + Không + Đang xác minh... + Đang yêu cầu SMS... + Mã PIN bạn đã nhập không chính xác. + Mã PIN chúng tôi gửi cho bạn đã hết hạn. + Lỗi mạng không xác định. + Phản hồi không xác định từ máy chủ. + Không thể kết nối đến máy chủ. + Không thể lập kết nối bảo mật. + Không thể tìm máy chủ. + Có gì đó sai đã xảy ra khi xử lý yêu cầu của bạn. + Đầu vào người dùng không hợp lệ + Tạm thời không có sẵn. Hãy thử lại sau. + Không có kết nối mạng. + Vui lòng thử lại trong %s + Bạn bị giới hạn tốc độ + Quá nhiều lần thử + Bạn đang sử dụng một phiên bản lỗi thời của ứng dụng này. + Cập nhật + Số điện thoại này hiện đã được đăng nhập ở một thiết bị khác. + Vui lòng nhập tên của bạn để cho những người không có bạn trong sổ địa chỉ của họ biết bạn là ai. + Tên của bạn + Nhập tên của bạn + Sử dụng nút chỉnh sửa để đặt tên của bạn. + Từ chối yêu cầu + Cài đặt Orbot + Khởi động Orbot + Không có ứng dụng chợ nào được cài đặt. + Kênh này sẽ làm cho địa chỉ XMPP của bạn trở thành công khai + sách điện tử + Gốc (không nén) + Mở bằng... + Ảnh hồ sơ Conversations + Chọn tài khoản + Khôi phục bản sao lưu + Khôi phục + Nhập mật khẩu của bạn cho tài khoản %s để khôi phục bản sao lưu. + Đừng sử dụng tính năng khôi phục bản sao lưu để cố gắng nhân bản (chạy đồng thời) một lượt cài đặt. Việc khôi phục một bản sao lưu chỉ dành cho việc di cư hoặc trong trường hợp bạn đã mất thiết bị gốc. + Không thể khôi phục bản sao lưu. + Không thể giải mã bản sao lưu. Mật khẩu có đúng không? + Sao lưu & khôi phục + Nhập địa chỉ XMPP + Tạo cuộc trò chuyện nhóm + Tham gia kênh công khai + Tạo cuộc trò chuyện nhóm riêng tư + Tạo kênh công khai + Tên kênh + Địa chỉ XMPP + Vui lòng cung cấp tên cho kênh + Vui lòng cung cấp địa chỉ XMPP + Đây là một địa chỉ XMPP. Vui lòng cung cấp một cái tên. + Đang tạo kênh công khai... + Kênh này đã tồn tại + Bạn đã tham gia một kênh đang tồn tại + Không thể lưu thiết lập kênh + Cho phép bất kỳ ai chỉnh sửa chủ đề + Cho phép bất kỳ ai mời những người khác + Bất kỳ ai cũng có thể chỉnh sửa chủ đề. + Chủ sở hữu có thể chỉnh sửa chủ đề. + Quản trị viên có thể chỉnh sửa chủ đề. + Chủ sở hữu có thể mời những người khác. + Bất kỳ ai cũng có thể mời những người khác. + Các địa chỉ XMPP có thể được quản trị viên nhìn thấy. + Các địa chỉ XMPP có thể được bất kỳ ai nhìn thấy. + Kênh công khai này không có thành viên nào. Hãy mời các liên hệ của bạn hoặc sử dụng nút chia sẻ để phân phát địa chỉ XMPP của kênh. + Cuộc trò chuyện nhóm riêng tư này không có thành viên nào. + Quản lý đặc quyền + Tìm kiếm thành viên + Tệp quá lớn + Đính kèm + Khám phá các kênh + Tìm kiếm kênh + Sự vi phạm tính riêng tư có thể có! + search.jabber.network.

Việc sử dụng tính năng này sẽ truyền địa chỉ IP và câu từ tìm kiếm của bạn đến dịch vụ đó. Hãy xem Chính sách riêng tư của họ để biết thêm thông tin.]]>
+ Tôi đã có một tài khoản rồi + Thêm tài khoản đang tồn tại + Đăng ký tài khoản mới + Cái này trông giống một địa chỉ miền + Vẫn thêm + Cái này trông giống một địa chỉ kênh + Chia sẻ tệp sao lưu + Bản sao lưu Conversations + Sự kiện + Mở bản sao lưu + Tệp bạn đã chọn không phải là tệp sao lưu của Conversations + Tài khoản này đã được thiết lập rồi + Vui lòng nhập mật khẩu cho tài khoản này + Không thể thực hiện hành động này + Tham gia kênh công khai... + Ứng dụng chia sẻ đã không cấp quyền truy cập tệp này. + + jabber.network + Máy chủ cục bộ + Đa số người dùng nên chọn \'jabber.network\' để có những đề xuất tốt hơn từ toàn thể hệ sinh thái XMPP. + Phương pháp khám phá kênh + Sao lưu + Giới thiệu + Vui lòng bật một tài khoản + Tạo cuộc gọi + Cuộc gọi đến + Cuộc gọi video đến + Đang kết nối + Đã kết nối + Đang chấp nhận cuộc gọi + Đang kết thúc cuộc gọi + Trả lời + Từ chối + Đang khám phá các thiết bị + Đang đổ chuông + Bận + Không thể kết nối cuộc gọi + Đã mất kết nối + Cuộc gọi đã bị rút lại + Lỗi ứng dụng + Cúp máy + Cuộc gọi đang diễn ra + Cuộc gọi video đang diễn ra + Tắt Tor để tạo cuộc gọi + Cuộc gọi đến + Cuộc gọi đến · %s + Cuộc gọi nhỡ · %s + Cuộc gọi đi + Cuộc gọi đi · %s + Cuộc gọi nhỡ + Cuộc gọi âm thanh + Cuộc gọi video + Trợ giúp + Chuyển sang cuộc hội thoại + Micro của bạn không có sẵn + Bạn chỉ có thể có một cuộc gọi trong một lúc. + Quay lại cuộc gọi đang diễn ra + Không thể chuyển máy ảnh + Ghim lên đầu + Bỏ ghim khỏi đầu + Tuyến đường GPS + Không thể sửa tin nhắn + Tất cả cuộc hội thoại + Cuộc hội thoại này + Ảnh đại diện của bạn + Ảnh đại diện cho %s + Được mã hoá bằng OMEMO + Được mã hoá bằng OpenPGP + Không được mã hoá + Thoát + Ghi lại tin nhắn thoại + Phát âm thanh + Tạm dừng âm thanh + Thêm liên hệ, tạo hoặc tham gia cuộc trò chuyện nhóm, hoặc khám phá các kênh + + Xem %1$d thành viên + + + Một số tin nhắn không thể được gửi + + Gửi đi thất bại + Thêm tuỳ chọn + Không tìm thấy ứng dụng nào + Mời vào Conversations + Không thể xử lý lời mời + Máy chủ không hỗ trợ tạo lời mời + Không có tài khoản đang hoạt động nào hỗ trợ tính năng này + Việc sao lưu đã được bắt đầu. Bạn sẽ nhận một thông báo khi việc đó đã hoàn tất. + Không thể bật video. diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 4ef9ecc32893138af264e1e8a4b4d56107a48a4c..4df82f5479b96c4fcbe83b51c2399bbac12d0eed 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -950,4 +950,4 @@ 没有活跃帐户支持此功能 已启动备份。一旦完成,你会收到通知。 无法启用视频 - + diff --git a/src/quicksy/res/values-ar/strings.xml b/src/quicksy/res/values-ar/strings.xml index d33b9d020a90383f0e6a8e7ed2e09f8834523f0f..d0af2d68c38183698e9c70d30bac16679b1dc1c1 100644 --- a/src/quicksy/res/values-ar/strings.xml +++ b/src/quicksy/res/values-ar/strings.xml @@ -1,6 +1,12 @@ + طول الوقت الذي يبقى فيه كويكسي صامتا بعد رؤية النشاط في جهاز آخر + عبر إرسال أثار الأخطاء تقوم بالمساعدة في تطوير برمجة كويكسي إجعل كلّ جهات إتصالك تعلم أنك تستعمل كويكسي + للمواصلة في إستقبال التنبيهات، حتى والشاشة مغلقة، يجب عليك أن تضيف كويكسي إلى قائمة التطبيقات المحميّة. صورة حساب كويكسي إن كويكسي Quicksy غير متوفر في بلدكم. - + لا يمكن التأكد من خادم الهويّة. + خطأ أمني مجهول. + تجاوز الوقت أثناء الإتصال بالخادم. + From af42e3465456155e5cecb1672faca78ab2224bc2 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 16:53:23 +0200 Subject: [PATCH 028/145] Revert "Always show Quote as last action" This reverts commit e528b9f5df59f7b49ae18c73396bd56525493e28. I was originally convinced by the argumentation (quote always in same place) but testing this out for a while really seems to break 'last correct' for me. I use that way more frequently that quote --- src/main/res/menu/message_context.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml index f6592c5aff533b8c6c802d2a5fc76e2a439df44f..f32505203e92f85efd1c705434f4454d0483955d 100644 --- a/src/main/res/menu/message_context.xml +++ b/src/main/res/menu/message_context.xml @@ -20,6 +20,10 @@ android:id="@+id/copy_link" android:title="@string/copy_link" android:visible="false" /> + - - + \ No newline at end of file From 581eb511b98eb0cc709790f0c100a3f37643e2d5 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 18:48:50 +0200 Subject: [PATCH 029/145] version bump to 2.10.0-beta --- CHANGELOG.md | 5 +++++ build.gradle | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9da1fec84bf42358dad7e9203e4ef3d174b61414..90bf2a78e513350a8e6e24ff0247851937c7a424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Version 2.10.0 + +* Show black bars when remote video does not match aspect ratio of screen +* Add setting to prevent screenshots + ### Version 2.9.13 * minor A/V improvements diff --git a/build.gradle b/build.gradle index 7720d848ad966cbaaea042f1f30e7bf3af33eaf1..329614436338ba8931c211be4731a0805d0975d8 100644 --- a/build.gradle +++ b/build.gradle @@ -93,8 +93,8 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 42015 - versionName "2.9.13" + versionCode 42016 + versionName "2.10.0-beta" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId From f975b5ddacb4e47f6ce68afc708e8a68aa3f63f1 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 25 Aug 2021 18:54:00 +0200 Subject: [PATCH 030/145] executePendingTransactions before trying to access secondary_fragment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If we don’t executePendingTransactions we might still access the overview fragment while a replacement operation is in the works. This will lead to two conversationfragments opening. --- .../ui/ConversationsActivity.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index e3f52aa2116d5619c8a4534e8dd9cf7bda264cc7..86f49064a566b4a747290b7fbab0281d970e439b 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -425,16 +425,18 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void openConversation(Conversation conversation, Bundle extras) { - ConversationFragment conversationFragment = (ConversationFragment) getFragmentManager().findFragmentById(R.id.secondary_fragment); + final FragmentManager fragmentManager = getFragmentManager(); + fragmentManager.executePendingTransactions(); + ConversationFragment conversationFragment = (ConversationFragment) fragmentManager.findFragmentById(R.id.secondary_fragment); final boolean mainNeedsRefresh; if (conversationFragment == null) { mainNeedsRefresh = false; - Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment); + final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); if (mainFragment instanceof ConversationFragment) { conversationFragment = (ConversationFragment) mainFragment; } else { conversationFragment = new ConversationFragment(); - FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.main_fragment, conversationFragment); fragmentTransaction.addToBackStack(null); try { @@ -562,17 +564,18 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void initializeFragments() { - FragmentTransaction transaction = getFragmentManager().beginTransaction(); - Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment); - Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment); + final FragmentManager fragmentManager = getFragmentManager(); + FragmentTransaction transaction = fragmentManager.beginTransaction(); + final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); + final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment); if (mainFragment != null) { if (binding.secondaryFragment != null) { if (mainFragment instanceof ConversationFragment) { getFragmentManager().popBackStack(); transaction.remove(mainFragment); transaction.commit(); - getFragmentManager().executePendingTransactions(); - transaction = getFragmentManager().beginTransaction(); + fragmentManager.executePendingTransactions(); + transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.secondary_fragment, mainFragment); transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment()); transaction.commit(); @@ -583,7 +586,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio transaction.remove(secondaryFragment); transaction.commit(); getFragmentManager().executePendingTransactions(); - transaction = getFragmentManager().beginTransaction(); + transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.main_fragment, secondaryFragment); transaction.addToBackStack(null); transaction.commit(); From 9526456d75c0a398e36938849b0cdf4ca73da974 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 25 Aug 2021 18:57:20 +0200 Subject: [PATCH 031/145] pulled translations from transifex --- src/main/res/values-de/strings.xml | 8 +++++++- src/main/res/values-gl/strings.xml | 8 +++++++- src/main/res/values-zh-rCN/strings.xml | 8 +++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index 782b5825fa3a85e1073934a187e4eec73471731b..a460ac3e10b88168753a3ea0e2d8fa58a56e0906 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -132,6 +132,8 @@ Mit dem Einsenden von Absturzberichten hilfst du bei der Weiterentwicklung Lese- und Empfangsbestätigung senden Informiere deine Kontakte, wenn du eine Nachricht empfangen und gelesen hast + Screenshots verhindern + Ausblenden von App-Inhalten im App-Switcher und Blockieren von Screenshots Benutzeroberfläche OpenKeychain verursachte einen Fehler. Fehlerhafter Schlüssel für die Verschlüsselung. @@ -414,6 +416,7 @@ Audio Video Bild + Vektorgrafik PDF-Dokument Android App Kontakt @@ -912,6 +915,7 @@ Verbindung unterbrochen Anruf zurückgenommen App-Fehler + Verifikationsproblem Auflegen Laufender Anruf Laufender Videoanruf @@ -963,4 +967,6 @@ Keine aktiven Konten unterstützen diese Funktion Das Backup wurde gestartet. Du bekommst eine Benachrichtigung sobald es fertig ist. Video kann nicht aktiviert werden. - + Textdokument + + diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index 2baea5f1699e358b95851cbe86e2c90e63e76a77..75e9c45dd742fff4f3f2e8a740dad562424a9819 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -132,6 +132,8 @@ Ao enviar trazas do sistema estás axudando ao desenvolvemento Confirmación de mensaxes Permitir aos teus contactos saber se recibiches e liches as súas mensaxes + Evitar capturas de pantalla + Agochar os contidos da app no cambiador de apps e bloquear pantallazos Interface OpenKeychain producíu un erro. Chave incorrecta para cifrar. @@ -414,6 +416,7 @@ son video imaxe + gráfico de vector documento PDF App Android Contacto @@ -912,6 +915,7 @@ Perdeuse a conexión Chamada cortada Fallo na aplicación + Problema na verificación Colgar Chamada en curso Videochamada en curso @@ -963,4 +967,6 @@ Ningunha conta activa soporta esta función Comezou a creación da copia de apoio. Recibirás unha notificación cando remate. Non se puido activar o vídeo. - + Documento de texto plano + + diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 4df82f5479b96c4fcbe83b51c2399bbac12d0eed..b3dcf959d3bd3bdc997990ced5c15dcf919e3460 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -129,6 +129,8 @@ 通过发送堆栈跟踪,您可以帮助Conversations持续发展 确认消息 让对方知道你收到并阅读了他们的消息 + 防止截屏 + 在应用切换中隐藏应用程序内容并阻止截图 用户界面 OpenKeychain报告一个错误。 错误的密钥 @@ -411,6 +413,7 @@ 音频 视频 图片 + 矢量图 PDF文档 Android App 联系人 @@ -901,6 +904,7 @@ 连接丢失 通话已撤销 程序错误 + 验证问题 挂断 正在进行的通话 正在进行的视频通话 @@ -950,4 +954,6 @@ 没有活跃帐户支持此功能 已启动备份。一旦完成,你会收到通知。 无法启用视频 - + 纯文本文档 + + From 2819545a43e98cd1224262eb624b4b16a052e9ec Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 25 Aug 2021 20:04:47 +0200 Subject: [PATCH 032/145] click on action bar title should open chat details screen --- .../ui/ConferenceDetailsActivity.java | 8 ++ .../ui/ConversationFragment.java | 10 +-- .../ui/ConversationsActivity.java | 58 ++++++++---- .../conversations/ui/util/ActionBarUtil.java | 88 +++++++++++++++++++ 4 files changed, 139 insertions(+), 25 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/ui/util/ActionBarUtil.java diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 574035e2c367d9bd6895b3142746282435a5ea11..d35d4808c29c34182fadda123e6afc3b5aaa7e73 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.ui; +import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -87,6 +88,13 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } }; + public static void open(final Activity activity, final Conversation conversation) { + Intent intent = new Intent(activity, ConferenceDetailsActivity.class); + intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); + intent.putExtra("uuid", conversation.getUuid()); + activity.startActivity(intent); + } + private final OnClickListener mNotifyStatusClickListener = new OnClickListener() { @Override public void onClick(View v) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 7bbd4c8e556317650dcc92e28a72748908164cfe..93a5ad2bfa7102417002497426bd43d81312319c 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -186,10 +186,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke @Override public void onClick(View v) { - Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class); - intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); - intent.putExtra("uuid", conversation.getUuid()); - startActivity(intent); + ConferenceDetailsActivity.open(getActivity(), conversation); } }; private final OnClickListener leaveMuc = new OnClickListener() { @@ -1272,10 +1269,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke activity.switchToContactDetails(conversation.getContact()); break; case R.id.action_muc_details: - Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class); - intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); - intent.putExtra("uuid", conversation.getUuid()); - startActivity(intent); + ConferenceDetailsActivity.open(getActivity(), conversation); break; case R.id.action_invite: startActivityForResult(ChooseContactActivity.create(activity, conversation), REQUEST_INVITE_TO_CONVERSATION); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index 86f49064a566b4a747290b7fbab0281d970e439b..171eea6adbd399ea25edbf951acf046a29f8ea4e 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -30,6 +30,8 @@ package eu.siacs.conversations.ui; +import static eu.siacs.conversations.ui.ConversationFragment.REQUEST_DECRYPT_PGP; + import android.annotation.SuppressLint; import android.app.Activity; import android.app.Fragment; @@ -65,13 +67,16 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.OmemoSetting; import eu.siacs.conversations.databinding.ActivityConversationsBinding; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.interfaces.OnBackendConnected; import eu.siacs.conversations.ui.interfaces.OnConversationArchived; import eu.siacs.conversations.ui.interfaces.OnConversationRead; import eu.siacs.conversations.ui.interfaces.OnConversationSelected; import eu.siacs.conversations.ui.interfaces.OnConversationsListItemUpdated; +import eu.siacs.conversations.ui.util.ActionBarUtil; import eu.siacs.conversations.ui.util.ActivityResult; import eu.siacs.conversations.ui.util.ConversationMenuConfigurator; import eu.siacs.conversations.ui.util.MenuDoubleTabUtil; @@ -83,8 +88,6 @@ import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; -import static eu.siacs.conversations.ui.ConversationFragment.REQUEST_DECRYPT_PGP; - public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged { public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.action.VIEW"; @@ -604,18 +607,38 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio private void invalidateActionBarTitle() { final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment); - if (mainFragment instanceof ConversationFragment) { - final Conversation conversation = ((ConversationFragment) mainFragment).getConversation(); - if (conversation != null) { - actionBar.setTitle(EmojiWrapper.transform(conversation.getName())); - actionBar.setDisplayHomeAsUpEnabled(true); - return; - } + if (actionBar == null) { + return; + } + final FragmentManager fragmentManager = getFragmentManager(); + final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); + if (mainFragment instanceof ConversationFragment) { + final Conversation conversation = ((ConversationFragment) mainFragment).getConversation(); + if (conversation != null) { + actionBar.setTitle(EmojiWrapper.transform(conversation.getName())); + actionBar.setDisplayHomeAsUpEnabled(true); + ActionBarUtil.setActionBarOnClickListener( + binding.toolbar, + (v) -> openConversationDetails(conversation) + ); + return; + } + } + actionBar.setTitle(R.string.app_name); + actionBar.setDisplayHomeAsUpEnabled(false); + ActionBarUtil.resetActionBarOnClickListeners(binding.toolbar); + } + + private void openConversationDetails(final Conversation conversation) { + if (conversation.getMode() == Conversational.MODE_MULTI) { + ConferenceDetailsActivity.open(this, conversation); + } else { + final Contact contact = conversation.getContact(); + if (contact.isSelf()) { + switchToAccount(conversation.getAccount()); + } else { + switchToContactDetails(contact); } - actionBar.setTitle(R.string.app_name); - actionBar.setDisplayHomeAsUpEnabled(false); } } @@ -624,17 +647,18 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (performRedirectIfNecessary(conversation, false)) { return; } - Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment); + final FragmentManager fragmentManager = getFragmentManager(); + final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); if (mainFragment instanceof ConversationFragment) { try { - getFragmentManager().popBackStack(); - } catch (IllegalStateException e) { + fragmentManager.popBackStack(); + } catch (final IllegalStateException e) { Log.w(Config.LOGTAG, "state loss while popping back state after archiving conversation", e); //this usually means activity is no longer active; meaning on the next open we will run through this again } return; } - Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment); + final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment); if (secondaryFragment instanceof ConversationFragment) { if (((ConversationFragment) secondaryFragment).getConversation() == conversation) { Conversation suggestion = ConversationsOverviewFragment.getSuggestion(this, conversation); diff --git a/src/main/java/eu/siacs/conversations/ui/util/ActionBarUtil.java b/src/main/java/eu/siacs/conversations/ui/util/ActionBarUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..80f0ae93eaad04a4a92410b0c56711bc6da65f12 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/util/ActionBarUtil.java @@ -0,0 +1,88 @@ +package eu.siacs.conversations.ui.util; + +import android.content.Context; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.lang.reflect.Field; + +public class ActionBarUtil { + + public static void resetActionBarOnClickListeners(@NonNull View view) { + final View title = findActionBarTitle(view); + final View subtitle = findActionBarSubTitle(view); + if (title != null) { + title.setOnClickListener(null); + } + if (subtitle != null) { + subtitle.setOnClickListener(null); + } + } + + public static void setActionBarOnClickListener(@NonNull View view, + @NonNull final View.OnClickListener onClickListener) { + final View title = findActionBarTitle(view); + final View subtitle = findActionBarSubTitle(view); + if (title != null) { + title.setOnClickListener(onClickListener); + } + if (subtitle != null) { + subtitle.setOnClickListener(onClickListener); + } + } + + private static @Nullable View findActionBarTitle(@NonNull View root) { + return findActionBarItem(root, "action_bar_title", "mTitleTextView"); + } + + private static @Nullable + View findActionBarSubTitle(@NonNull View root) { + return findActionBarItem(root, "action_bar_subtitle", "mSubtitleTextView"); + } + + private static @Nullable View findActionBarItem(@NonNull View root, + @NonNull String resourceName, + @NonNull String toolbarFieldName) { + View result = findViewSupportOrAndroid(root, resourceName); + + if (result == null) { + View actionBar = findViewSupportOrAndroid(root, "action_bar"); + if (actionBar != null) { + result = reflectiveRead(actionBar, toolbarFieldName); + } + } + if (result == null && root.getClass().getName().endsWith("widget.Toolbar")) { + result = reflectiveRead(root, toolbarFieldName); + } + return result; + } + + @SuppressWarnings("ConstantConditions") + private static @Nullable View findViewSupportOrAndroid(@NonNull View root, + @NonNull String resourceName) { + Context context = root.getContext(); + View result = null; + if (result == null) { + int supportID = context.getResources().getIdentifier(resourceName, "id", context.getPackageName()); + result = root.findViewById(supportID); + } + if (result == null) { + int androidID = context.getResources().getIdentifier(resourceName, "id", "android"); + result = root.findViewById(androidID); + } + return result; + } + + @SuppressWarnings("unchecked") + private static T reflectiveRead(@NonNull Object object, @NonNull String fieldName) { + try { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(object); + } catch (final Exception ex) { + return null; + } + } +} From 80d8b6dd880fc51aa928eee4056159e72df796a6 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 27 Aug 2021 07:47:55 +0000 Subject: [PATCH 033/145] Upload APKs after CI --- .github/workflows/android.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 2c0861ffb3e3822a8a27dbe1936a698e6cecc3f0..badbbf5d765fcae525b9cb7b0db484516f85996d 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -30,4 +30,9 @@ jobs: run: ./gradlew assembleConversationsFreeCompatDebug - name: Build Conversations (System) run: ./gradlew assembleConversationsFreeSystemDebug + - uses: actions/upload-artifact@v2 + with: + name: Conversations all-flavors (debug) + path: ./build/outputs/apk/**/debug/Conversations-*.apk + From ea0dc558cb823ef0286e44cafb7080ce6de3427d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 5 Sep 2021 16:29:02 +0200 Subject: [PATCH 034/145] use androidx ExifInterface to parse rotation. fixes #4154 --- build.gradle | 8 +- .../siacs/conversations/entities/Account.java | 4 +- .../persistance/FileBackend.java | 40 +++-- .../siacs/conversations/utils/ExifHelper.java | 161 ------------------ 4 files changed, 35 insertions(+), 178 deletions(-) delete mode 100644 src/main/java/eu/siacs/conversations/utils/ExifHelper.java diff --git a/build.gradle b/build.gradle index 329614436338ba8931c211be4731a0805d0975d8..36bf91f18583003f8a2d8f1a1444a24a2551cd1e 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.1' + classpath 'com.android.tools.build:gradle:7.0.2' } } @@ -42,12 +42,12 @@ dependencies { } conversationsPlaystoreCompatImplementation("com.android.installreferrer:installreferrer:2.2") conversationsPlaystoreSystemImplementation("com.android.installreferrer:installreferrer:2.2") - quicksyPlaystoreCompatImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.0' - quicksyPlaystoreSystemImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.0' + quicksyPlaystoreCompatImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.1' + quicksyPlaystoreSystemImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.1' implementation 'org.sufficientlysecure:openpgp-api:10.0' implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0' implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.exifinterface:exifinterface:1.3.2' + implementation 'androidx.exifinterface:exifinterface:1.3.3' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.emoji:emoji:1.1.0' diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 0796a1c00b3778247d5612d79cf4510ed83a4721..37f8114e870ce6d90df196fe2e3a7400dabebbe0 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -5,6 +5,8 @@ import android.database.Cursor; import android.os.SystemClock; import android.util.Log; +import com.google.common.base.Strings; + import org.json.JSONException; import org.json.JSONObject; @@ -247,7 +249,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable } public String getHostname() { - return this.hostname == null ? "" : this.hostname; + return Strings.nullToEmpty(this.hostname); } public void setHostname(String hostname) { diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index f9aea9da88c891c704e6ecc27250b599e7fe68ea..74c913162734bdcb2296987d509c362892463fd6 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -31,6 +31,7 @@ import android.util.LruCache; import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; import androidx.core.content.FileProvider; +import androidx.exifinterface.media.ExifInterface; import java.io.ByteArrayOutputStream; import java.io.Closeable; @@ -64,7 +65,6 @@ import eu.siacs.conversations.ui.RecordingActivity; import eu.siacs.conversations.ui.util.Attachment; import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.CryptoHelper; -import eu.siacs.conversations.utils.ExifHelper; import eu.siacs.conversations.utils.FileUtils; import eu.siacs.conversations.utils.FileWriterException; import eu.siacs.conversations.utils.MimeUtils; @@ -808,19 +808,34 @@ public class FileBackend { } } - private int getRotation(File file) { - return getRotation(Uri.parse("file://" + file.getAbsolutePath())); + private int getRotation(final File file) { + try (final InputStream inputStream = new FileInputStream(file)) { + return getRotation(inputStream); + } catch (Exception e) { + return 0; + } } - private int getRotation(Uri image) { - InputStream is = null; - try { - is = mXmppConnectionService.getContentResolver().openInputStream(image); - return ExifHelper.getOrientation(is); - } catch (FileNotFoundException e) { + private int getRotation(final Uri image) { + try (final InputStream is = mXmppConnectionService.getContentResolver().openInputStream(image)) { + return is == null ? 0 : getRotation(is); + } catch (final Exception e) { return 0; - } finally { - close(is); + } + } + + private static int getRotation(final InputStream inputStream) throws IOException { + final ExifInterface exif = new ExifInterface(inputStream); + final int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED); + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_180: + return 180; + case ExifInterface.ORIENTATION_ROTATE_90: + return 90; + case ExifInterface.ORIENTATION_ROTATE_270: + return 270; + default: + return 0; } } @@ -1468,7 +1483,8 @@ public class FileBackend { this.resId = resId; } - public @StringRes int getResId() { + public @StringRes + int getResId() { return resId; } } diff --git a/src/main/java/eu/siacs/conversations/utils/ExifHelper.java b/src/main/java/eu/siacs/conversations/utils/ExifHelper.java deleted file mode 100644 index ceda729331050141da1ca51392a6949729bfb5ba..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/utils/ExifHelper.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.siacs.conversations.utils; - -import android.util.Log; - -import java.io.IOException; -import java.io.InputStream; - -public class ExifHelper { - private static final String TAG = "CameraExif"; - - public static int getOrientation(InputStream is) { - if (is == null) { - return 0; - } - - byte[] buf = new byte[8]; - int length = 0; - - // ISO/IEC 10918-1:1993(E) - while (read(is, buf, 2) && (buf[0] & 0xFF) == 0xFF) { - int marker = buf[1] & 0xFF; - - // Check if the marker is a padding. - if (marker == 0xFF) { - continue; - } - - // Check if the marker is SOI or TEM. - if (marker == 0xD8 || marker == 0x01) { - continue; - } - // Check if the marker is EOI or SOS. - if (marker == 0xD9 || marker == 0xDA) { - return 0; - } - - // Get the length and check if it is reasonable. - if (!read(is, buf, 2)) { - return 0; - } - length = pack(buf, 0, 2, false); - if (length < 2) { - Log.e(TAG, "Invalid length"); - return 0; - } - length -= 2; - - // Break if the marker is EXIF in APP1. - if (marker == 0xE1 && length >= 6) { - if (!read(is, buf, 6)) return 0; - length -= 6; - if (pack(buf, 0, 4, false) == 0x45786966 && - pack(buf, 4, 2, false) == 0) { - break; - } - } - - // Skip other markers. - try { - is.skip(length); - } catch (IOException ex) { - return 0; - } - length = 0; - } - - // JEITA CP-3451 Exif Version 2.2 - if (length > 8) { - int offset = 0; - byte[] jpeg = new byte[length]; - if (!read(is, jpeg, length)) { - return 0; - } - - // Identify the byte order. - int tag = pack(jpeg, offset, 4, false); - if (tag != 0x49492A00 && tag != 0x4D4D002A) { - Log.e(TAG, "Invalid byte order"); - return 0; - } - boolean littleEndian = (tag == 0x49492A00); - - // Get the offset and check if it is reasonable. - int count = pack(jpeg, offset + 4, 4, littleEndian) + 2; - if (count < 10 || count > length) { - Log.e(TAG, "Invalid offset"); - return 0; - } - offset += count; - length -= count; - - // Get the count and go through all the elements. - count = pack(jpeg, offset - 2, 2, littleEndian); - while (count-- > 0 && length >= 12) { - // Get the tag and check if it is orientation. - tag = pack(jpeg, offset, 2, littleEndian); - if (tag == 0x0112) { - // We do not really care about type and count, do we? - int orientation = pack(jpeg, offset + 8, 2, littleEndian); - switch (orientation) { - case 1: - return 0; - case 3: - return 180; - case 6: - return 90; - case 8: - return 270; - } - Log.i(TAG, "Unsupported orientation"); - return 0; - } - offset += 12; - length -= 12; - } - } - - Log.i(TAG, "Orientation not found"); - return 0; - } - - private static int pack(byte[] bytes, int offset, int length, - boolean littleEndian) { - int step = 1; - if (littleEndian) { - offset += length - 1; - step = -1; - } - - int value = 0; - while (length-- > 0) { - value = (value << 8) | (bytes[offset] & 0xFF); - offset += step; - } - return value; - } - - private static boolean read(InputStream is, byte[] buf, int length) { - try { - return is.read(buf, 0, length) == length; - } catch (IOException ex) { - return false; - } - } -} From 4f362aafac1eb7c5cb976cc1bdac855fcff1c44c Mon Sep 17 00:00:00 2001 From: Alexei Sorokin Date: Fri, 28 May 2021 10:38:28 +0300 Subject: [PATCH 035/145] make the fulltext index for search more space-efficient It now uses the data from the messages table instead of having a copy of each message. The message UUIDs are no longer part of the index. --- .../eu/siacs/conversations/persistance/DatabaseBackend.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index ed6b80c5b7b4e02c3e5eb2459bbd3e099775b49b..7c0ee3d6dd47cb97c5157b2ff16abc6bb359c78a 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -170,10 +170,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static final String CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX = "CREATE INDEX message_file_path_index ON " + Message.TABLENAME + "(" + Message.RELATIVE_FILE_PATH + ")"; private static final String CREATE_MESSAGE_TYPE_INDEX = "CREATE INDEX message_type_index ON " + Message.TABLENAME + "(" + Message.TYPE + ")"; - private static final String CREATE_MESSAGE_INDEX_TABLE = "CREATE VIRTUAL TABLE messages_index USING fts4 (uuid TEXT PRIMARY KEY, body TEXT, tokenize = 'unicode61')"; + private static final String CREATE_MESSAGE_INDEX_TABLE = "CREATE VIRTUAL TABLE messages_index USING fts4 (uuid,body,notindexed=\"uuid\",content=\"" + Message.TABLENAME + "\",tokenize='unicode61')"; private static final String CREATE_MESSAGE_INSERT_TRIGGER = "CREATE TRIGGER after_message_insert AFTER INSERT ON " + Message.TABLENAME + " BEGIN INSERT INTO messages_index (uuid,body) VALUES (new.uuid,new.body); END;"; private static final String CREATE_MESSAGE_UPDATE_TRIGGER = "CREATE TRIGGER after_message_update UPDATE of uuid,body ON " + Message.TABLENAME + " BEGIN update messages_index set body=new.body,uuid=new.uuid WHERE uuid=old.uuid; END;"; - private static final String COPY_PREEXISTING_ENTRIES = "INSERT INTO messages_index(uuid,body) SELECT uuid,body FROM " + Message.TABLENAME + ";"; + private static final String COPY_PREEXISTING_ENTRIES = "INSERT INTO messages_index(messages_index) VALUES('rebuild');"; private DatabaseBackend(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); From 754773be553770c059e402e4a07b48cf8d2b066b Mon Sep 17 00:00:00 2001 From: Alexei Sorokin Date: Sat, 12 Jun 2021 20:47:53 +0300 Subject: [PATCH 036/145] match messages from the full-text index by rowid "uuid" is a primary key in "messages" but not in "messages_index", the implication of that is very slow matching by UUID. What can be done instead is matching messages_index.rowid to messages.rowid, that is, an always-present clustered index. This not only improves performance of full-text search but also of just updating messages in any shape or form. --- .../conversations/persistance/DatabaseBackend.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 7c0ee3d6dd47cb97c5157b2ff16abc6bb359c78a..4cdb6221469e81a7409af03c54b134dd05917822 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -171,8 +171,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static final String CREATE_MESSAGE_TYPE_INDEX = "CREATE INDEX message_type_index ON " + Message.TABLENAME + "(" + Message.TYPE + ")"; private static final String CREATE_MESSAGE_INDEX_TABLE = "CREATE VIRTUAL TABLE messages_index USING fts4 (uuid,body,notindexed=\"uuid\",content=\"" + Message.TABLENAME + "\",tokenize='unicode61')"; - private static final String CREATE_MESSAGE_INSERT_TRIGGER = "CREATE TRIGGER after_message_insert AFTER INSERT ON " + Message.TABLENAME + " BEGIN INSERT INTO messages_index (uuid,body) VALUES (new.uuid,new.body); END;"; - private static final String CREATE_MESSAGE_UPDATE_TRIGGER = "CREATE TRIGGER after_message_update UPDATE of uuid,body ON " + Message.TABLENAME + " BEGIN update messages_index set body=new.body,uuid=new.uuid WHERE uuid=old.uuid; END;"; + private static final String CREATE_MESSAGE_INSERT_TRIGGER = "CREATE TRIGGER after_message_insert AFTER INSERT ON " + Message.TABLENAME + " BEGIN INSERT INTO messages_index(rowid,uuid,body) VALUES(NEW.rowid,NEW.uuid,NEW.body); END;"; + private static final String CREATE_MESSAGE_UPDATE_TRIGGER = "CREATE TRIGGER after_message_update UPDATE OF uuid,body ON " + Message.TABLENAME + " BEGIN UPDATE messages_index SET body=NEW.body,uuid=NEW.uuid WHERE rowid=OLD.rowid; END;"; + private static final String CREATE_MESSAGE_DELETE_TRIGGER = "CREATE TRIGGER after_message_delete AFTER DELETE ON " + Message.TABLENAME + " BEGIN DELETE FROM messages_index WHERE rowid=OLD.rowid; END;"; private static final String COPY_PREEXISTING_ENTRIES = "INSERT INTO messages_index(messages_index) VALUES('rebuild');"; private DatabaseBackend(Context context) { @@ -262,6 +263,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_MESSAGE_INDEX_TABLE); db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER); db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER); + db.execSQL(CREATE_MESSAGE_DELETE_TRIGGER); } @Override @@ -522,6 +524,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_MESSAGE_INDEX_TABLE); db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER); db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER); + db.execSQL(CREATE_MESSAGE_DELETE_TRIGGER); db.execSQL(COPY_PREEXISTING_ENTRIES); } @@ -787,7 +790,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { final SQLiteDatabase db = this.getReadableDatabase(); final StringBuilder SQL = new StringBuilder(); final String[] selectionArgs; - SQL.append("SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + '.' + Conversation.CONTACTJID + ',' + Conversation.TABLENAME + '.' + Conversation.ACCOUNT + ',' + Conversation.TABLENAME + '.' + Conversation.MODE + " FROM " + Message.TABLENAME + " join " + Conversation.TABLENAME + " on " + Message.TABLENAME + '.' + Message.CONVERSATION + '=' + Conversation.TABLENAME + '.' + Conversation.UUID + " join messages_index ON messages_index.uuid=messages.uuid where " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + ',' + Message.ENCRYPTION_PGP + ',' + Message.ENCRYPTION_DECRYPTION_FAILED + ',' + Message.ENCRYPTION_AXOLOTL_FAILED + ") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + ',' + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ?"); + // TODO: change "ON messages_index.uuid=messages.uuid" to + // "ON messages_index.rowid=messages.rowid" when everyone's migrated. + SQL.append("SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + "." + Conversation.CONTACTJID + "," + Conversation.TABLENAME + "." + Conversation.ACCOUNT + "," + Conversation.TABLENAME + "." + Conversation.MODE + " FROM " + Message.TABLENAME + " JOIN " + Conversation.TABLENAME + " ON " + Message.TABLENAME + "." + Message.CONVERSATION + "=" + Conversation.TABLENAME + "." + Conversation.UUID + " JOIN messages_index ON messages_index.uuid=messages.uuid WHERE " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + "," + Message.ENCRYPTION_PGP + "," + Message.ENCRYPTION_DECRYPTION_FAILED + "," + Message.ENCRYPTION_AXOLOTL_FAILED + ") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + "," + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ?"); if (uuid == null) { selectionArgs = new String[]{FtsUtils.toMatchString(term)}; } else { @@ -1039,6 +1044,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { final SQLiteDatabase db = this.getWritableDatabase(); db.beginTransaction(); String[] args = {conversation.getUuid()}; + // TODO: remove once everyone has the after_message_delete trigger db.delete("messages_index", "uuid in (select uuid from messages where conversationUuid=?)", args); int num = db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args); db.setTransactionSuccessful(); @@ -1049,6 +1055,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void expireOldMessages(long timestamp) { final String[] args = {String.valueOf(timestamp)}; SQLiteDatabase db = this.getReadableDatabase(); + // TODO: remove once everyone has the after_message_delete trigger db.beginTransaction(); db.delete("messages_index", "uuid in (select uuid from messages where timeSent Date: Tue, 7 Sep 2021 16:47:40 +0200 Subject: [PATCH 037/145] add database migration for new fts scheme --- .../persistance/DatabaseBackend.java | 62 ++++++++++++------- .../services/XmppConnectionService.java | 5 +- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 4cdb6221469e81a7409af03c54b134dd05917822..f1cf78760a1ce40c8663bd134849f7d1c63d4400 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -11,6 +11,8 @@ import android.os.SystemClock; import android.util.Base64; import android.util.Log; +import com.google.common.base.Stopwatch; + import org.json.JSONException; import org.json.JSONObject; import org.whispersystems.libsignal.IdentityKey; @@ -62,7 +64,9 @@ import eu.siacs.conversations.xmpp.mam.MamReference; public class DatabaseBackend extends SQLiteOpenHelper { private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 48; + private static final int DATABASE_VERSION = 49; + + private static boolean requiresMessageIndexRebuild = false; private static DatabaseBackend instance = null; private static final String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -187,6 +191,17 @@ public class DatabaseBackend extends SQLiteOpenHelper { return values; } + public static boolean requiresMessageIndexRebuild() { + return requiresMessageIndexRebuild; + } + + public void rebuildMessagesIndex() { + final SQLiteDatabase db = getWritableDatabase(); + final Stopwatch stopwatch = Stopwatch.createStarted(); + db.execSQL(COPY_PREEXISTING_ENTRIES); + Log.d(Config.LOGTAG,"rebuilt message index in "+ stopwatch.stop().toString()); + } + public static synchronized DatabaseBackend getInstance(Context context) { if (instance == null) { instance = new DatabaseBackend(context); @@ -520,14 +535,6 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_RESOLVER_RESULTS_TABLE); } - if (oldVersion < 41 && newVersion >= 41) { - db.execSQL(CREATE_MESSAGE_INDEX_TABLE); - db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER); - db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER); - db.execSQL(CREATE_MESSAGE_DELETE_TRIGGER); - db.execSQL(COPY_PREEXISTING_ENTRIES); - } - if (oldVersion < 42 && newVersion >= 42) { db.execSQL("DROP TRIGGER IF EXISTS after_message_delete"); } @@ -554,10 +561,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 46 && newVersion >= 46) { final long start = SystemClock.elapsedRealtime(); db.rawQuery("PRAGMA secure_delete = FALSE", null).close(); - db.execSQL("update "+Message.TABLENAME+" set "+Message.EDITED+"=NULL"); + db.execSQL("update " + Message.TABLENAME + " set " + Message.EDITED + "=NULL"); db.rawQuery("PRAGMA secure_delete=ON", null).close(); final long diff = SystemClock.elapsedRealtime() - start; - Log.d(Config.LOGTAG,"deleted old edit information in "+diff+"ms"); + Log.d(Config.LOGTAG, "deleted old edit information in " + diff + "ms"); } if (oldVersion < 47 && newVersion >= 47) { db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.PRESENCE_NAME + " TEXT"); @@ -565,6 +572,19 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 48 && newVersion >= 48) { db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.RTP_CAPABILITY + " TEXT"); } + if (oldVersion < 49 && newVersion >= 49) { + db.beginTransaction(); + db.execSQL("DROP TRIGGER IF EXISTS after_message_insert;"); + db.execSQL("DROP TRIGGER IF EXISTS after_message_update;"); + db.execSQL("DROP TABLE IF EXISTS messages_index;"); + db.execSQL(CREATE_MESSAGE_INDEX_TABLE); + db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER); + db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER); + db.execSQL(CREATE_MESSAGE_DELETE_TRIGGER); + requiresMessageIndexRebuild = true; + db.setTransactionSuccessful(); + db.endTransaction(); + } } private void canonicalizeJids(SQLiteDatabase db) { @@ -779,7 +799,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { try { list.add(0, Message.fromCursor(cursor, conversation)); } catch (Exception e) { - Log.e(Config.LOGTAG,"unable to restore message"); + Log.e(Config.LOGTAG, "unable to restore message"); } } cursor.close(); @@ -790,14 +810,12 @@ public class DatabaseBackend extends SQLiteOpenHelper { final SQLiteDatabase db = this.getReadableDatabase(); final StringBuilder SQL = new StringBuilder(); final String[] selectionArgs; - // TODO: change "ON messages_index.uuid=messages.uuid" to - // "ON messages_index.rowid=messages.rowid" when everyone's migrated. - SQL.append("SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + "." + Conversation.CONTACTJID + "," + Conversation.TABLENAME + "." + Conversation.ACCOUNT + "," + Conversation.TABLENAME + "." + Conversation.MODE + " FROM " + Message.TABLENAME + " JOIN " + Conversation.TABLENAME + " ON " + Message.TABLENAME + "." + Message.CONVERSATION + "=" + Conversation.TABLENAME + "." + Conversation.UUID + " JOIN messages_index ON messages_index.uuid=messages.uuid WHERE " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + "," + Message.ENCRYPTION_PGP + "," + Message.ENCRYPTION_DECRYPTION_FAILED + "," + Message.ENCRYPTION_AXOLOTL_FAILED + ") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + "," + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ?"); + SQL.append("SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + "." + Conversation.CONTACTJID + "," + Conversation.TABLENAME + "." + Conversation.ACCOUNT + "," + Conversation.TABLENAME + "." + Conversation.MODE + " FROM " + Message.TABLENAME + " JOIN " + Conversation.TABLENAME + " ON " + Message.TABLENAME + "." + Message.CONVERSATION + "=" + Conversation.TABLENAME + "." + Conversation.UUID + " JOIN messages_index ON messages_index.rowid=messages.rowid WHERE " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + "," + Message.ENCRYPTION_PGP + "," + Message.ENCRYPTION_DECRYPTION_FAILED + "," + Message.ENCRYPTION_AXOLOTL_FAILED + ") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + "," + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ?"); if (uuid == null) { selectionArgs = new String[]{FtsUtils.toMatchString(term)}; } else { selectionArgs = new String[]{FtsUtils.toMatchString(term), uuid}; - SQL.append(" AND "+Conversation.TABLENAME+'.'+Conversation.UUID+"=?"); + SQL.append(" AND " + Conversation.TABLENAME + '.' + Conversation.UUID + "=?"); } SQL.append(" ORDER BY " + Message.TIME_SENT + " DESC limit " + Config.MAX_SEARCH_RESULTS); Log.d(Config.LOGTAG, "search term: " + FtsUtils.toMatchString(term)); @@ -861,7 +879,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public List getFilePathInfo() { final SQLiteDatabase db = this.getReadableDatabase(); - final Cursor cursor = db.query(Message.TABLENAME, new String[]{Message.UUID, Message.RELATIVE_FILE_PATH, Message.DELETED}, "type in (1,2,5) and "+Message.RELATIVE_FILE_PATH+" is not null", null, null, null, null); + final Cursor cursor = db.query(Message.TABLENAME, new String[]{Message.UUID, Message.RELATIVE_FILE_PATH, Message.DELETED}, "type in (1,2,5) and " + Message.RELATIVE_FILE_PATH + " is not null", null, null, null, null); final List list = new ArrayList<>(); while (cursor != null && cursor.moveToNext()) { list.add(new FilePathInfo(cursor.getString(0), cursor.getString(1), cursor.getInt(2) > 0)); @@ -874,7 +892,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public List getRelativeFilePaths(String account, Jid jid, int limit) { SQLiteDatabase db = this.getReadableDatabase(); - final String SQL = "select uuid,relativeFilePath from messages where type in (1,2,5) and deleted=0 and "+Message.RELATIVE_FILE_PATH+" is not null and conversationUuid=(select uuid from conversations where accountUuid=? and (contactJid=? or contactJid like ?)) order by timeSent desc"; + final String SQL = "select uuid,relativeFilePath from messages where type in (1,2,5) and deleted=0 and " + Message.RELATIVE_FILE_PATH + " is not null and conversationUuid=(select uuid from conversations where accountUuid=? and (contactJid=? or contactJid like ?)) order by timeSent desc"; final String[] args = {account, jid.toString(), jid.toString() + "/%"}; Cursor cursor = db.rawQuery(SQL + (limit > 0 ? " limit " + limit : ""), args); List filesPaths = new ArrayList<>(); @@ -899,7 +917,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public boolean deleted; private FilePathInfo(String uuid, String path, boolean deleted) { - super(uuid,path); + super(uuid, path); this.deleted = deleted; } @@ -1043,9 +1061,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { long start = SystemClock.elapsedRealtime(); final SQLiteDatabase db = this.getWritableDatabase(); db.beginTransaction(); - String[] args = {conversation.getUuid()}; - // TODO: remove once everyone has the after_message_delete trigger - db.delete("messages_index", "uuid in (select uuid from messages where conversationUuid=?)", args); + final String[] args = {conversation.getUuid()}; int num = db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args); db.setTransactionSuccessful(); db.endTransaction(); @@ -1055,9 +1071,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void expireOldMessages(long timestamp) { final String[] args = {String.valueOf(timestamp)}; SQLiteDatabase db = this.getReadableDatabase(); - // TODO: remove once everyone has the after_message_delete trigger db.beginTransaction(); - db.delete("messages_index", "uuid in (select uuid from messages where timeSent { - long deletionDate = getAutomaticMessageDeletionDate(); + if (DatabaseBackend.requiresMessageIndexRebuild()) { + DatabaseBackend.getInstance(this).rebuildMessagesIndex(); + } + final long deletionDate = getAutomaticMessageDeletionDate(); mLastExpiryRun.set(SystemClock.elapsedRealtime()); if (deletionDate > 0) { Log.d(Config.LOGTAG, "deleting messages that are older than " + AbstractGenerator.getTimestamp(deletionDate)); From 38a77dbba64bd74a7ddef27e8bc6ac932b12eff1 Mon Sep 17 00:00:00 2001 From: Maximilian Weiler <16721506+maweil@users.noreply.github.com> Date: Sun, 5 Sep 2021 22:39:29 +0200 Subject: [PATCH 038/145] Fix ImportBackupActivity not covered by screenshot prevention feature --- .../eu/siacs/conversations/ui/ImportBackupActivity.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java index a1f4d8d11a37ce20132442579bcfcd8fdb50c60a..90c214ab44f8648a6d95ba24cbebc0a4240461bc 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java @@ -29,6 +29,7 @@ import eu.siacs.conversations.databinding.ActivityImportBackupBinding; import eu.siacs.conversations.databinding.DialogEnterPasswordBinding; import eu.siacs.conversations.services.ImportBackupService; import eu.siacs.conversations.ui.adapter.BackupFileAdapter; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.ThemeHelper; public class ImportBackupActivity extends ActionBarActivity implements ServiceConnection, ImportBackupService.OnBackupFilesLoaded, BackupFileAdapter.OnItemClickedListener, ImportBackupService.OnBackupProcessed { @@ -54,6 +55,12 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo this.binding.list.setAdapter(this.backupFileAdapter); this.backupFileAdapter.setOnItemClickedListener(this); } + + @Override + protected void onResume(){ + super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); + } @Override public boolean onCreateOptionsMenu(final Menu menu) { From 96f0a09a5ddfd7684ba10d297329889a1129aa25 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 7 Sep 2021 16:56:24 +0200 Subject: [PATCH 039/145] pulled translations from transifex --- src/main/res/values-de/strings.xml | 18 +-- src/main/res/values-gl/strings.xml | 6 +- src/main/res/values-hu/strings.xml | 16 +++ src/main/res/values-it/strings.xml | 7 +- src/main/res/values-ja/strings.xml | 162 +++++++++++++------------ src/main/res/values-pl/strings.xml | 8 +- src/main/res/values-pt-rBR/strings.xml | 8 +- 7 files changed, 132 insertions(+), 93 deletions(-) diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index a460ac3e10b88168753a3ea0e2d8fa58a56e0906..c5a7e680b494bf2d28f5094c77492487e086cd1d 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -94,7 +94,7 @@ Gerät auswählen Unverschlüsselt schreiben… Nachricht senden - Sende Nachricht an %s + Nachricht an %s senden OMEMO-verschlüsselt schreiben… v\\OMEMO-verschlüsselte Nachricht senden OpenPGP-verschlüsselt schreiben… @@ -112,7 +112,7 @@ Deine Nachricht konnte nicht verschlüsselt werden, weil dein Kontakt seinen öffentlichen Schlüssel nicht bekannt gibt.\n\nBitte sage deinem Kontakt, er möge OpenPGP einrichten. Keine OpenPGP-Schlüssel gefunden Deine Nachrichten konnten nicht verschlüsselt werden, weil deine Kontakte ihre öffentlichen Schlüssel nicht bekannt geben.\n\nBitte sage ihnen, sie mögen OpenPGP einrichten. - Allgemeines + Allgemein Dateien annehmen Dateien automatisch annehmen, die kleiner sind als … Anhänge @@ -151,7 +151,7 @@ Bilddatei konnte nicht konvertiert werden Datei nicht gefunden Allgemeiner Fehler. Vielleicht hast du keinen Speicherplatz mehr? - Die App, mit der du das Bild ausgesucht hast, hat keine Rechte eingeräumt, um das Bild zu betrachten.\n\nBenutze einen anderen Dateimanager, um ein Bild auszuwählen. + Die App, mit der du das Bild ausgesucht hast, hat nicht die erforderlichen Berechtigungen, um das Bild zu betrachten.\n\nBenutze einen anderen Dateimanager, um ein Bild auszuwählen. Die App, die du zum Teilen dieser Datei verwendet hast, hat nicht die erforderlichen Berechtigungen bereitgestellt. Unbekannt Vorübergehend abgeschaltet @@ -170,7 +170,7 @@ Domain nicht überprüfbar Verstoß gegen die Richtlinien Inkompatibler Server - Stream Fehler + Stream-Fehler Fehler beim Öffnen des Streams Unverschlüsselt OTR @@ -182,7 +182,7 @@ Öffentlichen OpenPGP-Schlüssel veröffentlichen Öffentlichen OpenPGP-Schlüssel verwerfen Bist du sicher, dass du deinen öffentlichen OpenPGP-Schlüssel aus deiner Anwesenheitsmitteilung entfernen möchtest?\nDeine Kontakte können dir dann keine OpenPGP-verschlüsselten Nachrichten senden. - Öffentlicher OpenPGP-Schlüssel veröffentlicht + Öffentlicher OpenPGP-Schlüssel veröffentlicht. Konto aktivieren Bist du dir sicher? Die Löschung deines Kontos löscht deinen gesamten Gesprächsverlauf @@ -252,14 +252,14 @@ Thema Gruppenchat wird beigetreten… Verlassen - Der Kontakt hat dich zur Kontaktliste hinzugefügt + Kontakt hat dich zur Kontaktliste hinzugefügt Auch hinzufügen %s hat bis zu diesem Punkt gelesen %s haben bis zu diesem Punkt gelesen %1$s +%2$d andere haben bis zu diesem Punkt gelesen Alle haben bis zu diesem Punkt gelesen Veröffentlichen - Avatar antippen, um ein Bild aus Galerie auszuwählen + Avatar antippen, um ein Bild aus der Galerie auszuwählen Veröffentliche… Der Server hat die Veröffentlichung des Avatars abgelehnt. Bild konnte nicht konvertiert werden @@ -306,7 +306,7 @@ verwende Konto %s gehostet bei %s %s auf HTTP-Host wird überprüft - Nicht verbunden, bitte später versuchen + Du bist nicht verbunden. Bitte versuche es später noch einmal %s-Größe prüfen %1$s-Größe auf %2$s prüfen Nachrichtenoptionen @@ -792,7 +792,7 @@ Überprüfe %s %s geschickt.]]> Wir haben dir eine weitere SMS mit einem 6-stelligen Code geschickt. - Gib bitte den 6-stellige PIN unten ein. + Gib bitte die 6-stellige PIN unten ein. SMS erneut versenden SMS erneut versenden (%s) Bitte warten (%s) diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index 75e9c45dd742fff4f3f2e8a740dad562424a9819..808a00ca05366a21186a7edf07383f44471e8f89 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -131,7 +131,7 @@ Nunca enviar informe de erros Ao enviar trazas do sistema estás axudando ao desenvolvemento Confirmación de mensaxes - Permitir aos teus contactos saber se recibiches e liches as súas mensaxes + Permitir aos teus contactos saber se recibiches e leches as súas mensaxes Evitar capturas de pantalla Agochar os contidos da app no cambiador de apps e bloquear pantallazos Interface @@ -151,7 +151,7 @@ Non se puido converter o ficheiro de imaxe Arquivo non atopado Erro xeral de I/O. ¿Quedaches sen espazo no disco? - A app utilizada para escoller esta imaxe non deu permisos suficientes para ler o ficheiro.\n\nUsa un xestor de ficheiros diferente para escoller a imaxe + A app utilizada para seleccionar esta imaxe non deu permisos suficientes para ler o ficheiro.\n\nUsa un xestor de ficheiros diferente para elexir a imaxe A app que usaches para compartir este ficheiro non concedeu os permisos suficientes. Descoñecido Desactivado temporalmente @@ -624,7 +624,7 @@ Vas verificar as chaves OMEMO de %1$s despois de premer na ligazón. Esto só é seguro se seguiches esta ligazón desde unha fonte de confianza onde só %2$s a podería ter publicado. Validar chaves OMEMO Mostrar inactivos - Amagar inactivos + Agochar inactivos Retirar confianza a dispositivo Tes a certeza de que queres eliminar a verificación deste dispositivo?\nEste dispositivo e as súas mensaxes serán marcados como \"Non confiable\". diff --git a/src/main/res/values-hu/strings.xml b/src/main/res/values-hu/strings.xml index 7ef8338a6ee9b97593e6fce27092dab1e806ede0..162212928314f90fae219975deb5a3112dd316a6 100644 --- a/src/main/res/values-hu/strings.xml +++ b/src/main/res/values-hu/strings.xml @@ -53,6 +53,7 @@ %s összes partnerének tiltását feloldja? Partner tiltva Tiltva + Szeretné eltávolítani ezt a könyvjelzőkből: %s? Ezzel a könyvjelzővel megjelölt beszélgetései nem lesznek eltávolítva. Új fiók regisztrálása a kiszolgálón Jelszó megváltoztatása a kiszolgálón Megosztás ezzel… @@ -70,6 +71,8 @@ Tiltás feloldása Mentés Rendben + %1$s összeomlott + Azzal, hogy az XMPP fiókja használatával beküldi a veremkiíratásokat, elősegítheti a %1$s alkalmazás folyamatos fejlesztését. Küldés most Sose kérdezzen újra Nem sikerült kapcsolódni a fiókhoz @@ -99,6 +102,7 @@ Küldés titkosítatlanul A visszafejtés sikertelen. Talán nem rendelkezik a megfelelő személyes kulccsal. OpenKeychain + OpenKeychain alkalmazást használja az üzenetek titkosításához és visszafejtéséhez, valamint a személyes kulcsai kezeléséhez.

Ez GPLv3+ szerint licencelt, és elérhető az F-Droid és a Google Play szoftveráruházakban.

(Ezután indítsa újra a %1$s alkalmazást.)]]>
Újraindítás Telepítés Telepítse az OpenKeychain alkalmazás @@ -120,6 +124,7 @@ Csengőhang Értesítési hang Értesítési hang új üzeneteknél + Csengőhang bejövő hívásnál Türelmi idő Az időtartam, amíg az értesítések némítva vannak az egyéb eszközei egyikén történt tevékenység észlelése után. Speciális @@ -127,7 +132,10 @@ A veremkiíratások elküldésével segíti a fejlesztést Üzenetek megerősítése Tudassa a partnereivel, hogy megkapta és elolvasta az üzeneteiket + Képernyőfotó készítésének megakadályozása + Az alkalmazás tartalmának elrejtése az alkalmazásváltóban és a képernyőfotók blokkolása Felhasználói felület + Az OpenKeychain hibát produkált. Rossz kulcs a titkosításhoz. Elfogadás Hiba történt @@ -143,6 +151,8 @@ Nem sikerült átalakítani a képet A fájl nem található Általános bemeneti/kimeneti hiba. Talán elfogyott a tárolóhely? + A kép kiválasztásához használt alkalmazás nem biztosított számunkra elegendő jogosultságot a fájl olvasásához.\n\nHasználjon másik fájlkezelőt a kép kiválasztásához + A fájl megosztásához használt alkalmazás nem biztosított számunkra elegendő jogosultságot. Ismeretlen Átmenetileg letiltva Elérhető @@ -157,6 +167,7 @@ A kiszolgáló nem támogatja a regisztrációt Érvénytelen regisztrációs token A TLS-egyeztetés sikertelen + Tartomány nem ellenőrizhető Irányelv megsértése Nem kompatibilis kiszolgáló Adatfolyamhiba @@ -174,6 +185,7 @@ Az OpenPGP nyilvános kulcs közzé lett téve. Fiók engedélyezése Biztos benne? + A fiók törlésével az összes beszélgetési előzményei is eltávolításra kerülnek Hang rögzítése XMPP-cím XMPP-cím tiltása @@ -204,6 +216,7 @@ egy napja volt aktív %d napja volt aktív Titkosított üzenet. Telepítse az OpenKeychain alkalmazást a visszafejtéshez. + Új OpenPGP titkosítású üzenetek találhatók OpenPGP kulcsazonosító OMEMO ujjlenyomat v\\OMEMO ujjlenyomat @@ -394,6 +407,7 @@ hang videó kép + vektorgrafika PDF-dokumentum Android alkalmazás Partner @@ -447,6 +461,8 @@ A kiszolgáló nem felelős a tartományért Törött Elérhetőség + Távol, ha az eszköz le van zárva + Mutasson „Távoli”-ként, ha az eszköz le van zárva Rezgés kezelése csendes módként Kiterjesztett kapcsolati beállítások Gépnév és port beállításainak megjelenítése egy fiók beállításakor diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 9271b113dba5be836c6c105b61d146fe14da59bc..a4544d79883f5264992d84514314a45f813aea01 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -132,6 +132,8 @@ Se scegli di inviare una segnalazione dell’errore aiuterai lo sviluppo Conferma messaggi Fai sapere ai tuoi contatti quando hai ricevuto e letto i loro messaggi + Impedisci cattura schermo + Nascondi i contenuti dell\'app nell\'elenco recenti e blocca la cattura delle schermate Interfaccia utente OpenKeychain ha generato un errore. Chiave di cifratura non valida. @@ -912,6 +914,7 @@ Connessione persa Chiamata ritirata Errore dell\'app + Problema di verifica Riaggancia Chiamata in corso Chiamata video in corso @@ -963,4 +966,6 @@ Nessun account attivo supporta questa funzione Il backup è iniziato. Riceverai una notifica una volta completato. Impossibile attivare il video. - + Documento di testo + + diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index f1a3daa2f5966c3a61c6ff0e762492b2a36c67ab..1dde2ca623b7f7759b5964d215ac1fdc4f822a34 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -71,23 +71,23 @@ %1$s がクラッシュしました あなたの XMPP アカウントを使用してスタックトレースの送信をすることで、 %1$s の継続的な開発を支援します。 今すぐ送信 - 今後表示しない + 今後は表示しない アカウントに接続できません 複数のアカウントに接続できません タップしてアカウントを管理 - 添付ファイル - 連絡先が連絡先名簿にありません。追加しますか? + ファイルを添付 + 連絡先が連絡先名簿にありません。名簿に追加しますか? 連絡先を追加 配信に失敗しました - 転送用画像の準備中 - 転送用画像の準備中 + 送信用画像の準備中 + 送信用画像の準備中 ファイル共有中。しばらくお待ちください… - 履歴をクリア - 会話履歴をクリア + 履歴を消去 + 会話履歴を消去 この会話のすべてのメッセージを削除してもよろしいですか?\n\n警告: 他のデバイスやサーバーに保存されているメッセージのコピーには影響しません。 ファイルを削除 このファイルを削除してもよろしいですか?\n\n警告: これは、他のデバイスやサーバーに保存されているファイルのコピーは削除しません。 - その後、この会話を閉じる + この後、この会話を閉じる デバイスを選択 暗号化されていないメッセージを送信 メッセージを送信 @@ -96,8 +96,8 @@ v\\OMEMO 暗号化メッセージを送信 OpenPGP 暗号化メッセージを送信 ニックネームが変更されました - 暗号化されていない送信 - 復号に失敗しました。おそらく秘密鍵が正しくないようです。 + 暗号化せずに送信 + 復号に失敗しました。適切な秘密鍵を持っていないのかもしれません。 OpenKeychain OpenKeychain を利用して、メッセージの暗号化および復号、そしてあなたの公開鍵を管理します。

それは GPLv3+ ライセンスの下で、F-Droid および Google Play から利用可能です。

(後で %1$s を再起動してください。)]]>
再起動 @@ -126,9 +126,11 @@ 別のデバイスでの操作を検知した際に、通知を止める時間の長さ 詳細 クラッシュレポートを送信しない - スタックトレースを送信して、Conversations の継続的な開発を支援します - メッセージの確認 + スタックトレースを送信すると、 Conversations の開発を支援します + メッセージを確認 あなたがメッセージを受信して読んだときに、連絡先に知らせます + スクリーンショットを防ぐ + アプリスイッチャーでアプリの内容を隠し、スクリーンショットを防ぐ UI OpenKeychain でエラーが発生しました。 暗号化の鍵が不正です。 @@ -139,15 +141,15 @@ 参加アップデートを送信 参加アップデートを受信 参加アップデートを問合せ - 写真を選択 + 画像を選択 写真を撮影 事前にサブスクリプション要求を許可する 選択したファイルは画像ではありません 画像ファイルを変換できません ファイルが見つかりません - 一般的な I/O エラー。おそらく空き容量がなくなっていませんか? - あなたが画像の選択のために使用したアプリは、読み取りに必要なアクセス権がありません。\n\n別のファイルマネージャを使用して、画像を選択してください。 - このファイルを共有するために使用したアプリに、十分な許可が与えられていませんでした。 + 一般的な入出力エラー。空き容量がなくなっていませんか? + あなたが画像の選択のために使用したアプリは、読み取りに必要なアクセス権がありません。\n\n画像を選択するために、別のファイルマネージャーを使ってください + このファイルを共有するために使用したアプリは、十分な許可が与えられていませんでした。 不明 一時的に無効 オンライン @@ -155,11 +157,11 @@ オフライン 許可されていません サーバーが見つかりません - 接続エラー + 接続なし 登録に失敗しました ユーザー名は既に使用されています 登録が完了しました - サーバーが登録をサポートしていません + サーバーは登録をサポートしていません トークンが無効です TLS ネゴシエーションに失敗しました 検証不可能なドメイン @@ -176,7 +178,7 @@ アバターを公開 OpenPGP 公開鍵を公開 OpenPGP 公開鍵を削除 - 在席告知から OpenPGP 公開鍵を削除してもよろしいですか?\n連絡先はあなたに OpenPGP 暗号化メッセージを送信できなくなります。 + 存在告知から OpenPGP 公開鍵を削除してもよろしいですか?\n連絡先はあなたに OpenPGP 暗号化メッセージを送信できなくなります。 OpenPGP 公開鍵を公開しました。 アカウントを有効にする よろしいですか? @@ -190,11 +192,11 @@ メモリ不足です。画像が大きすぎます %s をお使いのアドレス帳に追加しますか? サーバー情報 - XEP-0313: メッセージ アーカイブ管理 + XEP-0313: メッセージ中央管理 XEP-0280: メッセージ複写 XEP-0352: クライアント状態表示 XEP-0191: ブロッキング コマンド - XEP-0237: 名簿バージョニング + XEP-0237: 名簿バージョン管理 XEP-0198: ストリーム管理 XEP-0215: 外部サービスの発見 XEP-0163: PEP (アバター / OMEMO) @@ -211,7 +213,7 @@ 1日前に会いました %d日前に会いました 暗号化されたメッセージです。復号するには OpenKeychain をインストールしてください。 - 新しい OpenPGP 暗号化されたメッセージが見つかりました + 新しい OpenPGP で暗号化されたメッセージが見つかりました OpenPGP 鍵 ID OMEMO フィンガープリント v\\OMEMO フィンガープリント @@ -233,7 +235,7 @@ 参加 channel@conference.example.com/nick channel@conference.example.com - ブックマークとして保存 + ブックマークに保存 ブックマークを削除 グループチャットを破棄する 談話室を破棄する @@ -254,14 +256,14 @@ 公開 アバターをタップしてギャラリーから画像を選択します 公開中… - サーバーがあなたの公開を拒否しました + サーバーはあなたが公開するものを拒否しました 画像を変換できません - ディスクにアバターを保存できませんでした + ディスクにアバターを保存できません (または長押しするとデフォルトに戻します) ご利用のサーバーは、アバターの公開をサポートしていません ささやいた %s へ - 非公開メッセージを %s に送信 + 非公開メッセージを %s へ送信 接続 このアカウントは既に存在します 次へ @@ -282,15 +284,15 @@ ご利用には注意してください %s について 消音時間 - 開始時間 - 終了時間 + 開始時刻 + 終了時刻 消音時間を有効にする 消音時間の間、通知は無音になります その他 ブックマークと同期 ブックマークに従って、グループチャットに自動で参加します。 OMEMO フィンガープリントをクリップボードにコピーしました - このグループチャットから追い出されています + このグループチャットから出禁にされています このグループチャットはメンバー制です リソース制約 このグループチャットから蹴り出されています @@ -298,7 +300,7 @@ 既にこのグループチャットに参加していません アカウント %s を使用 %s 上でホストされた - HTTP ホストの %s を確認中 + HTTP ホスト上の %s を確認中 接続されていません。後でもう一度お試しください %s の大きさを確認 %2$s で %1$s の大きさを確認 @@ -307,7 +309,7 @@ 引用として貼り付け 元の URL をコピー 再送 - ファイル URL + ファイルの URL URL をクリップボードにコピーしました XMPP アドレスをクリップボードにコピーしました エラーメッセージをクリップボードにコピーしました @@ -350,16 +352,16 @@ グループチャットのサーバーが見つかりませんでした グループチャットを作成できません アカウントのアバター - OMEMO フィンガープリントをクリップボードにコピー + クリップボードに OMEMO フィンガープリントをコピー OMEMO 鍵を再生成 - デバイスをクリア - OMEMO の告知から他のすべてのデバイスをクリアしてもよろしいですか? お使いのデバイスが次回接続したとき、それらは自分自身を再告知しますが、その間に送信されたメッセージを受信できない場合があります。 + デバイスを消去 + OMEMO の告知から他のすべてのデバイスを消去してもよろしいですか? お使いのデバイスが次回接続したとき、それらのデバイスは自分自身を再告知しますが、その間に送信されたメッセージを受信できない場合があります。 この連絡先で使用可能な鍵がありません。\nサーバーから新しい鍵を取得できませんでした。連絡先のサーバーに問題がある可能性があります。 - この連絡先で利用可能な鍵はありません。\n双方に存在サブスクリプションあることを確認してください。 - 何か問題が発生しました。 + この連絡先で利用可能な鍵はありません。\n双方に存在サブスクリプションがあることを確認してください。 + 何か問題が発生しました サーバーから履歴を取得中 - サーバーにこれ以上履歴はありません - アップデート中… + サーバーにこれ以上履歴がありません + 更新中… パスワードを変更しました! パスワードを変更できません パスワードを変更 @@ -383,10 +385,10 @@ グループチャットから削除 談話室から削除 %s の所属を変更できません - グループチャットから追い出す - 談話室から追い出す - あなたは公開談話室から %s を削除しようとしています。その唯一の手段は、そのユーザーを永久に追い出すことです。 - 今すぐ追い出す + グループチャットから出禁にする + 談話室から出禁にする + あなたは公開談話室から %s を削除しようとしています。その唯一の手段は、そのユーザーを永久に出禁にすることです。 + 今すぐ出禁にする %s の役割を変更できません 非公開グループチャットの環境設定 公開談話室の環境設定 @@ -397,18 +399,19 @@ グループチャットのオプションが変更されました! グループチャットのオプションを変更できませんでした なし - 通知があるまで + 通知が来るまで スヌーズ 返信する 既読にする 入力 - Enter は送信 - メッセージの送信に Enter キーを使用する。このオプションが無効でも、常に Ctrl+Enter でメッセージを送信できます。 + Enter で送信 + メッセージの送信に Enter キーを使用します。このオプションが無効でも、常に Ctrl+Enter でメッセージを送信できます。 Enter キーを表示 絵文字キーを Enter キーに変更 音声 ビデオ 画像 + ベクター画像 PDF 文書 Android アプリ 連絡先 @@ -432,15 +435,15 @@ システムの CA を信頼しない すべての証明書を手動で承認する必要があります 証明書を削除 - 手動で承認した証明書を削除します - 手動で承認した証明書はありません + 手動で承認した証明書を削除 + 手動で承認した証明書がありません 証明書を削除 - 選択を削除 - キャンセル + 選択したものを削除 + 中止 - %d 証明書を削除しました + %d個の証明書を削除しました - “送信”ボタンをクイックアクションで置き換えます + “送信”ボタンをクイックアクションで置き換える クイックアクション なし 最近使用した @@ -458,24 +461,24 @@ ダウンロードに失敗しました: ファイルに書き込みできません Tor ネットワークが利用できません バインド失敗 - サーバーがこのドメインに応答しません + そのサーバーはこのドメインに責任を持ちません 壊れています 在席状況 - デバイスがロックされたときは離席 - デバイスがロックされたときは離席と表示 - サイレントモード時は取込中 - デバイスがサイレントモードの時は取込中と表示 + デバイスがロックされているときは離席 + デバイスがロックされているときは離席と表示 + サイレントモードのときは取込中 + デバイスがサイレントモードのときは取込中と表示 バイブレートをサイレントモードとして扱う - デバイスがバイブレート時は取込中と表示 + デバイスがバイブレートのときは取込中と表示 拡張接続設定 アカウントを設定するときにホスト名とポートの設定を表示します xmpp.example.com 証明書でログイン 証明書を解析できません - アーカイブの設定 - サーバー側のアーカイブの設定 - アーカイブの設定を取得しています。しばらくお待ちください… - アーカイブの設定を取得できません + アーカイブ設定 + サーバー側のアーカイブ設定 + アーカイブ設定を取得しています。しばらくお待ちください… + アーカイブ設定を取得できません キャプチャが要求されました 上の画像からテキストを入力してください 信頼されていない証明書チェーン @@ -490,11 +493,11 @@ ホスト名 ポート サーバーまたは .onion のアドレス - これは有効なポート番号ではありません - これは有効なホスト名ではありません - %1$d / %2$d アカウントが接続しました + 有効なポート番号ではありません + 有効なホスト名ではありません + %2$d個中%1$d個のアカウントが接続しました - %d メッセージ + %d件のメッセージ さらにメッセージを読み込む %s でファイル共有 @@ -515,7 +518,7 @@ 常に 大きい画像のみ 電池最適化が有効 - お使いのデバイスは、%1$s で通知の遅延やメッセージの損失につながる可能性のある、重い電池の最適化を使用しています。\nそれらを無効にすることをお勧めします。 + お使いのデバイスは、%1$s で通知の遅延やメッセージの損失につながる可能性のある、重い電池の最適化を使用しています。\nそれを無効にすることをお勧めします。 お使いのデバイスは、%1$s で通知の遅延やメッセージの損失につながる可能性のある、重い電池の最適化を使用しています。\n\n今、それらを無効にするように求められます。 無効 選択した範囲が大きすぎます @@ -588,19 +591,19 @@ エラーメッセージを表示 エラーメッセージ データセーバーを有効にしました - お使いのオペレーティングシステムは、%1$s がバックグラウンドのときにインターネットにアクセスすることを制限しています。新しいメッセージの通知を受信するには、“データセーバー”がオンになっているとき、%1$s に無制限のアクセスを許可する必要があります。\n%1$s は可能なときにデータを保存するための努力をします。 + お使いのオペレーティングシステムは、%1$s がバックグラウンドのときにインターネットにアクセスすることを制限しています。新しいメッセージの通知を受信するには、“データセーバー”がオンならば、%1$s に無制限のアクセスを許可する必要があります。\n%1$s は可能なときにデータを保存するための努力をします。 お使いのデバイスは、%1$s のデータセーバーを無効にできません。 一時ファイルを作成できません このデバイスは検証済です フィンガープリントをコピー - 所有するすべての OMEMO 鍵を確認完了 + 所有するすべての OMEMO 鍵を検証完了 バーコードに、この会話のフィンガープリントが含まれていません。 フィンガープリントを検証しました カメラを使用して連絡先のバーコードをスキャンします 鍵が取得されるのをお待ちください - バーコードとして共有 - XMPP URI として共有 - HTTP リンクとして共有 + バーコードで共有 + XMPP URI で共有 + HTTP リンクで共有 検証前に白紙信託する 認証されていない連絡先からの新規デバイスを信頼するが、認証されている連絡先からの新規デバイスについては手動での確認を求める。 OMEMO 鍵を盲目的に信用していた。つまり、他の人かもしれないし、誰かが盗聴しているかもしれない。 @@ -704,10 +707,10 @@ このデバイス向けにメッセージは暗号化されませんでした。 OMEMO メッセージの復号に失敗しました。 元に戻す - 場所の共有が無効 + 位置の共有が無効 位置を固定 位置を固定しない - 場所をコピー + 位置をコピー 位置を共有 位置を共有 位置を表示 @@ -718,8 +721,8 @@ メッセージを検索 GIF 会話を表示 - 場所共有プラグイン - 場所共有プラグインの代わりに、組み込みの地図を使う + 位置共有プラグイン + 位置共有プラグインの代わりに、組み込みの地図を使う ウェブアドレスをコピー XMPP アドレスをコピー S3 の HTTP ファイル共有 @@ -735,7 +738,7 @@ グループチャット名 このグループチャットは破棄されました フォアグラウンドサービス - この通知カテゴリーは %1$s が実行されていることを表示する、永続的な通知を表示するために使用されます。 + この通知カテゴリーは %1$s が実行していることを表示する、永続的な通知を表示するために使用されます。 ステータス情報 接続の問題 この通知カテゴリーは、アカウントへの接続に問題があった場合に、通知を表示するために使用されます。 @@ -744,6 +747,7 @@ メッセージ 着信 発信 + サイレントメッセージ この通知グループは、音を鳴らしてはいけない通知を表示するために使用します。例えば、他のデバイスでアクティブになっているときなどです (猶予期間)。 配信に失敗 メッセージ通知設定 @@ -813,7 +817,7 @@ 電子書籍 原物 (非圧縮) …で開く - Conversations プロフィール写真 + Conversations プロフィール画像 アカウントを選択 バックアップを復元 復元 @@ -940,4 +944,6 @@ この機能をサポートするアクティブなアカウントがありません バックアップを開始しました。 バックアップが完了すると通知が届きます。 映像を有効化できません。 - + プレーンテキスト文書 + + diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index eef1ba92bce7121c70200b99beb83ef7c5edf059..7325816ed51dee7ffe32697e5d403d566912a0e5 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -138,6 +138,8 @@ Wysyłając nam ślady stosu pomagasz w rozwoju Potwierdzenia wiadomości Zezwól na wysyłanie do osób z twojej listy kontaktów informacji o tym, kiedy otrzymałeś i przeczytałeś wiadomość od nich + Zapobiegaj zrzutom ekranu + Ukryj zawartość aplikacji w podglądzie aplikacji oraz zablokuj zrzuty ekranu UI OpenKeychain zgłosiło błąd. Zły klucz szyfrowania. @@ -420,6 +422,7 @@ plik audio plik wideo obraz + grafika wektorowa Dokument PDF Aplikacja Androida Kontakt @@ -935,6 +938,7 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż Utracono połączenie Anulowane połączenie Błąd aplikacji + Problem z weryfikacją Rozłącz Połączenie wychodzące Wideorozmowa wychodząca @@ -990,4 +994,6 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż Nie ma aktywnych kont wspierających tę funkcję Tworzenie kopii zapasowej się rozpoczęło. Dostaniesz powiadomienie kiedy się zakończy. Nie można włączyć wideo. - + Dokument zwykłego tekstu + + diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index ffc3c0682718673100c96b54971a7cdbc8384db6..5e15e47c15e6e9cc9e211bf74a0c9f492194392a 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -132,6 +132,8 @@ Ao enviar os stack traces você está colaborando com o desenvolvimento Confirmação de mensagens Permite que seus contatos saibam quando você recebeu e leu as mensagens deles. + Impedir capturas de tela + Esconde o conteúdo do app no alternador de apps e bloqueia capturas de tela IU O OpenKeychain produziu um erro. Chave ruim para a criptografia @@ -414,6 +416,7 @@ áudio vídeo imagem + gráfico vetorial Documento PDF Aplicativo Android Contato @@ -912,6 +915,7 @@ Conexão perdida Chamada rejeitada Falha no aplicativo + Problema de verificação Desligar Chamada em andamento Chamada de vídeo em andamento @@ -963,4 +967,6 @@ Nenhuma conta ativa suporta esse recurso O backup foi iniciado. Você receberá uma notificação assim que ele for concluído. Não foi possível habilitar o vídeo. - + Documento em texto puro + + From 8d45cc5827f9fb3f6535749cc7d5c624fc08f5b5 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sat, 4 Sep 2021 15:56:37 +0200 Subject: [PATCH 040/145] Fixing trailing characters treated as part of URI error (#3938). --- .../java/eu/siacs/conversations/utils/Patterns.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/Patterns.java b/src/main/java/eu/siacs/conversations/utils/Patterns.java index fae13aaea79f811be0a1f4c64fda72178e1d65b2..d220b332689389ad0c2d135a0b96b1a30319e415 100644 --- a/src/main/java/eu/siacs/conversations/utils/Patterns.java +++ b/src/main/java/eu/siacs/conversations/utils/Patterns.java @@ -304,9 +304,15 @@ public class Patterns { + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@"; private static final String PORT_NUMBER = "\\:\\d{1,5}"; + private static final String PATH_AND_QUERY_CHARS_WITHOUT_SLASH = + "\\;\\?\\:\\@\\&\\=\\#\\~" // plus optional query params + + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_"; private static final String PATH_AND_QUERY = "\\/(?:(?:[" + LABEL_CHAR - + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus optional query params - + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*"; + + PATH_AND_QUERY_CHARS_WITHOUT_SLASH + + "]+[^" + PATH_AND_QUERY_CHARS_WITHOUT_SLASH + "\\s]" // path and query chars must not be trailing + + "|\\/" // trailing slashes are fine + + ")|(?:\\%[a-fA-F0-9]{2}))*"; + /** * Regular expression pattern to match most part of RFC 3987 * Internationalized URLs, aka IRIs. From 4040d5f6471d6c958321902f335014bdaeedbfa2 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sat, 4 Sep 2021 16:01:23 +0200 Subject: [PATCH 041/145] Treat dollar signs as URI chars (fixing #3859). --- src/main/java/eu/siacs/conversations/utils/Patterns.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/utils/Patterns.java b/src/main/java/eu/siacs/conversations/utils/Patterns.java index d220b332689389ad0c2d135a0b96b1a30319e415..0ee96b74c6c325e352518fb7d6b87f0d9019ef34 100644 --- a/src/main/java/eu/siacs/conversations/utils/Patterns.java +++ b/src/main/java/eu/siacs/conversations/utils/Patterns.java @@ -306,7 +306,7 @@ public class Patterns { private static final String PORT_NUMBER = "\\:\\d{1,5}"; private static final String PATH_AND_QUERY_CHARS_WITHOUT_SLASH = "\\;\\?\\:\\@\\&\\=\\#\\~" // plus optional query params - + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_"; + + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_\\$"; private static final String PATH_AND_QUERY = "\\/(?:(?:[" + LABEL_CHAR + PATH_AND_QUERY_CHARS_WITHOUT_SLASH + "]+[^" + PATH_AND_QUERY_CHARS_WITHOUT_SLASH + "\\s]" // path and query chars must not be trailing From ca08c27eef6493d799fdd6600ac8cc9e36ba0002 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sat, 4 Sep 2021 17:33:25 +0200 Subject: [PATCH 042/145] Parse IPv6 URIs (#3841). --- .../siacs/conversations/utils/Patterns.java | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/Patterns.java b/src/main/java/eu/siacs/conversations/utils/Patterns.java index 0ee96b74c6c325e352518fb7d6b87f0d9019ef34..02a4373452d4523204fcb617bed81efa4a9fda41 100644 --- a/src/main/java/eu/siacs/conversations/utils/Patterns.java +++ b/src/main/java/eu/siacs/conversations/utils/Patterns.java @@ -254,6 +254,39 @@ public class Patterns { + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" + "|[1-9][0-9]|[0-9]))"); + + /** + * IPv6 address matcher for + * IPv6 addresses + * zero compressed IPv6 addresses (section 2.2 of rfc5952) + * link-local IPv6 addresses with zone index (section 11 of rfc4007) + * IPv4-Embedded IPv6 Address (section 2 of rfc6052) + * IPv4-mapped IPv6 addresses (section 2.1 of rfc2765) + * IPv4-translated addresses (section 2.1 of rfc2765) + * + * Taken from https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses/17871737#17871737 + */ + public static final Pattern IP6_ADDRESS + = Pattern.compile( + "\\[" + + "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + + "([0-9a-fA-F]{1,4}:){1,7}:|" + + "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + + "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + + "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + + "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + + "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + + "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + + ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + + "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + + "::(ffff(:0{1,4}){0,1}:){0,1}" + + "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + + "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|" + + "([0-9a-fA-F]{1,4}:){1,4}:" + + "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + + "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))" + + "\\]" + ); /** * Valid UCS characters defined in RFC 3987. Excludes space characters. */ @@ -296,7 +329,7 @@ public class Patterns { private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" +")"; private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD; public static final Pattern DOMAIN_NAME - = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")"); + = Pattern.compile("(" + HOST_NAME + "|" + IP6_ADDRESS + "|" + IP_ADDRESS +")"); private static final String PROTOCOL = "(?i:http|https|rtsp):\\/\\/"; /* A word boundary or end of input. This is to stop foo.sure from matching as foo.su */ private static final String WORD_BOUNDARY = "(?:\\b|$|^)"; @@ -341,12 +374,12 @@ public class Patterns { * {@link #IP_ADDRESS} */ private static final Pattern STRICT_DOMAIN_NAME - = Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + ")"); + = Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + "|" + IP6_ADDRESS + ")"); /** * Regular expression that matches domain names without a TLD */ private static final String RELAXED_DOMAIN_NAME = - "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS + ")"; + "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS + "|" + IP6_ADDRESS + ")"; /** * Regular expression to match strings that do not start with a supported protocol. The TLDs * are expected to be one of the known TLDs. From 63f5f8c89d46ce5bbb100d9f1de39a7bf37e9e92 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 8 Sep 2021 10:47:34 +0200 Subject: [PATCH 043/145] modify TODOs in JingleRtpConnection upon better understanding of the WebRTC stack --- .../xmpp/jingle/JingleRtpConnection.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 67c32d72aa425be24e58cb1879ed86c001011bd7..6afcff336336be8241dc50a244a55a3b3a8eaa08 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1038,6 +1038,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web return RtpEndUserState.CONNECTING; } case SESSION_ACCEPTED: + //TODO refactor this out into separate method (that uses switch for better readability) final PeerConnection.PeerConnectionState state; try { state = webRTCWrapper.getState(); @@ -1340,9 +1341,13 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web if (newState == PeerConnection.PeerConnectionState.CLOSED && this.rtpConnectionEnded == 0) { this.rtpConnectionEnded = SystemClock.elapsedRealtime(); } - //TODO 'DISCONNECTED' might be an opportunity to renew the offer and send a transport-replace - //TODO exact syntax is yet to be determined but transport-replace sounds like the most reasonable - //as there is no content-replace + //TODO 'failed' means we need to restart ICE + // + //TODO 'disconnected' can probably be ignored as "This is a less stringent test than failed + // and may trigger intermittently and resolve just as spontaneously on less reliable networks, + // or during temporary disconnections. When the problem resolves, the connection may return + // to the connected state." + // Obviously the UI needs to reflect this new state with a 'reconnecting' display or something if (Arrays.asList(PeerConnection.PeerConnectionState.FAILED, PeerConnection.PeerConnectionState.DISCONNECTED).contains(newState)) { if (isTerminated()) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not sending session-terminate after connectivity error because session is already in state " + this.state); From 5a9777f7f1069370f029228dc8ac20458465812c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 8 Sep 2021 11:13:22 +0200 Subject: [PATCH 044/145] version bump to 2.10.0-beta.2 + changelog --- CHANGELOG.md | 1 + build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90bf2a78e513350a8e6e24ff0247851937c7a424..48a5feff5391bd9d9a44a3de7a71a7ffaafe4e16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Version 2.10.0 * Show black bars when remote video does not match aspect ratio of screen +* Improve search performance * Add setting to prevent screenshots ### Version 2.9.13 diff --git a/build.gradle b/build.gradle index 36bf91f18583003f8a2d8f1a1444a24a2551cd1e..4a80ccaaf872f5cc9ff5c6c5e8e530087bc1d9b6 100644 --- a/build.gradle +++ b/build.gradle @@ -93,8 +93,8 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 42016 - versionName "2.10.0-beta" + versionCode 42017 + versionName "2.10.0-beta.2" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId From dfeeaff74ca05d37b1a0fe5e721e12832e2124dc Mon Sep 17 00:00:00 2001 From: Millesimus Date: Wed, 8 Sep 2021 15:25:47 +0200 Subject: [PATCH 045/145] >.< should not be rendered as quote (bugfix). --- src/main/java/eu/siacs/conversations/utils/UIHelper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 4c2c0b5855039658368b535ddceedcc766800c07..26732b501e45733b3738f2fa20edb72ab3a64fb0 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -422,6 +422,7 @@ public class UIHelper { final char first = body.charAt(pos + 1); return first == ';' || first == ':' + || first == '.' // do not quote >.< (but >>.<) || closingBeforeWhitespace(body, pos + 1); } } From 4d36231fa57a9faed345e625a474c807c21272ef Mon Sep 17 00:00:00 2001 From: Millesimus Date: Wed, 8 Sep 2021 16:32:44 +0200 Subject: [PATCH 046/145] >.< should be quoteable (bugfix). --- src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java index 4e4617cab78c9d7ee99c175ca2833e5602dea4e4..ac2913037c85d579858572991bc44def0884ebd0 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java @@ -77,10 +77,10 @@ public class QuoteHelper { } public static boolean isNestedTooDeeply (CharSequence line){ - if (isPositionQuoteCharacter(line, 0)) { + if (isPositionQuoteStart(line, 0)) { int nestingDepth = 1; for (int i = 1; i < line.length(); i++) { - if (isPositionQuoteCharacter(line, i)) { + if (isPositionQuoteStart(line, i)) { nestingDepth++; } if (nestingDepth > (Config.QUOTING_MAX_DEPTH - 1)) { From 3135550b83a4aa05a080567e8f7914040fc723e4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 8 Sep 2021 20:53:11 +0200 Subject: [PATCH 047/145] pulled translations from transifex --- src/main/res/values-ja/strings.xml | 2 +- src/main/res/values-tr-rTR/strings.xml | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 1dde2ca623b7f7759b5964d215ac1fdc4f822a34..a5cb3837e3220993a91fe5696ed9608bb4fd9193 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -727,7 +727,7 @@ XMPP アドレスをコピー S3 の HTTP ファイル共有 直接検索 - ‘会話の開始’画面でキーボードを開き、検索フィールドにカーソルを置きます + ‘会話を開始’画面でキーボードを開き、検索フィールドにカーソルを置きます グループチャットのアバター ホストはグループチャットのアバターをサポートしていません 所有者だけが、グループチャットのアバターを変更可能です diff --git a/src/main/res/values-tr-rTR/strings.xml b/src/main/res/values-tr-rTR/strings.xml index 4f0084189198bc0dde733f5135a5745c8a832dae..72094ced9684ff3211ab65813a90dfe4ed445b3a 100644 --- a/src/main/res/values-tr-rTR/strings.xml +++ b/src/main/res/values-tr-rTR/strings.xml @@ -132,6 +132,8 @@ Yığın izi göndererek gelişime yardımcı oluyorsunuz. İletileri onayla Onların iletilerini aldığınızda ve okuduğunuzda, kişilerinizin bunu bilmesini sağlayın + Ekran görüntülerini engelle + Uygulama anahtarlayıcısında uygulama içeriklerini sakla ve ekran görüntülerini engelle Arabirim OpenKeychain bir hata verdi. Kötü anahar şifrelemesi. @@ -414,6 +416,7 @@ ses video görüntü + Vektör grafik PDF belgesi Android uygulaması Kişi @@ -912,6 +915,7 @@ Bağlantı kesildi Geri çekilmiş arama Uygulama hatası + Doğrulama sorunu Çağrıyı sonlandır Devam eden arama Deaam eden görüntülü arama @@ -963,4 +967,6 @@ Bu özelliği destekleyen aktif bir hesap yok Yedekleme başlatıldı. Tamamlandığı zaman bir bildirim alacaksınız. Video etkinleştirilemedi - + Düz metin dosyası + + From 2957bccb33fe36b426feb88eedfc675a3b03a28c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 8 Sep 2021 21:30:21 +0200 Subject: [PATCH 048/145] Revert "Fixing trailing characters treated as part of URI error (#3938)." This reverts commit 8d45cc5827f9fb3f6535749cc7d5c624fc08f5b5. --- .../java/eu/siacs/conversations/utils/Patterns.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/Patterns.java b/src/main/java/eu/siacs/conversations/utils/Patterns.java index 02a4373452d4523204fcb617bed81efa4a9fda41..026951b22eed95f2943f46c8a44266e1dabe8058 100644 --- a/src/main/java/eu/siacs/conversations/utils/Patterns.java +++ b/src/main/java/eu/siacs/conversations/utils/Patterns.java @@ -337,15 +337,9 @@ public class Patterns { + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@"; private static final String PORT_NUMBER = "\\:\\d{1,5}"; - private static final String PATH_AND_QUERY_CHARS_WITHOUT_SLASH = - "\\;\\?\\:\\@\\&\\=\\#\\~" // plus optional query params - + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_\\$"; private static final String PATH_AND_QUERY = "\\/(?:(?:[" + LABEL_CHAR - + PATH_AND_QUERY_CHARS_WITHOUT_SLASH - + "]+[^" + PATH_AND_QUERY_CHARS_WITHOUT_SLASH + "\\s]" // path and query chars must not be trailing - + "|\\/" // trailing slashes are fine - + ")|(?:\\%[a-fA-F0-9]{2}))*"; - + + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus optional query params + + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_\\$])|(?:\\%[a-fA-F0-9]{2}))*"; /** * Regular expression pattern to match most part of RFC 3987 * Internationalized URLs, aka IRIs. @@ -516,4 +510,4 @@ public class Patterns { * Do not create this static utility class. */ private Patterns() {} -} \ No newline at end of file +} From 8d9c51d7558a58013f8f4c517c760499cfa3d78e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 10 Sep 2021 10:25:31 +0200 Subject: [PATCH 049/145] pulled translations from transifex --- src/main/res/values-it/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index a4544d79883f5264992d84514314a45f813aea01..a1835ab4cb7add9238d34ba8c252f20678ddd483 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -416,6 +416,7 @@ audio video immagine + grafica vettoriale Documento PDF Applicazione Android Contatto From d436c5f856d1c242b0f39fb7c2828af6ad42de81 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 10 Sep 2021 18:46:10 +0200 Subject: [PATCH 050/145] catch exception when trying to read display name. fixes #4163 --- src/main/java/eu/siacs/conversations/utils/MimeUtils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java index b320c0c1178dc597d7533ff20dbd79d6b9f0319a..76d4911b82d5045bd111a2682fb940cff82caed0 100644 --- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java @@ -568,6 +568,8 @@ public final class MimeUtils { if (cursor != null && cursor.moveToFirst()) { return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); } + } catch (Exception e) { + return null; } return null; } From 25f137441b2753bf4438c4b3ebd09703fdb8b13b Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 10 Sep 2021 18:46:37 +0200 Subject: [PATCH 051/145] catch security exception when viewing file from media preview --- .../siacs/conversations/ui/adapter/MediaPreviewAdapter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java index 73f24fe1541208333a8b8c2a3b62591b6ec1627a..44a3835e0e881bc7feeb36c74a26032f72b9a9a4 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java @@ -75,8 +75,10 @@ public class MediaPreviewAdapter extends RecyclerView.Adapter Date: Fri, 10 Sep 2021 19:07:57 +0200 Subject: [PATCH 052/145] run file observer on its own thread. fixes #4164 --- .../services/XmppConnectionService.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 1f3b666ac6275b276a35a7f3fb003d6ef2de5b05..1c91c1ee867c2cfbd897690466d1ae7e9f5f82fa 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -75,6 +75,8 @@ import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -184,8 +186,9 @@ public class XmppConnectionService extends Service { private static final String SETTING_LAST_ACTIVITY_TS = "last_activity_timestamp"; public final CountDownLatch restoredFromDatabaseLatch = new CountDownLatch(1); - private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor("FileAdding"); - private final SerialSingleThreadExecutor mVideoCompressionExecutor = new SerialSingleThreadExecutor("VideoCompression"); + private final static Executor FILE_OBSERVER_EXECUTOR = Executors.newSingleThreadExecutor(); + private final static Executor FILE_ATTACHMENT_EXECUTOR = Executors.newSingleThreadExecutor(); + private final static SerialSingleThreadExecutor VIDEO_COMPRESSION_EXECUTOR = new SerialSingleThreadExecutor("VideoCompression"); private final SerialSingleThreadExecutor mDatabaseWriterExecutor = new SerialSingleThreadExecutor("DatabaseWriter"); private final SerialSingleThreadExecutor mDatabaseReaderExecutor = new SerialSingleThreadExecutor("DatabaseReader"); private final SerialSingleThreadExecutor mNotificationExecutor = new SerialSingleThreadExecutor("NotificationExecutor"); @@ -563,9 +566,9 @@ public class XmppConnectionService extends Service { Log.d(Config.LOGTAG, "counterpart=" + message.getCounterpart()); final AttachFileToConversationRunnable runnable = new AttachFileToConversationRunnable(this, uri, type, message, callback); if (runnable.isVideoMessage()) { - mVideoCompressionExecutor.execute(runnable); + VIDEO_COMPRESSION_EXECUTOR.execute(runnable); } else { - mFileAddingExecutor.execute(runnable); + FILE_ATTACHMENT_EXECUTOR.execute(runnable); } } @@ -592,7 +595,7 @@ public class XmppConnectionService extends Service { message.setType(Message.TYPE_IMAGE); } Log.d(Config.LOGTAG, "attachImage: type=" + message.getType()); - mFileAddingExecutor.execute(() -> { + FILE_ATTACHMENT_EXECUTOR.execute(() -> { try { getFileBackend().copyImageToPrivateStorage(message, uri); } catch (FileBackend.ImageCompressionException e) { @@ -1146,11 +1149,11 @@ public class XmppConnectionService extends Service { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { startContactObserver(); } - mFileAddingExecutor.execute(fileBackend::deleteHistoricAvatarPath); + FILE_OBSERVER_EXECUTOR.execute(fileBackend::deleteHistoricAvatarPath); if (Compatibility.hasStoragePermission(this)) { Log.d(Config.LOGTAG, "starting file observer"); - mFileAddingExecutor.execute(this.fileObserver::startWatching); - mFileAddingExecutor.execute(this::checkForDeletedFiles); + FILE_OBSERVER_EXECUTOR.execute(this.fileObserver::startWatching); + FILE_OBSERVER_EXECUTOR.execute(this::checkForDeletedFiles); } if (Config.supportOpenPgp()) { this.pgpServiceConnection = new OpenPgpServiceConnection(this, "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() { @@ -1266,8 +1269,8 @@ public class XmppConnectionService extends Service { public void restartFileObserver() { Log.d(Config.LOGTAG, "restarting file observer"); - mFileAddingExecutor.execute(this.fileObserver::restartWatching); - mFileAddingExecutor.execute(this::checkForDeletedFiles); + FILE_OBSERVER_EXECUTOR.execute(this.fileObserver::restartWatching); + FILE_OBSERVER_EXECUTOR.execute(this::checkForDeletedFiles); } public void toggleScreenEventReceiver() { @@ -1927,7 +1930,7 @@ public class XmppConnectionService extends Service { private void restoreMessages(Conversation conversation) { conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); conversation.findUnsentTextMessages(message -> markMessage(message, Message.STATUS_WAITING)); - conversation.findUnreadMessages(message -> mNotificationService.pushFromBacklog(message)); + conversation.findUnreadMessages(mNotificationService::pushFromBacklog); } public void loadPhoneContacts() { From 68d8e2b9cf4b3b89b06fa0118f3bf9d1e06ee589 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 11 Sep 2021 09:55:44 +0200 Subject: [PATCH 053/145] delete targe file after unsuccessful image compression --- .../persistance/FileBackend.java | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 74c913162734bdcb2296987d509c362892463fd6..48ffae422f1741aae56e680255e8b4d074f0226b 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -33,6 +33,8 @@ import androidx.annotation.StringRes; import androidx.core.content.FileProvider; import androidx.exifinterface.media.ExifInterface; +import com.google.common.io.ByteStreams; + import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; @@ -627,20 +629,20 @@ public class FileBackend { private void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException { Log.d(Config.LOGTAG, "copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath()); file.getParentFile().mkdirs(); - OutputStream os = null; - InputStream is = null; try { file.createNewFile(); - os = new FileOutputStream(file); - is = mXmppConnectionService.getContentResolver().openInputStream(uri); - byte[] buffer = new byte[1024]; - int length; - while ((length = is.read(buffer)) > 0) { - try { - os.write(buffer, 0, length); - } catch (IOException e) { - throw new FileWriterException(); - } + } catch (IOException e) { + throw new FileCopyException(R.string.error_unable_to_create_temporary_file); + } + try (final OutputStream os = new FileOutputStream(file); + final InputStream is = mXmppConnectionService.getContentResolver().openInputStream(uri)) { + if (is == null) { + throw new FileCopyException(R.string.error_file_not_found); + } + try { + ByteStreams.copy(is, os); + } catch (IOException e) { + throw new FileWriterException(); } try { os.flush(); @@ -648,16 +650,17 @@ public class FileBackend { throw new FileWriterException(); } } catch (final FileNotFoundException e) { + cleanup(file); throw new FileCopyException(R.string.error_file_not_found); } catch (final FileWriterException e) { + cleanup(file); throw new FileCopyException(R.string.error_unable_to_create_temporary_file); } catch (final SecurityException e) { + cleanup(file); throw new FileCopyException(R.string.error_security_exception); } catch (final IOException e) { + cleanup(file); throw new FileCopyException(R.string.error_io_exception); - } finally { - close(os); - close(is); } } @@ -708,7 +711,7 @@ public class FileBackend { private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException, ImageCompressionException { final File parent = file.getParentFile(); - if (parent.mkdirs()) { + if (parent != null && parent.mkdirs()) { Log.d(Config.LOGTAG, "created parent directory"); } InputStream is = null; @@ -753,13 +756,15 @@ public class FileBackend { } scaledBitmap.recycle(); } catch (final FileNotFoundException e) { + cleanup(file); throw new FileCopyException(R.string.error_file_not_found); - } catch (IOException e) { - e.printStackTrace(); + } catch (final IOException e) { + cleanup(file); throw new FileCopyException(R.string.error_io_exception); } catch (SecurityException e) { + cleanup(file); throw new FileCopyException(R.string.error_security_exception_during_image_copy); - } catch (OutOfMemoryError e) { + } catch (final OutOfMemoryError e) { ++sampleSize; if (sampleSize <= 3) { copyImageToPrivateStorage(file, image, sampleSize); @@ -772,6 +777,14 @@ public class FileBackend { } } + private static void cleanup(final File file) { + try { + file.delete(); + } catch (Exception e) { + + } + } + public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException, ImageCompressionException { Log.d(Config.LOGTAG, "copy image (" + image.toString() + ") to private storage " + file.getAbsolutePath()); copyImageToPrivateStorage(file, image, 0); From 3f315751a12fb8178d44733289e77197c7a0f296 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 11 Sep 2021 10:28:34 +0200 Subject: [PATCH 054/145] version bump to 2.10.0 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 4a80ccaaf872f5cc9ff5c6c5e8e530087bc1d9b6..1fcf2d8bc9b8280cfa83b3f19e36535331acbd6a 100644 --- a/build.gradle +++ b/build.gradle @@ -93,8 +93,8 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 42017 - versionName "2.10.0-beta.2" + versionCode 42018 + versionName "2.10.0" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId From 3075833ab3812f7dbce8886fd750950fb376dd48 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 15 Sep 2021 11:38:03 +0200 Subject: [PATCH 055/145] swap out transcoder library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the transcoder library we used hasn’t been updated in years this commit switches to a maintained fork https://natario1.github.io/Transcoder/ --- build.gradle | 3 +- .../AttachFileToConversationRunnable.java | 326 +++++++++--------- .../utils/Android360pFormatStrategy.java | 76 ---- .../utils/Android720pFormatStrategy.java | 76 ---- .../utils/TranscoderStrategies.java | 41 +++ 5 files changed, 209 insertions(+), 313 deletions(-) delete mode 100644 src/main/java/eu/siacs/conversations/utils/Android360pFormatStrategy.java delete mode 100644 src/main/java/eu/siacs/conversations/utils/Android720pFormatStrategy.java create mode 100644 src/main/java/eu/siacs/conversations/utils/TranscoderStrategies.java diff --git a/build.gradle b/build.gradle index 1fcf2d8bc9b8280cfa83b3f19e36535331acbd6a..7bf12f01a1222347ac45099a6690619370f287de 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,8 @@ dependencies { implementation 'org.whispersystems:signal-protocol-java:2.6.2' implementation 'com.makeramen:roundedimageview:2.3.0' implementation "com.wefika:flowlayout:0.4.1" - implementation 'net.ypresto.androidtranscoder:android-transcoder:0.3.0' + implementation 'com.otaliastudios:transcoder:0.10.3' + implementation 'org.jxmpp:jxmpp-jid:1.0.1' implementation 'org.osmdroid:osmdroid-android:6.1.10' implementation 'org.hsluv:hsluv:0.2' diff --git a/src/main/java/eu/siacs/conversations/services/AttachFileToConversationRunnable.java b/src/main/java/eu/siacs/conversations/services/AttachFileToConversationRunnable.java index 5d39911edf6e4ae52557ae6cba2c0286fbd5708c..db879799d8835ffd5497c1b166dc64f96bbbee0d 100644 --- a/src/main/java/eu/siacs/conversations/services/AttachFileToConversationRunnable.java +++ b/src/main/java/eu/siacs/conversations/services/AttachFileToConversationRunnable.java @@ -3,16 +3,19 @@ package eu.siacs.conversations.services; import android.content.Context; import android.content.SharedPreferences; import android.net.Uri; -import android.os.ParcelFileDescriptor; import android.preference.PreferenceManager; import android.util.Log; -import net.ypresto.androidtranscoder.MediaTranscoder; -import net.ypresto.androidtranscoder.format.MediaFormatStrategy; +import androidx.annotation.NonNull; + +import com.otaliastudios.transcoder.Transcoder; +import com.otaliastudios.transcoder.TranscoderListener; + +import org.jetbrains.annotations.NotNull; import java.io.File; -import java.io.FileDescriptor; import java.io.FileNotFoundException; +import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -23,161 +26,164 @@ import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.ui.UiCallback; -import eu.siacs.conversations.utils.Android360pFormatStrategy; -import eu.siacs.conversations.utils.Android720pFormatStrategy; import eu.siacs.conversations.utils.MimeUtils; - -public class AttachFileToConversationRunnable implements Runnable, MediaTranscoder.Listener { - - private final XmppConnectionService mXmppConnectionService; - private final Message message; - private final Uri uri; - private final String type; - private final UiCallback callback; - private final boolean isVideoMessage; - private final long originalFileSize; - private int currentProgress = -1; - - AttachFileToConversationRunnable(XmppConnectionService xmppConnectionService, Uri uri, String type, Message message, UiCallback callback) { - this.uri = uri; - this.type = type; - this.mXmppConnectionService = xmppConnectionService; - this.message = message; - this.callback = callback; - final String mimeType = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type); - final int autoAcceptFileSize = mXmppConnectionService.getResources().getInteger(R.integer.auto_accept_filesize); - this.originalFileSize = FileBackend.getFileSize(mXmppConnectionService,uri); - this.isVideoMessage = (mimeType != null && mimeType.startsWith("video/")) && originalFileSize > autoAcceptFileSize && !"uncompressed".equals(getVideoCompression()); - } - - boolean isVideoMessage() { - return this.isVideoMessage; - } - - private void processAsFile() { - final String path = mXmppConnectionService.getFileBackend().getOriginalPath(uri); - if (path != null && !FileBackend.isPathBlacklisted(path)) { - message.setRelativeFilePath(path); - mXmppConnectionService.getFileBackend().updateFileParams(message); - if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - mXmppConnectionService.getPgpEngine().encrypt(message, callback); - } else { - mXmppConnectionService.sendMessage(message); - callback.success(message); - } - } else { - try { - mXmppConnectionService.getFileBackend().copyFileToPrivateStorage(message, uri, type); - mXmppConnectionService.getFileBackend().updateFileParams(message); - if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - final PgpEngine pgpEngine = mXmppConnectionService.getPgpEngine(); - if (pgpEngine != null) { - pgpEngine.encrypt(message, callback); - } else if (callback != null) { - callback.error(R.string.unable_to_connect_to_keychain, null); - } - } else { - mXmppConnectionService.sendMessage(message); - callback.success(message); - } - } catch (FileBackend.FileCopyException e) { - callback.error(e.getResId(), message); - } - } - } - - private void processAsVideo() throws FileNotFoundException { - Log.d(Config.LOGTAG,"processing file as video"); - mXmppConnectionService.startForcingForegroundNotification(); - message.setRelativeFilePath(message.getUuid() + ".mp4"); - final DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message); - final MediaFormatStrategy formatStrategy = "720".equals(getVideoCompression()) ? new Android720pFormatStrategy() : new Android360pFormatStrategy(); - file.getParentFile().mkdirs(); - final ParcelFileDescriptor parcelFileDescriptor = mXmppConnectionService.getContentResolver().openFileDescriptor(uri, "r"); - if (parcelFileDescriptor == null) { - throw new FileNotFoundException("Parcel File Descriptor was null"); - } - FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); - Future future = MediaTranscoder.getInstance().transcodeVideo(fileDescriptor, file.getAbsolutePath(), formatStrategy, this); - try { - future.get(); - } catch (InterruptedException e) { - throw new AssertionError(e); - } catch (ExecutionException e) { - if (e.getCause() instanceof Error) { - mXmppConnectionService.stopForcingForegroundNotification(); - processAsFile(); - } else { - Log.d(Config.LOGTAG, "ignoring execution exception. Should get handled by onTranscodeFiled() instead", e); - } - } - } - - @Override - public void onTranscodeProgress(double progress) { - final int p = (int) Math.round(progress * 100); - if (p > currentProgress) { - currentProgress = p; - mXmppConnectionService.getNotificationService().updateFileAddingNotification(p,message); - } - } - - @Override - public void onTranscodeCompleted() { - mXmppConnectionService.stopForcingForegroundNotification(); - final File file = mXmppConnectionService.getFileBackend().getFile(message); - long convertedFileSize = mXmppConnectionService.getFileBackend().getFile(message).getSize(); - Log.d(Config.LOGTAG,"originalFileSize="+originalFileSize+" convertedFileSize="+convertedFileSize); - if (originalFileSize != 0 && convertedFileSize >= originalFileSize) { - if (file.delete()) { - Log.d(Config.LOGTAG,"original file size was smaller. deleting and processing as file"); - processAsFile(); - return; - } else { - Log.d(Config.LOGTAG,"unable to delete converted file"); - } - } - mXmppConnectionService.getFileBackend().updateFileParams(message); - if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - mXmppConnectionService.getPgpEngine().encrypt(message, callback); - } else { - mXmppConnectionService.sendMessage(message); - callback.success(message); - } - } - - @Override - public void onTranscodeCanceled() { - mXmppConnectionService.stopForcingForegroundNotification(); - processAsFile(); - } - - @Override - public void onTranscodeFailed(Exception e) { - mXmppConnectionService.stopForcingForegroundNotification(); - Log.d(Config.LOGTAG,"video transcoding failed",e); - processAsFile(); - } - - @Override - public void run() { - if (this.isVideoMessage()) { - try { - processAsVideo(); - } catch (FileNotFoundException e) { - processAsFile(); - } - } else { - processAsFile(); - } - } - - private String getVideoCompression() { - return getVideoCompression(mXmppConnectionService); - } - - public static String getVideoCompression(final Context context) { - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - return preferences.getString("video_compression", context.getResources().getString(R.string.video_compression)); - } +import eu.siacs.conversations.utils.TranscoderStrategies; + +public class AttachFileToConversationRunnable implements Runnable, TranscoderListener { + + private final XmppConnectionService mXmppConnectionService; + private final Message message; + private final Uri uri; + private final String type; + private final UiCallback callback; + private final boolean isVideoMessage; + private final long originalFileSize; + private int currentProgress = -1; + + AttachFileToConversationRunnable(XmppConnectionService xmppConnectionService, Uri uri, String type, Message message, UiCallback callback) { + this.uri = uri; + this.type = type; + this.mXmppConnectionService = xmppConnectionService; + this.message = message; + this.callback = callback; + final String mimeType = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type); + final int autoAcceptFileSize = mXmppConnectionService.getResources().getInteger(R.integer.auto_accept_filesize); + this.originalFileSize = FileBackend.getFileSize(mXmppConnectionService, uri); + this.isVideoMessage = (mimeType != null && mimeType.startsWith("video/")) && originalFileSize > autoAcceptFileSize && !"uncompressed".equals(getVideoCompression()); + } + + boolean isVideoMessage() { + return this.isVideoMessage; + } + + private void processAsFile() { + final String path = mXmppConnectionService.getFileBackend().getOriginalPath(uri); + if (path != null && !FileBackend.isPathBlacklisted(path)) { + message.setRelativeFilePath(path); + mXmppConnectionService.getFileBackend().updateFileParams(message); + if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + mXmppConnectionService.getPgpEngine().encrypt(message, callback); + } else { + mXmppConnectionService.sendMessage(message); + callback.success(message); + } + } else { + try { + mXmppConnectionService.getFileBackend().copyFileToPrivateStorage(message, uri, type); + mXmppConnectionService.getFileBackend().updateFileParams(message); + if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + final PgpEngine pgpEngine = mXmppConnectionService.getPgpEngine(); + if (pgpEngine != null) { + pgpEngine.encrypt(message, callback); + } else if (callback != null) { + callback.error(R.string.unable_to_connect_to_keychain, null); + } + } else { + mXmppConnectionService.sendMessage(message); + callback.success(message); + } + } catch (FileBackend.FileCopyException e) { + callback.error(e.getResId(), message); + } + } + } + + private void processAsVideo() throws FileNotFoundException { + Log.d(Config.LOGTAG, "processing file as video"); + mXmppConnectionService.startForcingForegroundNotification(); + message.setRelativeFilePath(message.getUuid() + ".mp4"); + final DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message); + if (Objects.requireNonNull(file.getParentFile()).mkdirs()) { + Log.d(Config.LOGTAG, "created parent directory for video file"); + } + + final boolean highQuality = "720".equals(getVideoCompression()); + + final Future future = Transcoder.into(file.getAbsolutePath()). + addDataSource(mXmppConnectionService, uri) + .setVideoTrackStrategy(highQuality ? TranscoderStrategies.VIDEO_720P : TranscoderStrategies.VIDEO_360P) + .setAudioTrackStrategy(highQuality ? TranscoderStrategies.AUDIO_HQ : TranscoderStrategies.AUDIO_MQ) + .setListener(this) + .transcode(); + try { + future.get(); + } catch (InterruptedException e) { + throw new AssertionError(e); + } catch (ExecutionException e) { + if (e.getCause() instanceof Error) { + mXmppConnectionService.stopForcingForegroundNotification(); + processAsFile(); + } else { + Log.d(Config.LOGTAG, "ignoring execution exception. Should get handled by onTranscodeFiled() instead", e); + } + } + } + + @Override + public void onTranscodeProgress(double progress) { + final int p = (int) Math.round(progress * 100); + if (p > currentProgress) { + currentProgress = p; + mXmppConnectionService.getNotificationService().updateFileAddingNotification(p, message); + } + } + + @Override + public void onTranscodeCompleted(int successCode) { + mXmppConnectionService.stopForcingForegroundNotification(); + final File file = mXmppConnectionService.getFileBackend().getFile(message); + long convertedFileSize = mXmppConnectionService.getFileBackend().getFile(message).getSize(); + Log.d(Config.LOGTAG, "originalFileSize=" + originalFileSize + " convertedFileSize=" + convertedFileSize); + if (originalFileSize != 0 && convertedFileSize >= originalFileSize) { + if (file.delete()) { + Log.d(Config.LOGTAG, "original file size was smaller. deleting and processing as file"); + processAsFile(); + return; + } else { + Log.d(Config.LOGTAG, "unable to delete converted file"); + } + } + mXmppConnectionService.getFileBackend().updateFileParams(message); + if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + mXmppConnectionService.getPgpEngine().encrypt(message, callback); + } else { + mXmppConnectionService.sendMessage(message); + callback.success(message); + } + } + + @Override + public void onTranscodeCanceled() { + mXmppConnectionService.stopForcingForegroundNotification(); + processAsFile(); + } + + @Override + public void onTranscodeFailed(@NonNull @NotNull Throwable exception) { + mXmppConnectionService.stopForcingForegroundNotification(); + Log.d(Config.LOGTAG, "video transcoding failed", exception); + processAsFile(); + } + + @Override + public void run() { + if (this.isVideoMessage()) { + try { + processAsVideo(); + } catch (FileNotFoundException e) { + processAsFile(); + } + } else { + processAsFile(); + } + } + + private String getVideoCompression() { + return getVideoCompression(mXmppConnectionService); + } + + public static String getVideoCompression(final Context context) { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + return preferences.getString("video_compression", context.getResources().getString(R.string.video_compression)); + } } diff --git a/src/main/java/eu/siacs/conversations/utils/Android360pFormatStrategy.java b/src/main/java/eu/siacs/conversations/utils/Android360pFormatStrategy.java deleted file mode 100644 index a692cc6dca5aba382a64b026bffba0812235fe7d..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/utils/Android360pFormatStrategy.java +++ /dev/null @@ -1,76 +0,0 @@ -package eu.siacs.conversations.utils; - -import android.media.MediaCodecInfo; -import android.media.MediaFormat; -import android.os.Build; -import android.util.Log; - -import androidx.annotation.RequiresApi; - -import net.ypresto.androidtranscoder.format.MediaFormatExtraConstants; -import net.ypresto.androidtranscoder.format.MediaFormatStrategy; -import net.ypresto.androidtranscoder.format.OutputFormatUnavailableException; - -import eu.siacs.conversations.Config; - -public class Android360pFormatStrategy implements MediaFormatStrategy { - - private static final int LONGER_LENGTH = 640; - private static final int SHORTER_LENGTH = 360; - private static final int DEFAULT_VIDEO_BITRATE = 1000 * 1000; - private static final int DEFAULT_AUDIO_BITRATE = 128 * 1000; - private final int mVideoBitrate; - private final int mAudioBitrate; - private final int mAudioChannels; - - public Android360pFormatStrategy() { - mVideoBitrate = DEFAULT_VIDEO_BITRATE; - mAudioBitrate = DEFAULT_AUDIO_BITRATE; - mAudioChannels = 2; - } - - @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) - @Override - public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) { - int width = inputFormat.getInteger(MediaFormat.KEY_WIDTH); - int height = inputFormat.getInteger(MediaFormat.KEY_HEIGHT); - int longer, shorter, outWidth, outHeight; - if (width >= height) { - longer = width; - shorter = height; - outWidth = LONGER_LENGTH; - outHeight = SHORTER_LENGTH; - } else { - shorter = width; - longer = height; - outWidth = SHORTER_LENGTH; - outHeight = LONGER_LENGTH; - } - if (longer * 9 != shorter * 16) { - throw new OutputFormatUnavailableException("This video is not 16:9, and is not able to transcode. (" + width + "x" + height + ")"); - } - if (shorter <= SHORTER_LENGTH) { - Log.d(Config.LOGTAG, "This video is less or equal to 360p, pass-through. (" + width + "x" + height + ")"); - return null; - } - MediaFormat format = MediaFormat.createVideoFormat("video/avc", outWidth, outHeight); - format.setInteger(MediaFormat.KEY_BIT_RATE, mVideoBitrate); - format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); - format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 3); - format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - format.setInteger(MediaFormat.KEY_PROFILE ,MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline); - format.setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel13); - } - return format; - } - - @Override - public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) { - final MediaFormat format = MediaFormat.createAudioFormat(MediaFormatExtraConstants.MIMETYPE_AUDIO_AAC, inputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE), mAudioChannels); - format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); - format.setInteger(MediaFormat.KEY_BIT_RATE, mAudioBitrate); - return format; - } - -} diff --git a/src/main/java/eu/siacs/conversations/utils/Android720pFormatStrategy.java b/src/main/java/eu/siacs/conversations/utils/Android720pFormatStrategy.java deleted file mode 100644 index 274ebb76f12d356b72f7aa95a02289d0c2f103b5..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/utils/Android720pFormatStrategy.java +++ /dev/null @@ -1,76 +0,0 @@ -package eu.siacs.conversations.utils; - -import android.media.MediaCodecInfo; -import android.media.MediaFormat; -import android.os.Build; -import android.util.Log; - -import androidx.annotation.RequiresApi; - -import net.ypresto.androidtranscoder.format.MediaFormatExtraConstants; -import net.ypresto.androidtranscoder.format.MediaFormatStrategy; -import net.ypresto.androidtranscoder.format.OutputFormatUnavailableException; - -import eu.siacs.conversations.Config; - -public class Android720pFormatStrategy implements MediaFormatStrategy { - - private static final int LONGER_LENGTH = 1280; - private static final int SHORTER_LENGTH = 720; - private static final int DEFAULT_VIDEO_BITRATE = 2000 * 1000; - private static final int DEFAULT_AUDIO_BITRATE = 192 * 1000; - private final int mVideoBitrate; - private final int mAudioBitrate; - private final int mAudioChannels; - - public Android720pFormatStrategy() { - mVideoBitrate = DEFAULT_VIDEO_BITRATE; - mAudioBitrate = DEFAULT_AUDIO_BITRATE; - mAudioChannels = 2; - } - - @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) - @Override - public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) { - int width = inputFormat.getInteger(MediaFormat.KEY_WIDTH); - int height = inputFormat.getInteger(MediaFormat.KEY_HEIGHT); - int longer, shorter, outWidth, outHeight; - if (width >= height) { - longer = width; - shorter = height; - outWidth = LONGER_LENGTH; - outHeight = SHORTER_LENGTH; - } else { - shorter = width; - longer = height; - outWidth = SHORTER_LENGTH; - outHeight = LONGER_LENGTH; - } - if (longer * 9 != shorter * 16) { - throw new OutputFormatUnavailableException("This video is not 16:9, and is not able to transcode. (" + width + "x" + height + ")"); - } - if (shorter <= SHORTER_LENGTH) { - Log.d(Config.LOGTAG, "This video is less or equal to 720p, pass-through. (" + width + "x" + height + ")"); - return null; - } - MediaFormat format = MediaFormat.createVideoFormat("video/avc", outWidth, outHeight); - format.setInteger(MediaFormat.KEY_BIT_RATE, mVideoBitrate); - format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); - format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 3); - format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - format.setInteger(MediaFormat.KEY_PROFILE ,MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline); - format.setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel13); - } - return format; - } - - @Override - public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) { - final MediaFormat format = MediaFormat.createAudioFormat(MediaFormatExtraConstants.MIMETYPE_AUDIO_AAC, inputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE), mAudioChannels); - format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); - format.setInteger(MediaFormat.KEY_BIT_RATE, mAudioBitrate); - return format; - } - -} diff --git a/src/main/java/eu/siacs/conversations/utils/TranscoderStrategies.java b/src/main/java/eu/siacs/conversations/utils/TranscoderStrategies.java new file mode 100644 index 0000000000000000000000000000000000000000..0fb0766d1f1ff14c8c9f5809772c5251c1d717cb --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/TranscoderStrategies.java @@ -0,0 +1,41 @@ +package eu.siacs.conversations.utils; + +import com.otaliastudios.transcoder.strategy.DefaultAudioStrategy; +import com.otaliastudios.transcoder.strategy.DefaultVideoStrategy; + +public final class TranscoderStrategies { + + public static final DefaultVideoStrategy VIDEO_720P = DefaultVideoStrategy.atMost(720) + .bitRate(2L * 1000 * 1000) + .frameRate(30) + .keyFrameInterval(3F) + .build(); + + public static final DefaultVideoStrategy VIDEO_360P = DefaultVideoStrategy.atMost(360) + .bitRate(1000 * 1000) + .frameRate(30) + .keyFrameInterval(3F) + .build(); + + //TODO do we want to add 240p (@500kbs) and 1080p (@4mbs?) ? + // see suggested bit rates on https://www.videoproc.com/media-converter/bitrate-setting-for-h264.htm + + public static final DefaultAudioStrategy AUDIO_HQ = DefaultAudioStrategy.builder() + .bitRate(192 * 1000) + .channels(2) + .sampleRate(DefaultAudioStrategy.SAMPLE_RATE_AS_INPUT) + .build(); + + public static final DefaultAudioStrategy AUDIO_MQ = DefaultAudioStrategy.builder() + .bitRate(128 * 1000) + .channels(2) + .sampleRate(DefaultAudioStrategy.SAMPLE_RATE_AS_INPUT) + .build(); + + //TODO if we add 144p we definitely want to add a lower audio bit rate as well + + private TranscoderStrategies() { + throw new IllegalStateException("Do not instantiate me"); + } + +} From 73000962fef7a824ec9616d8323b1fa522b5e5d8 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 15 Sep 2021 21:32:27 +0200 Subject: [PATCH 056/145] bump transcoder version fixes #4167 --- build.gradle | 2 +- src/main/AndroidManifest.xml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 7bf12f01a1222347ac45099a6690619370f287de..1a711cc0ce20b55dd08b6a6d76b19a4a955c0122 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,7 @@ dependencies { implementation 'org.whispersystems:signal-protocol-java:2.6.2' implementation 'com.makeramen:roundedimageview:2.3.0' implementation "com.wefika:flowlayout:0.4.1" - implementation 'com.otaliastudios:transcoder:0.10.3' + implementation 'com.otaliastudios:transcoder:0.10.4' implementation 'org.jxmpp:jxmpp-jid:1.0.1' implementation 'org.osmdroid:osmdroid-android:6.1.10' diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index c98aae0419d3e7576a455c3e193fecd18a65bb98..cc4959ebb1fea45a047ab5be76d14b8bfe5ae96d 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -39,8 +39,6 @@ - - From 231d97ea81bb2249afa432b43d78ac963b80594a Mon Sep 17 00:00:00 2001 From: Ashique Bava <67498676+ashique-perumbavoor@users.noreply.github.com> Date: Mon, 20 Sep 2021 11:52:55 +0530 Subject: [PATCH 057/145] Migrate Fragments to AndroidX --- .../ui/ConversationFragment.java | 43 ++++++++------- .../ui/ConversationsActivity.java | 54 +++++++++---------- .../ui/ConversationsOverviewFragment.java | 15 +++--- .../siacs/conversations/ui/XmppFragment.java | 3 +- .../ui/adapter/ConversationAdapter.java | 2 +- .../ui/adapter/MessageAdapter.java | 4 +- .../ui/util/MucDetailsContextMenuHelper.java | 2 +- 7 files changed, 62 insertions(+), 61 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 93a5ad2bfa7102417002497426bd43d81312319c..5828ee2210b1d28e0282b8b652b0d75463696203 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -3,8 +3,6 @@ package eu.siacs.conversations.ui; import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.Fragment; -import android.app.FragmentManager; import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; @@ -52,6 +50,8 @@ import androidx.appcompat.app.AlertDialog; import androidx.core.view.inputmethod.InputConnectionCompat; import androidx.core.view.inputmethod.InputContentInfoCompat; import androidx.databinding.DataBindingUtil; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; import com.google.common.base.Optional; @@ -465,41 +465,41 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke private boolean firstWord = false; private Message mPendingDownloadableMessage; - private static ConversationFragment findConversationFragment(Activity activity) { - Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); + private static ConversationFragment findConversationFragment(FragmentManager fragmentManager) { + Fragment fragment = fragmentManager.findFragmentById(R.id.main_fragment); if (fragment instanceof ConversationFragment) { return (ConversationFragment) fragment; } - fragment = activity.getFragmentManager().findFragmentById(R.id.secondary_fragment); + fragment = fragmentManager.findFragmentById(R.id.secondary_fragment); if (fragment instanceof ConversationFragment) { return (ConversationFragment) fragment; } return null; } - public static void startStopPending(Activity activity) { - ConversationFragment fragment = findConversationFragment(activity); + public static void startStopPending(FragmentManager fragmentManager) { + ConversationFragment fragment = findConversationFragment(fragmentManager); if (fragment != null) { fragment.messageListAdapter.startStopPending(); } } - public static void downloadFile(Activity activity, Message message) { - ConversationFragment fragment = findConversationFragment(activity); + public static void downloadFile(FragmentManager fragmentManager, Message message) { + ConversationFragment fragment = findConversationFragment(fragmentManager); if (fragment != null) { fragment.startDownloadable(message); } } - public static void registerPendingMessage(Activity activity, Message message) { - ConversationFragment fragment = findConversationFragment(activity); + public static void registerPendingMessage(FragmentManager fragmentManager, Message message) { + ConversationFragment fragment = findConversationFragment(fragmentManager); if (fragment != null) { fragment.pendingMessage.push(message); } } - public static void openPendingMessage(Activity activity) { - ConversationFragment fragment = findConversationFragment(activity); + public static void openPendingMessage(FragmentManager fragmentManager) { + ConversationFragment fragment = findConversationFragment(fragmentManager); if (fragment != null) { Message message = fragment.pendingMessage.pop(); if (message != null) { @@ -508,12 +508,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } - public static Conversation getConversation(Activity activity) { - return getConversation(activity, R.id.secondary_fragment); + public static Conversation getConversation(FragmentManager fragmentManager) { + return getConversation(fragmentManager, R.id.secondary_fragment); } - private static Conversation getConversation(Activity activity, @IdRes int res) { - final Fragment fragment = activity.getFragmentManager().findFragmentById(res); + private static Conversation getConversation(FragmentManager fragmentManager, @IdRes int res) { + final Fragment fragment = fragmentManager.findFragmentById(res); if (fragment instanceof ConversationFragment) { return ((ConversationFragment) fragment).getConversation(); } else { @@ -521,8 +521,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } - public static ConversationFragment get(Activity activity) { - FragmentManager fragmentManager = activity.getFragmentManager(); + public static ConversationFragment get(FragmentManager fragmentManager) { Fragment fragment = fragmentManager.findFragmentById(R.id.main_fragment); if (fragment instanceof ConversationFragment) { return (ConversationFragment) fragment; @@ -532,12 +531,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } - public static Conversation getConversationReliable(Activity activity) { - final Conversation conversation = getConversation(activity, R.id.secondary_fragment); + public static Conversation getConversationReliable(FragmentManager fragmentManager) { + final Conversation conversation = getConversation(fragmentManager, R.id.secondary_fragment); if (conversation != null) { return conversation; } - return getConversation(activity, R.id.main_fragment); + return getConversation(fragmentManager, R.id.main_fragment); } private static boolean scrolledToBottom(AbsListView listView) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index 171eea6adbd399ea25edbf951acf046a29f8ea4e..fb0f0e696ad84cd2cf8e661ddf698b0462231902 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -34,9 +34,6 @@ import static eu.siacs.conversations.ui.ConversationFragment.REQUEST_DECRYPT_PGP import android.annotation.SuppressLint; import android.app.Activity; -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; @@ -55,6 +52,9 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; import org.openintents.openpgp.util.OpenPgpApi; @@ -165,8 +165,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } invalidateActionBarTitle(); - if (binding.secondaryFragment != null && ConversationFragment.getConversation(this) == null) { - Conversation conversation = ConversationsOverviewFragment.getSuggestion(this); + if (binding.secondaryFragment != null && ConversationFragment.getConversation(getSupportFragmentManager()) == null) { + Conversation conversation = ConversationsOverviewFragment.getSuggestion(getSupportFragmentManager()); if (conversation != null) { openConversation(conversation, null); } @@ -202,7 +202,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (xmppConnectionService == null) { return; } - final Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment); + final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_fragment); if (fragment instanceof ConversationsOverviewFragment) { if (ExceptionHelper.checkForCrash(this)) { return; @@ -255,14 +255,14 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void notifyFragmentOfBackendConnected(@IdRes int id) { - final Fragment fragment = getFragmentManager().findFragmentById(id); + final Fragment fragment = getSupportFragmentManager().findFragmentById(id); if (fragment instanceof OnBackendConnected) { ((OnBackendConnected) fragment).onBackendConnected(); } } private void refreshFragment(@IdRes int id) { - final Fragment fragment = getFragmentManager().findFragmentById(id); + final Fragment fragment = getSupportFragmentManager().findFragmentById(id); if (fragment instanceof XmppFragment) { ((XmppFragment) fragment).refresh(); } @@ -287,10 +287,10 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio switch (requestCode) { case REQUEST_OPEN_MESSAGE: refreshUiReal(); - ConversationFragment.openPendingMessage(this); + ConversationFragment.openPendingMessage(getSupportFragmentManager()); break; case REQUEST_PLAY_PAUSE: - ConversationFragment.startStopPending(this); + ConversationFragment.startStopPending(getSupportFragmentManager()); break; } } @@ -317,7 +317,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void handleNegativeActivityResult(int requestCode) { - Conversation conversation = ConversationFragment.getConversationReliable(this); + Conversation conversation = ConversationFragment.getConversationReliable(getSupportFragmentManager()); switch (requestCode) { case REQUEST_DECRYPT_PGP: if (conversation == null) { @@ -332,7 +332,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void handlePositiveActivityResult(int requestCode, final Intent data) { - Conversation conversation = ConversationFragment.getConversationReliable(this); + Conversation conversation = ConversationFragment.getConversationReliable(getSupportFragmentManager()); if (conversation == null) { Log.d(Config.LOGTAG, "conversation not found"); return; @@ -364,8 +364,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio this.binding = DataBindingUtil.setContentView(this, R.layout.activity_conversations); setSupportActionBar(binding.toolbar); configureActionBar(getSupportActionBar()); - this.getFragmentManager().addOnBackStackChangedListener(this::invalidateActionBarTitle); - this.getFragmentManager().addOnBackStackChangedListener(this::showDialogsIfMainIsOverview); + this.getSupportFragmentManager().addOnBackStackChangedListener(this::invalidateActionBarTitle); + this.getSupportFragmentManager().addOnBackStackChangedListener(this::showDialogsIfMainIsOverview); this.initializeFragments(); this.invalidateActionBarTitle(); final Intent intent; @@ -386,7 +386,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio final MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code); if (qrCodeScanMenuItem != null) { if (isCameraFeatureAvailable()) { - Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment); + Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_fragment); boolean visible = getResources().getBoolean(R.bool.show_qr_code_scan) && fragment instanceof ConversationsOverviewFragment; qrCodeScanMenuItem.setVisible(visible); @@ -400,7 +400,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio @Override public void onConversationSelected(Conversation conversation) { clearPendingViewIntent(); - if (ConversationFragment.getConversation(this) == conversation) { + if (ConversationFragment.getConversation(getSupportFragmentManager()) == conversation) { Log.d(Config.LOGTAG, "ignore onConversationSelected() because conversation is already open"); return; } @@ -428,7 +428,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void openConversation(Conversation conversation, Bundle extras) { - final FragmentManager fragmentManager = getFragmentManager(); + final FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.executePendingTransactions(); ConversationFragment conversationFragment = (ConversationFragment) fragmentManager.findFragmentById(R.id.secondary_fragment); final boolean mainNeedsRefresh; @@ -480,7 +480,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } switch (item.getItemId()) { case android.R.id.home: - FragmentManager fm = getFragmentManager(); + FragmentManager fm = getSupportFragmentManager(); if (fm.getBackStackEntryCount() > 0) { try { fm.popBackStack(); @@ -497,7 +497,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio startActivity(new Intent(this, SearchActivity.class)); return true; case R.id.action_search_this_conversation: - final Conversation conversation = ConversationFragment.getConversation(this); + final Conversation conversation = ConversationFragment.getConversation(getSupportFragmentManager()); if (conversation == null) { return true; } @@ -512,7 +512,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio @Override public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { if (keyCode == KeyEvent.KEYCODE_DPAD_UP && keyEvent.isCtrlPressed()) { - final ConversationFragment conversationFragment = ConversationFragment.get(this); + final ConversationFragment conversationFragment = ConversationFragment.get(getSupportFragmentManager()); if (conversationFragment != null && conversationFragment.onArrowUpCtrlPressed()) { return true; } @@ -567,14 +567,14 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void initializeFragments() { - final FragmentManager fragmentManager = getFragmentManager(); + final FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction transaction = fragmentManager.beginTransaction(); final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment); if (mainFragment != null) { if (binding.secondaryFragment != null) { if (mainFragment instanceof ConversationFragment) { - getFragmentManager().popBackStack(); + getSupportFragmentManager().popBackStack(); transaction.remove(mainFragment); transaction.commit(); fragmentManager.executePendingTransactions(); @@ -588,7 +588,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (secondaryFragment instanceof ConversationFragment) { transaction.remove(secondaryFragment); transaction.commit(); - getFragmentManager().executePendingTransactions(); + getSupportFragmentManager().executePendingTransactions(); transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.main_fragment, secondaryFragment); transaction.addToBackStack(null); @@ -610,7 +610,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (actionBar == null) { return; } - final FragmentManager fragmentManager = getFragmentManager(); + final FragmentManager fragmentManager = getSupportFragmentManager(); final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); if (mainFragment instanceof ConversationFragment) { final Conversation conversation = ((ConversationFragment) mainFragment).getConversation(); @@ -647,7 +647,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (performRedirectIfNecessary(conversation, false)) { return; } - final FragmentManager fragmentManager = getFragmentManager(); + final FragmentManager fragmentManager = getSupportFragmentManager(); final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); if (mainFragment instanceof ConversationFragment) { try { @@ -661,7 +661,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment); if (secondaryFragment instanceof ConversationFragment) { if (((ConversationFragment) secondaryFragment).getConversation() == conversation) { - Conversation suggestion = ConversationsOverviewFragment.getSuggestion(this, conversation); + Conversation suggestion = ConversationsOverviewFragment.getSuggestion(getSupportFragmentManager(), conversation); if (suggestion != null) { openConversation(suggestion, null); } @@ -671,7 +671,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio @Override public void onConversationsListItemUpdated() { - Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment); + Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_fragment); if (fragment instanceof ConversationsOverviewFragment) { ((ConversationsOverviewFragment) fragment).refresh(); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java index eebd94df500256ce883a91ead1b0b66aae7a7ada..ff33c61557eddfdbec95e1881d108b46c3078392 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java @@ -31,7 +31,6 @@ package eu.siacs.conversations.ui; import android.app.Activity; import android.app.AlertDialog; -import android.app.Fragment; import android.content.Intent; import android.graphics.Canvas; import android.graphics.Paint; @@ -46,6 +45,8 @@ import android.view.ViewGroup; import android.widget.Toast; import androidx.databinding.DataBindingUtil; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -139,7 +140,7 @@ public class ConversationsOverviewFragment extends XmppFragment { activity.xmppConnectionService.archiveConversation(c); return; } - final boolean formerlySelected = ConversationFragment.getConversation(getActivity()) == swipedConversation.peek(); + final boolean formerlySelected = ConversationFragment.getConversation(requireActivity().getSupportFragmentManager()) == swipedConversation.peek(); if (activity instanceof OnConversationArchived) { ((OnConversationArchived) activity).onConversationArchived(swipedConversation.peek()); } @@ -202,19 +203,19 @@ public class ConversationsOverviewFragment extends XmppFragment { private ItemTouchHelper touchHelper; - public static Conversation getSuggestion(Activity activity) { + public static Conversation getSuggestion(FragmentManager fragmentManager) { final Conversation exception; - Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); + Fragment fragment = fragmentManager.findFragmentById(R.id.main_fragment); if (fragment instanceof ConversationsOverviewFragment) { exception = ((ConversationsOverviewFragment) fragment).swipedConversation.peek(); } else { exception = null; } - return getSuggestion(activity, exception); + return getSuggestion(fragmentManager, exception); } - public static Conversation getSuggestion(Activity activity, Conversation exception) { - Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); + public static Conversation getSuggestion(FragmentManager fragmentManager, Conversation exception) { + Fragment fragment = fragmentManager.findFragmentById(R.id.main_fragment); if (fragment instanceof ConversationsOverviewFragment) { List conversations = ((ConversationsOverviewFragment) fragment).conversations; if (conversations.size() > 0) { diff --git a/src/main/java/eu/siacs/conversations/ui/XmppFragment.java b/src/main/java/eu/siacs/conversations/ui/XmppFragment.java index 30524d2f5441f3e0405ec38d1807e9a7d3294080..a16d0ae7ffa56381e1d008d1783900fa1243b73a 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppFragment.java @@ -30,7 +30,8 @@ package eu.siacs.conversations.ui; import android.app.Activity; -import android.app.Fragment; + +import androidx.fragment.app.Fragment; import eu.siacs.conversations.ui.interfaces.OnBackendConnected; diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index 049703597d2409235059ba15ce1625d47648b74d..496c087bd263aed49e6aad845480ab15c8e86020 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -60,7 +60,7 @@ public class ConversationAdapter extends RecyclerView.Adapter { viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.VISIBLE); viewHolder.download_button.setText(text); - viewHolder.download_button.setOnClickListener(v -> ConversationFragment.downloadFile(activity, message)); + viewHolder.download_button.setOnClickListener(v -> ConversationFragment.downloadFile(activity.getSupportFragmentManager(), message)); } private void displayOpenableMessage(ViewHolder viewHolder, final Message message, final boolean darkBackground) { @@ -872,7 +872,7 @@ public class MessageAdapter extends ArrayAdapter { public void openDownloadable(Message message) { if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - ConversationFragment.registerPendingMessage(activity, message); + ConversationFragment.registerPendingMessage(activity.getSupportFragmentManager(), message); ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ConversationsActivity.REQUEST_OPEN_MESSAGE); return; } 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 da1ac7a4448043ec4910b9cf720dfe4f6bf8fdc7..6f928723676c1fb092467ca03d9ce5ba9c9e1b6e 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java @@ -172,7 +172,7 @@ public final class MucDetailsContextMenuHelper { return true; case R.id.send_private_message: if (activity instanceof ConversationsActivity) { - ConversationFragment conversationFragment = ConversationFragment.get(activity); + ConversationFragment conversationFragment = ConversationFragment.get(activity.getSupportFragmentManager()); if (conversationFragment != null) { conversationFragment.privateMessageWith(user.getFullJid()); return true; From 951d84f40461ca761b6569089127e5bcf3bcaa8e Mon Sep 17 00:00:00 2001 From: Alexei Sorokin Date: Sun, 19 Sep 2021 20:35:27 +0300 Subject: [PATCH 058/145] make sure messages_index is always cleaned up fully. fixes #4170 --- .../conversations/persistance/DatabaseBackend.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index f1cf78760a1ce40c8663bd134849f7d1c63d4400..de9bc0d2ce32e5135bdb7613ea8e24bd4c89b85d 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -535,9 +535,6 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_RESOLVER_RESULTS_TABLE); } - if (oldVersion < 42 && newVersion >= 42) { - db.execSQL("DROP TRIGGER IF EXISTS after_message_delete"); - } if (QuickConversationsService.isQuicksy() && oldVersion < 43 && newVersion >= 43) { List accounts = getAccounts(db); for (Account account : accounts) { @@ -576,14 +573,21 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.beginTransaction(); db.execSQL("DROP TRIGGER IF EXISTS after_message_insert;"); db.execSQL("DROP TRIGGER IF EXISTS after_message_update;"); + db.execSQL("DROP TRIGGER IF EXISTS after_message_delete;"); db.execSQL("DROP TABLE IF EXISTS messages_index;"); + // a hack that should not be necessary, but + // there was at least one occurence when SQLite failed at this + db.execSQL("DROP TABLE IF EXISTS messages_index_docsize;"); + db.execSQL("DROP TABLE IF EXISTS messages_index_segdir;"); + db.execSQL("DROP TABLE IF EXISTS messages_index_segments;"); + db.execSQL("DROP TABLE IF EXISTS messages_index_stat;"); db.execSQL(CREATE_MESSAGE_INDEX_TABLE); db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER); db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER); db.execSQL(CREATE_MESSAGE_DELETE_TRIGGER); - requiresMessageIndexRebuild = true; db.setTransactionSuccessful(); db.endTransaction(); + requiresMessageIndexRebuild = true; } } From bfc8668803374dcac78d3e09e4edcf368fca81d1 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 20 Sep 2021 09:27:40 +0200 Subject: [PATCH 059/145] bump appcompat version --- build.gradle | 4 ++-- .../eu/siacs/conversations/ui/ContactDetailsActivity.java | 2 ++ .../java/eu/siacs/conversations/ui/ConversationsActivity.java | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 1a711cc0ce20b55dd08b6a6d76b19a4a955c0122..6dc3fdbcb92ffc5b8fae647ff4c52ca28e17b745 100644 --- a/build.gradle +++ b/build.gradle @@ -46,12 +46,12 @@ dependencies { quicksyPlaystoreSystemImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.1' implementation 'org.sufficientlysecure:openpgp-api:10.0' implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0' - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.exifinterface:exifinterface:1.3.3' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.emoji:emoji:1.1.0' - implementation 'com.google.android.material:material:1.3.0' + implementation 'com.google.android.material:material:1.4.0' compatImplementation 'androidx.emoji:emoji-appcompat:1.1.0' conversationsFreeCompatImplementation 'androidx.emoji:emoji-bundled:1.1.0' quicksyFreeCompatImplementation 'androidx.emoji:emoji-bundled:1.1.0' diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 74f9e7068d491764427967334428eb8f6deecaaf..eb85d46e4a5f1a54be076b4a72a5f31c35e3c0a7 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -131,6 +131,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp } private void showAddToPhoneBookDialog() { + //TODO check if isQuicksy and contact is on quicksy.im domain + // store in final boolean. show different message. use phone number for add final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.action_add_phone_book)); builder.setMessage(getString(R.string.add_phone_book_text, contact.getJid().toEscapedString())); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index fb0f0e696ad84cd2cf8e661ddf698b0462231902..9ed1e3883c1c16b1830b9f135aa09c77d4b6ce6f 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -281,6 +281,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults); if (grantResults.length > 0) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { From b01bca74fd3fcd818d4085e9259d66a3069e8214 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 20 Sep 2021 09:54:42 +0200 Subject: [PATCH 060/145] fix some linter warnings --- .../siacs/conversations/ui/ManageAccountActivity.java | 1 + .../eu/siacs/conversations/ui/WelcomeActivity.java | 1 + .../siacs/conversations/ui/ConversationFragment.java | 11 ++++++----- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java index c1ee451be7ac01ac8a25b3e7fd7cddedf6885750..6aecf4b26faaf601f37152dcd3fdfb48ecd50442 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -228,6 +228,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults.length > 0) { if (allGranted(grantResults)) { switch (requestCode) { diff --git a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java index 8f652ce8e522bef41e4631c504aaacf3c22647cf..9f3252ac1f309487af17a6bb77f2ccf36375ccf8 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java @@ -201,6 +201,7 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults); if (grantResults.length > 0) { if (allGranted(grantResults)) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 5828ee2210b1d28e0282b8b652b0d75463696203..9ad7caed50cf32f0ed3212303b2ac91a45fc27c5 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1579,6 +1579,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } if (writeGranted(grantResults, permissions)) { if (activity != null && activity.xmppConnectionService != null) { + activity.xmppConnectionService.getBitmapCache().evictAll(); activity.xmppConnectionService.restartFileObserver(); } refresh(); @@ -1617,9 +1618,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke @SuppressLint("InflateParams") protected void clearHistoryDialog(final Conversation conversation) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()); builder.setTitle(getString(R.string.clear_conversation_history)); - final View dialogView = getActivity().getLayoutInflater().inflate(R.layout.dialog_clear_history, null); + final View dialogView = requireActivity().getLayoutInflater().inflate(R.layout.dialog_clear_history, null); final CheckBox endConversationCheckBox = dialogView.findViewById(R.id.end_conversation_checkbox); builder.setView(dialogView); builder.setNegativeButton(getString(R.string.cancel), null); @@ -1637,7 +1638,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } protected void muteConversationDialog(final Conversation conversation) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()); builder.setTitle(R.string.disable_notifications); final int[] durations = getResources().getIntArray(R.array.mute_options_durations); final CharSequence[] labels = new CharSequence[durations.length]; @@ -1653,13 +1654,13 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (durations[which] == -1) { till = Long.MAX_VALUE; } else { - till = System.currentTimeMillis() + (durations[which] * 1000); + till = System.currentTimeMillis() + (durations[which] * 1000L); } conversation.setMutedTill(till); activity.xmppConnectionService.updateConversation(conversation); activity.onConversationsListItemUpdated(); refresh(); - getActivity().invalidateOptionsMenu(); + requireActivity().invalidateOptionsMenu(); }); builder.create().show(); } From ba9596b37d3f173b15426f36d241104267ffc596 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 20 Sep 2021 10:07:38 +0200 Subject: [PATCH 061/145] catch rare exception around execute pending fragment transactions --- .../siacs/conversations/ui/ConversationsActivity.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index 9ed1e3883c1c16b1830b9f135aa09c77d4b6ce6f..2c2c4fe48256fd6f77f6822f85f8d8846f3cc207 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -430,7 +430,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio private void openConversation(Conversation conversation, Bundle extras) { final FragmentManager fragmentManager = getSupportFragmentManager(); - fragmentManager.executePendingTransactions(); + executePendingTransactions(fragmentManager); ConversationFragment conversationFragment = (ConversationFragment) fragmentManager.findFragmentById(R.id.secondary_fragment); final boolean mainNeedsRefresh; if (conversationFragment == null) { @@ -462,6 +462,14 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } } + private static void executePendingTransactions(final FragmentManager fragmentManager) { + try { + fragmentManager.executePendingTransactions(); + } catch (final Exception e) { + Log.e(Config.LOGTAG,"unable to execute pending fragment transactions"); + } + } + public boolean onXmppUriClicked(Uri uri) { XmppUri xmppUri = new XmppUri(uri); if (xmppUri.isValidJid() && !xmppUri.hasFingerprints()) { From f9f994c540347062cf7f9de96531bde956cf97c0 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 20 Sep 2021 10:08:11 +0200 Subject: [PATCH 062/145] Intent.EXTRA_ALLOW_MULTIPLE is now supported by minSdk --- .../conversations/ui/ConversationFragment.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 9ad7caed50cf32f0ed3212303b2ac91a45fc27c5..bf9aa93d3e3369689fbc0c84e014c78dbc3acbaa 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -135,6 +135,8 @@ import static eu.siacs.conversations.utils.PermissionUtils.allGranted; import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied; import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; +import org.jetbrains.annotations.NotNull; + public class ConversationFragment extends XmppFragment implements EditMessage.KeyboardListener, MessageAdapter.OnContactPictureLongClicked, MessageAdapter.OnContactPictureClicked { @@ -1692,7 +1694,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke this.activity.xmppConnectionService.updateConversation(conversation); this.activity.onConversationsListItemUpdated(); refresh(); - getActivity().invalidateOptionsMenu(); + requireActivity().invalidateOptionsMenu(); } @@ -1702,9 +1704,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke switch (attachmentChoice) { case ATTACHMENT_CHOICE_CHOOSE_IMAGE: intent.setAction(Intent.ACTION_GET_CONTENT); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); - } + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); intent.setType("image/*"); chooser = true; break; @@ -1722,9 +1722,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke case ATTACHMENT_CHOICE_CHOOSE_FILE: chooser = true; intent.setType("*/*"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); - } + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setAction(Intent.ACTION_GET_CONTENT); break; @@ -1807,7 +1805,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } private void showErrorMessage(final Message message) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()); builder.setTitle(R.string.error_message); final String errorMessage = message.getErrorMessage(); final String[] errorMessageParts = errorMessage == null ? new String[0] : errorMessage.split("\\u001f"); @@ -1828,7 +1826,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke private void deleteFile(final Message message) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()); builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.delete_file_dialog); builder.setMessage(R.string.delete_file_dialog_msg); @@ -1962,7 +1960,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } @Override - public void onSaveInstanceState(Bundle outState) { + public void onSaveInstanceState(@NotNull Bundle outState) { super.onSaveInstanceState(outState); if (conversation != null) { outState.putString(STATE_CONVERSATION_UUID, conversation.getUuid()); From 572b9c2dc6352c9e15052f23c3f621dc1fb06f24 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 20 Sep 2021 11:29:35 +0200 Subject: [PATCH 063/145] pulled translations from transifex --- src/main/res/values-bg/strings.xml | 56 ++++++++++++++++++- src/main/res/values-gl/strings.xml | 32 +++++------ src/main/res/values-ja/strings.xml | 86 +++++++++++++++--------------- src/main/res/values-vi/strings.xml | 8 ++- 4 files changed, 122 insertions(+), 60 deletions(-) diff --git a/src/main/res/values-bg/strings.xml b/src/main/res/values-bg/strings.xml index d27eda1f8d3500851a1822a222741bdba6ee0e2e..41b22aed687736e55b8e43a93620d0a78b1c9fce 100644 --- a/src/main/res/values-bg/strings.xml +++ b/src/main/res/values-bg/strings.xml @@ -29,10 +29,17 @@ току-що преди 1 минута преди %d минути + + %d непрочетен разговор + + + %d непрочетени разговора + + изпращане… Дешифроване на съобщението. Моля, изчакайте… Съобщение, шифр. чрез OpenPGP - Псевдонимът вече се използва + Псевдонимът е зает Неправилен псевдоним Администратор Собственик @@ -63,14 +70,23 @@ Деблокиране Запазване Добре + %1$s се срина + Използването на Вашия профил в XMPP за изпращане на проследявания на стека помага на разработката на %1$s. Изпращане сега Не ме питайте повече + Свързването с профила е невъзможно + Свързването с няколко профила е невъзможно + Докоснете за управление на профилите си Прикачане на файл + Добавяне на този липсващ контакт към списъка с контакти? Добавяне на контакт доставянето се провали + Изображението се подготвя за изпращане + Изображенията се подготвят за изпращане Споделяне на файлове. Моля, изчакайте… Изчистване на историята Изчистване на историята на разговорите + Наистина ли искате да изтриете всички съобщения в този разговор?\n\nВнимание: Това няма да изтрие съобщенията, които се съхраняват на други устройства или сървъри. Изтриване на файла Наистина ли искате да изтриете този файл?\n\nВнимание: Това няма да изтрие копията на файла, които се съхраняват на други устройства или сървъри. Затваряне на този разговор след това @@ -81,16 +97,20 @@ Изпр. на съобщение, шифр. чрез OMEMO Изпр. на съобщение, шифр. чрез v\\OMEMO Изпр. на съобщение, шифр. чрез OpenPGP + Новият псевдоним е зает Изпращане нешифровано Неуспешно дешифроване. Възможно е да нямате правилния частен ключ. OpenKeychain + OpenKeychain, за да шифрова и дешифрова съобщенията и да управлява публичните Ви ключове.OpenKeychain е с лиценз GPLv3+ и може се свали от F-Droid и Google Play.

(Моля, рестартирайте %1$s след това.)]]>
Рестартиране Инсталиране Моля, инсталирайте OpenKeychain предлагане… изчакване… Не е открит OpenPGP ключ + Съобщението Ви не може да се шифрова, тъй като контактът Ви не е обявил публичния си ключ.\n\nМоля, помолете го да инсталира и настрои OpenPGP. Не са открити OpenPGP ключове + Съобщението Ви не може да се шифрова, тъй като контактите Ви не са обявили публичните си ключове.\n\nМоля, помолете ги да инсталират и настроят OpenPGP. Общи Приемане на файлове Автоматично приемане на файлове с размер, по-малък от… @@ -101,12 +121,20 @@ Известие чрез светодиода Мигане на индикаторния светодиод при получаване на ново съобщение Тон на звънене + Звук за известията + Звук за известията при получаване на нови съобщения + Звук за входящи обаждания Период на пренебрегване + Продължителност на времето, през което известията се заглушават, след като бъде забележена дейност на някое от другите Ви устройства. Разширени Никога да не се изпращат доклади за сривове + Изпращайки проследявания на стека, Вие помагате на разработката Потвърждаване на съобщенията Така контактите Ви ще разбират, че сте получили и прочели съобщенията им + Забраняване на снимките на екрана + Скриване на съдържанието на приложенията от превключвателя на приложения и блокиране на снимките на екрана Потр. интерфейс + Възникна грешка в OpenKeychain. Неправилен ключ за шифроване. Приемане Възникна грешка @@ -119,8 +147,11 @@ Заснемане Предварително позволяване на абониране при заявка Избраният файл не е изображение + Файлът с изображението не може да бъде преобразуван Файлът не е открит Обща В/И грешка. Може би нямате достатъчно свободно място? + Приложението, което използвахте, за да изберете това изображение, не предоставя нужните права за прочитането му.\n\nИзползвайте друг файлов мениджър, за да изберете изображение. + Приложението, което използвахте, за да споделите този файл, не предоставя нужните правомощия. Непознат Временно деактивиран На линия @@ -132,7 +163,9 @@ Неуспешна регистрация Потребителското име е заето Регистрацията е завършена + Регистрацията не се поддържа от сървъра Договарянето чрез TLS беше неуспешно + Домейнът не може да се провери Нарушение на политиката Несъвместим сървър Поточна грешка @@ -147,13 +180,16 @@ Публикуване на публичния OpenPGP ключ Премахване на публичния OpenPGP ключ Наистина ли искате да премахнете своя публичен OpenPGP ключ от известяването си за присъствие?\nКонтактите Ви вече няма да могат да Ви изпращат съобщение, шифровани чрез OpenPGP. + Публичният OpenPGP ключ е публикуван. Активиране на профила Сигурни ли сте? + Изтриването на профила Ви ще изтрие и цялата история на разговорите Ви Запис на глас XMPP адрес username@example.com Парола Това не е валиден XMPP адрес + Няма достатъчно памет. Изображението е твърде голямо. Искате ли да добавите %s към адресния си указател? Инф. за сървъра XEP-0313: Управление на архива на съобщенията @@ -162,6 +198,7 @@ XEP-0191: Команда за блокиране XEP-0237: Поддържане на версия на списъка XEP-0198: Управление на потоците + XEP-0215: Откриване на външни услуги XEP-0163: PEP (Аватари / OMEMO) XEP-0363: Качване на файл през HTTP XEP-0357: Изпращане @@ -169,12 +206,19 @@ не е налично Липсват обявления за публичен ключ последно видян току-що + последно видян преди една минута последно видян преди %d минути + последно видян преди час последно видян преди %d часа + последно видян преди ден последно видян преди %d дни + Шифровано съобщение. Моля, инсталирайте OpenKeychain, за да го дешифровате. + Открити са нови съобщения, шифровани чрез OpenPGP Ид. на OpenPGP ключа Отпечатък OMEMO Отпечатък v\\OMEMO + Отпечатък OMEMO (източник на съобщението) + v\\Отпечатък OMEMO (източник на съобщението) Други устройства Доверяване на отпечатъци OMEMO Изтегляне на ключове… @@ -190,11 +234,16 @@ Избиране Контактът вече съществува Присъединяване + канал@беседа.сървър.com/псевдоним + канал@беседа.сървър.com Запазване като отметка Изтриване на отметка Унищожаване на груповия разговор + Унищожаване на канала Наистина ли искате да унищожите този групов разговор?\n\nВнимание: Груповият разговор ще бъде премахнат от сървъра. + Наистина ли искате да унищожите този групов канал?\n\nВнимание: Груповият канал ще бъде напълно премахнат от сървъра. Груповият разговор не може да бъде унищожен + Каналът не може да бъде унищожен Редактиране на темата на груповия разговор Тема Присъединяване в групов разговор… @@ -203,18 +252,23 @@ Добавяне обратно %s е прочел до тук %s човека са прочели до тук + %1$s и още %2$d човека са прочели до тук Всички са прочели до тук Публикуване + Докоснете аватара, за да изберете снимка от галерията Публикуване… Сървърът отказа Вашето публикуване + Снимката Ви не може да бъде преобразувана Неуспешно запазване на аватара на диска (Или задръжте, за да върнете началното) + Сървърът Ви не поддържа публикуване на аватари прошепна на %s Изпращане на лично съобщение до %s Свързване Този профил вече съществува Следващо + Установена сесия Пропускане Изключване на известията Включване diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index 808a00ca05366a21186a7edf07383f44471e8f89..a47a27885af476a6e92f0e4eebddcb38bb23a263 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -47,8 +47,8 @@ Participante Visitante Desexas eliminar a %s da túa lista de contactos? As conversas con este contacto non se eliminarán. - Desexa evitar que %s lle envíe mensaxes? - Desexa desbloquear %s e permitirlles que lle envíen mensaxes? + Queres evitar que %s che envíe mensaxes? + Queres desbloquear a %s e permitirlle que che envíe mensaxes? Bloquear todos os contactos desde 1%s? Desbloquear todos os contactos desde 1%s? Contacto bloqueado @@ -492,7 +492,7 @@ Anovar certificado Fallo obtendo a chave OMEMO! Comprobouse a chave OMEMO co certificado! - O seu dispositivo non admite a selección de certificados do cliente! + O teu dispositivo non admite a selección de certificados do cliente! Conexión Conectar vía Tor Facer pasar todas as conexións a través da rede Tor. Require Orbot @@ -541,7 +541,7 @@
Podes rexistrarte co teu número de teléfono e Quicksy suxerirache automáticamente —tomando os números da túa libreta de enderezos como referencia— posibles contactos para ti.

Ao rexistrarte aceptas a nosa política de privacidade.]]>
Aceptar e continuar Tes unha guía para crear unha conta en conversations.im¹\nAo escoller conversations.im como provedor poderás comunicarte con outras usuarias de outros provedores con só darlles o teu enderezo XMPP completo. - O seu enderezo XMPP completo será: %s + O teu enderezo XMPP completo será: %s Crear conta Utilizar o meu propio proveedor Elixe un identificador @@ -554,7 +554,7 @@ Non dipoñible Ocupada Xerouse un contrasinal seguro - O seu dispositivo non permite non optimizar a batería + O teu dispositivo non permite evitar a optimización a batería Fallo no rexistro: inténteo de novo Fallo no rexistro: contrasinal moi feble Escoller participantes @@ -594,7 +594,7 @@ Borrar identidades OMEMO Rexenerar chaves OMEMO. Todos os teus contactos terán que verificar a túa conta de novo. Utiliza esto só como último recurso. Eliminar as chaves seleccionadas. - Precisa estar conectada para publicar o seu avatar. + Debes ter conexión para publicar o teu avatar. Mostrar mensaxe do fallo Mensaxe de fallo Aforrador de datos habilitado @@ -783,15 +783,15 @@ Código de país non válido Indica un país número de teléfono - Valide o seu número de teléfono - Quicsy enviaralle unha mensaxe SMS (poderíanse aplicar cargos) para validar o seu número de teléfono. Introduza o código de país e número de teléfono. + Valida o teu número de teléfono + Quicksy vaiche enviar unha mensaxe SMS (podería ter custos) para validar o teu número de teléfono. Escribe o código de país e número de teléfono.
%s

É correcto, ou quere modificar o número?]]>
%s non é un número de teléfono válido. - Por favor introduza o seu número de teléfono. + Por favor escribe o teu número de teléfono. Buscar países Validar %s - %s.]]> - Enviamoslle outro SMS con un código de 6 díxitos. + %s.]]> + Enviamosche outro SMS cun código de 6 díxitos. Por favor, introduza o pin de 6 díxitos inferior. Reenviar SMS Reenviar SMS (%s) @@ -805,7 +805,7 @@ Validando... Solicitando SMS... O pin introducido non é correcto. - O pin que lle enviamos caducou. + O pin que che enviamos caducou. Fallo descoñecido na rede. Resposta descoñecida desde o servidor. Non se puido conectar co servidor. @@ -822,9 +822,9 @@ Actualizar Este número de teléfono está actualmente ligado a outro dispositivo. Por favor, escribe o teu nome para permitir que a xente que non te ten na axenda de enderezos sepa quen es. - O seu nome - Introduza o seu nome - Utilice o botón editar para escribir o seu nome. + O teu nome + Escribe o teu nome + Establece o teu nome usando o botón editar. Rexeitar solicitude Instalar Orbot Iniciar Orbot @@ -834,7 +834,7 @@ Orixinal (non comprimido) Abrir con... Imaxe de perfil en Conversations - Escoller conta + Elexir conta Restablecer copia de apoio Restablecer Escribe o contrasinal da conta %s para restablecer a copia. diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index a5cb3837e3220993a91fe5696ed9608bb4fd9193..a6d2842ce75ff507148690945fc43fceecc343e7 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -37,7 +37,7 @@ メッセージを復号しています。しばらくお待ちください… OpenPGP 暗号化メッセージ ニックネームは既に使用されています - 正しくないニックネームです + 不正なニックネーム 管理者 所有者 調停者 @@ -134,7 +134,7 @@ UI OpenKeychain でエラーが発生しました。 暗号化の鍵が不正です。 - 受付 + 受け入れる エラーが発生しました エラー あなたのアカウント @@ -143,7 +143,7 @@ 参加アップデートを問合せ 画像を選択 写真を撮影 - 事前にサブスクリプション要求を許可する + サブスクリプション要求を事前に付与する 選択したファイルは画像ではありません 画像ファイルを変換できません ファイルが見つかりません @@ -294,10 +294,10 @@ OMEMO フィンガープリントをクリップボードにコピーしました このグループチャットから出禁にされています このグループチャットはメンバー制です - リソース制約 + リソース制限 このグループチャットから蹴り出されています このグループチャットは閉鎖されました - 既にこのグループチャットに参加していません + あなたはもうこのグループチャットに参加していません アカウント %s を使用 %s 上でホストされた HTTP ホスト上の %s を確認中 @@ -319,7 +319,7 @@ ブロック一覧を表示 アカウントの詳細 確認 - 再度実行してください + 後でもう一度お試しください フォアグラウンドサービス オペレーティングシステムが接続を切断するのを防止します バックアップを作成 @@ -401,7 +401,7 @@ なし 通知が来るまで スヌーズ - 返信する + 返信 既読にする 入力 Enter で送信 @@ -471,7 +471,7 @@ バイブレートをサイレントモードとして扱う デバイスがバイブレートのときは取込中と表示 拡張接続設定 - アカウントを設定するときにホスト名とポートの設定を表示します + アカウントを設定するときに、ホスト名とポートの設定を表示 xmpp.example.com 証明書でログイン 証明書を解析できません @@ -481,7 +481,7 @@ アーカイブ設定を取得できません キャプチャが要求されました 上の画像からテキストを入力してください - 信頼されていない証明書チェーン + 信頼できない証明書チェーン XMPP アドレスが証明書と一致しません 証明書を更新 OMEMO 鍵の取得中にエラー! @@ -536,7 +536,7 @@ conversations.im 上にアカウントを作成する設定の指南です。¹\nconversations.im をプロバイダーとして選択した場合、あなたの完全な XMPP アドレスを他のプロバイダーのユーザーに示すことで、その人と連絡をとることができます。 あなたの完全なXMPPアドレスは: %s アカウントを作成 - 独自のプロバイダーを使用する + 自分のプロバイダーを使用 ユーザー名を選択 在席状況を手動で管理 ステータスメッセージの編集時に、在席状況を設定します。 @@ -548,7 +548,7 @@ 取込中 安全なパスワードが生成されました お使いのデバイスは電池最適化の停止をサポートしていません - 登録に失敗しました: 後でもう一度試してください + 登録に失敗しました: 後でもう一度お試しください 登録に失敗しました: パスワードが弱すぎます 参加者を選択 グループチャットを作成しています… @@ -572,7 +572,7 @@ コンピューター 携帯電話 タブレット - Web ブラウザー + ウェブブラウザ コンソール 支払が必要です インターネット使用権限の付与 @@ -604,10 +604,10 @@ バーコードで共有 XMPP URI で共有 HTTP リンクで共有 - 検証前に白紙信託する + 検証前の盲目的な信頼 認証されていない連絡先からの新規デバイスを信頼するが、認証されている連絡先からの新規デバイスについては手動での確認を求める。 OMEMO 鍵を盲目的に信用していた。つまり、他の人かもしれないし、誰かが盗聴しているかもしれない。 - 信頼されていない + 信頼できない 不正な二次元バーコード キャッシュフォルダを消去します (カメラアプリで使用) キャッシュを消去 @@ -654,14 +654,14 @@ 復号を再試行 セッション失敗 ダウングレードされた SASL メカニズム - サーバーはWebサイトでの登録が必要です - Webサイトを開く - Webサイトを開くアプリが見つかりません + サーバーはウェブサイトでの登録が必要です + ウェブサイトを開く + ウェブサイトを開くアプリが見つかりません Heads-up 通知 Heads-up 通知を表示 今日 昨日 - DNSSEC でホスト名を検証 + DNSSEC でホスト名の妥当性を確認 検証されたホスト名を含むサーバー証明書は検証済みと見なされます 証明書は XMPP アドレスを含みません 一時的 @@ -671,10 +671,10 @@ メッセージ 非公開メッセージを無効にしました 保護されたアプリ - 画面がオフになっている場合でも通知を受信し続けるには、保護されたアプリの一覧に Conversations を追加する必要があります。 + 画面がオフになっているときでも通知を受信し続けるには、保護されたアプリの一覧に Conversations を追加する必要があります。 未知の証明書を受け入れますか? サーバー証明書が既知の認証局によって署名されていません。 - 不一致なサーバー名を受け入れますか? + サーバー名の不一致を受け入れますか? サーバーは\"%s\"として認証できませんでした。証明書は次の場合にのみ有効です: それでも接続を希望しますか? 証明書の詳細: @@ -684,12 +684,12 @@ メッセージ送信後に下へスクロール ステータスメッセージを編集 ステータスメッセージを編集 - 暗号化をしない + 暗号化が無効 %1$s は %2$s に暗号化メッセージを送れません。連絡先が利用しているサーバーが古すぎるか、クライアントが OMEMO を扱えません。 デバイスの一覧を取得できません 暗号化の鍵を取得できません ヒント: お互いが連絡先名簿に加えれば解決するでしょう。 - この会話でOMEMOの暗号化を無効にしてよろしいですか?\n\nこれにより、サーバー管理者がメッセージを読むことが可能になりますが、時代遅れのクライアントを使っている人と連絡をとるには、この方法しかないかもしれません。 + この会話で OMEMO の暗号化を無効にしてもよろしいですか?\nこれにより、サーバー管理者がメッセージを読むことが可能になりますが、時代遅れのクライアントを使っている人と連絡をとるには、この方法しかないかもしれません。 今すぐ無効化 下書き: OMEMO 暗号化 @@ -737,6 +737,7 @@ 名前の記入は任意です グループチャット名 このグループチャットは破棄されました + 録音を保存できません フォアグラウンドサービス この通知カテゴリーは %1$s が実行していることを表示する、永続的な通知を表示するために使用されます。 ステータス情報 @@ -745,13 +746,13 @@ メッセージ 通話 メッセージ - 着信 - 発信 + 着信通話 + 継続中の通話 サイレントメッセージ この通知グループは、音を鳴らしてはいけない通知を表示するために使用します。例えば、他のデバイスでアクティブになっているときなどです (猶予期間)。 配信に失敗 メッセージ通知設定 - 通話着信の通知設定 + 着信通話の通知設定 重要性、音、振動 ビデオの圧縮 メディアを表示 @@ -763,7 +764,7 @@ 中 (360p) 高 (720p) あなたは既にメッセージを作成中です。 - 未実装の機能 + 実装されてない機能 不正な国コード 国を選択 電話番号 @@ -797,7 +798,7 @@ サーバーが見つかりません。 要求の処理中に、何か問題が発生しました。 無効なユーザーの入力 - 一時的に入手不可能です。後でもう一度試してください。 + 一時的に入手不可能です。後でもう一度お試しください。 ネットワーク接続なし。 %s でもう一度お試しください。 上限に到達しました @@ -813,7 +814,7 @@ Orbot をインストール Orbot を開始 マーケットアプリがインストールされていません。 - この談話室では、あなたのXMPPアドレスを公開します + この談話室では、あなたの XMPP アドレスを公開します 電子書籍 原物 (非圧縮) …で開く @@ -849,7 +850,7 @@ 誰でも他の人を招待できます。 XMPP アドレスは管理者が見れます。 XMPP アドレスは誰でも見れます。 - この公開談話室には参加者がいません。連絡先を招待したり、共有ボタンを使って XMPP アドレスを配布できます。 + この公開談話室には参加者がいません。連絡先を招待したり、共有ボタンを使用して XMPP アドレスを配布できます。 この非公開グループチャットには参加者がいません。 権限を管理 参加者を検索 @@ -860,7 +861,7 @@ プライバシー侵害の可能性あり! search.jabber.networkを利用します。

この機能を使うと、あなたののIPアドレスや検索キーワードがそのサービスに送信されます。詳しくは、プライバシーポリシーをご覧ください。]]>
既にアカウントを持っています - 存在するアカウントを追加 + 既存アカウントを追加 新しいアカウントを登録 これはドメインアドレスのようです とにかく追加 @@ -874,7 +875,7 @@ このアカウントのパスワートを入力してください この操作を実行できません 公開談話室に参加… - 共有アプリがこのファイルへのアクセスを許可していませんでした。 + 共有アプリがこのファイルへのアクセス権限を付与していませんでした。 jabber.network ローカルサーバー @@ -882,8 +883,8 @@ 談話室の発見方法 アカウントを有効にしてください 通話をする - 通話着信 - 映像通話着信 + 着信通話 + 着信映像通話 接続中 接続しました 通話受入 @@ -896,21 +897,22 @@ 通話に接続できません 接続切断 撤回された通話 - ハングアップ + 検証に問題 + 電話を切る 継続中の通話 継続中の映像通話 通話するのに Tor を無効にする - 通話着信 - 通話着信・%s - 不在通話着信・%s - 通話発信 - 通話発信・%s - 不在通話着信 + 着信通話 + 着信通話・%s + 不在着信通話・%s + 発信通話 + 発信通話・%s + 不在着信通話 音声通話 映像通話 会話に切り替え マイクが利用できません - 1回につき1回線のみ + 1度に1回線の通話のみ。 継続中の通話に戻る カメラを切り替えできません 最上に留める @@ -940,7 +942,7 @@ アプリケーションが見つかりません 会話に招待 招待を解析できません - サーバーは招待をサポートしていません + サーバーは招待の作成をサポートしていません この機能をサポートするアクティブなアカウントがありません バックアップを開始しました。 バックアップが完了すると通知が届きます。 映像を有効化できません。 diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml index 31cd4d7355ba5fb272e68505e2be6761b3e2f23e..9fef998b039213ac67c19d8fc01dcacc82a07aaf 100644 --- a/src/main/res/values-vi/strings.xml +++ b/src/main/res/values-vi/strings.xml @@ -129,6 +129,8 @@ Bằng việc gửi báo cáo hoạt động, bạn đang hỗ trợ sự phát triển Xác nhận tin nhắn Báo cho liên hệ của bạn biết khi bạn đã nhận và đọc tin nhắn + Ngăn chặn chụp màn hình + Ẩn nội dung ứng dụng trong màn hình chuyển ứng dụng và chặn chụp màn hình UI OpenKeychain đã có lỗi. Mã khoá mã hoá bị lỗi. @@ -411,6 +413,7 @@ âm thanh video hình ảnh + hình ảnh véc tơ tài liệu PDF Ứng dụng Android Liên hệ @@ -901,6 +904,7 @@ Đã mất kết nối Cuộc gọi đã bị rút lại Lỗi ứng dụng + Vấn đề xác minh Cúp máy Cuộc gọi đang diễn ra Cuộc gọi video đang diễn ra @@ -950,4 +954,6 @@ Không có tài khoản đang hoạt động nào hỗ trợ tính năng này Việc sao lưu đã được bắt đầu. Bạn sẽ nhận một thông báo khi việc đó đã hoàn tất. Không thể bật video. - + Tài liệu văn bản thuần + + From d5994a8d65a902633bd04001b854cadfcf804460 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 Sep 2021 10:19:05 +0200 Subject: [PATCH 064/145] add to address book should add phone number for Quicksy+quicksy.im fixes #4165 --- .../utils/PhoneNumberUtilWrapper.java | 2 ++ .../ui/ContactDetailsActivity.java | 24 +++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java diff --git a/src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java b/src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..89d58e220d150b4e84446d5404c0cf5f8c5ff2b4 --- /dev/null +++ b/src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java @@ -0,0 +1,2 @@ +package eu.siacs.conversations.utils;public class PhoneNumberUtilWrapper { +} diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index eb85d46e4a5f1a54be076b4a72a5f31c35e3c0a7..e1ea08b5f66c15df5efff80b4fa415d1067fb9f4 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -45,6 +45,7 @@ import eu.siacs.conversations.databinding.ActivityContactDetailsBinding; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.ListItem; +import eu.siacs.conversations.services.AbstractQuickConversationsService; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.ui.adapter.MediaAdapter; @@ -58,6 +59,7 @@ 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.PhoneNumberUtilWrapper; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xml.Namespace; @@ -131,17 +133,29 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp } private void showAddToPhoneBookDialog() { - //TODO check if isQuicksy and contact is on quicksy.im domain - // store in final boolean. show different message. use phone number for add + final Jid jid = contact.getJid(); + final boolean quicksyContact = AbstractQuickConversationsService.isQuicksy() + && Config.QUICKSY_DOMAIN.equals(jid.getDomain()) + && jid.getLocal() != null; + final String value; + if (quicksyContact) { + value = PhoneNumberUtilWrapper.toFormattedPhoneNumber(this, jid); + } else { + value = jid.toEscapedString(); + } final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.action_add_phone_book)); - builder.setMessage(getString(R.string.add_phone_book_text, contact.getJid().toEscapedString())); + builder.setMessage(getString(R.string.add_phone_book_text, value)); builder.setNegativeButton(getString(R.string.cancel), null); builder.setPositiveButton(getString(R.string.add), (dialog, which) -> { final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); intent.setType(Contacts.CONTENT_ITEM_TYPE); - intent.putExtra(Intents.Insert.IM_HANDLE, contact.getJid().toEscapedString()); - intent.putExtra(Intents.Insert.IM_PROTOCOL, CommonDataKinds.Im.PROTOCOL_JABBER); + if (quicksyContact) { + intent.putExtra(Intents.Insert.PHONE, value); + } else { + intent.putExtra(Intents.Insert.IM_HANDLE, value); + intent.putExtra(Intents.Insert.IM_PROTOCOL, CommonDataKinds.Im.PROTOCOL_JABBER); + } intent.putExtra("finishActivityOnSaveCompleted", true); try { startActivityForResult(intent, 0); From 75c20a7a2b99db8e46639a4f6a67bcdccdf50d1a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 Sep 2021 10:20:21 +0200 Subject: [PATCH 065/145] handle on-device contacts with unstable system uri on device contacts (contacts not synced) have an unstable system uri. For quicksy.im contacts we can identify the contact based on the phone number instead. fixes #4174 --- .../utils/PhoneNumberUtilWrapper.java | 11 ++++- .../ui/ConversationsActivity.java | 2 +- .../android/PhoneNumberContact.java | 43 +++++++++++-------- .../siacs/conversations/entities/Entry.java | 16 +++---- .../services/QuickConversationsService.java | 14 +++++- 5 files changed, 57 insertions(+), 29 deletions(-) diff --git a/src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java b/src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java index 89d58e220d150b4e84446d5404c0cf5f8c5ff2b4..2f7963cf69d20ae746b3aff661a036606e86415f 100644 --- a/src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java +++ b/src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java @@ -1,2 +1,11 @@ -package eu.siacs.conversations.utils;public class PhoneNumberUtilWrapper { +package eu.siacs.conversations.utils; + +import android.content.Context; + +import eu.siacs.conversations.xmpp.Jid; + +public class PhoneNumberUtilWrapper { + public static String toFormattedPhoneNumber(Context context, Jid jid) { + throw new AssertionError("This method is not implemented in Conversations"); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index 2c2c4fe48256fd6f77f6822f85f8d8846f3cc207..21032ea04d94407674a20a5a744ab02665ebf8d4 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -538,6 +538,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio @Override protected void onStart() { + super.onStart(); final int theme = findTheme(); if (this.mTheme != theme) { this.mSkipBackgroundBinding = true; @@ -546,7 +547,6 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio this.mSkipBackgroundBinding = false; } mRedirectInProcess.set(false); - super.onStart(); } @Override diff --git a/src/quicksy/java/eu/siacs/conversations/android/PhoneNumberContact.java b/src/quicksy/java/eu/siacs/conversations/android/PhoneNumberContact.java index 6afb5a0d75b1620e7e7a04399fd1c72dc1537ac1..a8bbf88d9579451ba8e6c88d4df7649352531521 100644 --- a/src/quicksy/java/eu/siacs/conversations/android/PhoneNumberContact.java +++ b/src/quicksy/java/eu/siacs/conversations/android/PhoneNumberContact.java @@ -46,30 +46,30 @@ public class PhoneNumberContact extends AbstractPhoneContact { ContactsContract.Data.PHOTO_URI, ContactsContract.Data.LOOKUP_KEY, ContactsContract.CommonDataKinds.Phone.NUMBER}; - final Cursor cursor; - try { - cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, null, null, null); - } catch (final Exception e) { - return ImmutableMap.of(); - } final HashMap contacts = new HashMap<>(); - while (cursor != null && cursor.moveToNext()) { - try { - final PhoneNumberContact contact = new PhoneNumberContact(context, cursor); - final PhoneNumberContact preexisting = contacts.get(contact.getPhoneNumber()); - if (preexisting == null || preexisting.rating() < contact.rating()) { - contacts.put(contact.getPhoneNumber(), contact); + try (final Cursor cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, null, null, null)){ + while (cursor != null && cursor.moveToNext()) { + try { + final PhoneNumberContact contact = new PhoneNumberContact(context, cursor); + final PhoneNumberContact preexisting = contacts.get(contact.getPhoneNumber()); + if (preexisting == null || preexisting.rating() < contact.rating()) { + contacts.put(contact.getPhoneNumber(), contact); + } + } catch (final IllegalArgumentException ignored) { + } - } catch (final IllegalArgumentException e) { - Log.d(Config.LOGTAG, e.getMessage()); } - } - if (cursor != null) { - cursor.close(); + } catch (final Exception e) { + return ImmutableMap.of(); } return ImmutableMap.copyOf(contacts); } + public static PhoneNumberContact findByUriOrNumber(Collection haystack, Uri uri, String number) { + final PhoneNumberContact byUri = findByUri(haystack, uri); + return byUri != null || number == null ? byUri : findByNumber(haystack, number); + } + public static PhoneNumberContact findByUri(Collection haystack, Uri needle) { for (PhoneNumberContact contact : haystack) { if (needle.equals(contact.getLookupUri())) { @@ -78,4 +78,13 @@ public class PhoneNumberContact extends AbstractPhoneContact { } return null; } + + private static PhoneNumberContact findByNumber(Collection haystack, String needle) { + for (PhoneNumberContact contact : haystack) { + if (needle.equals(contact.getPhoneNumber())) { + return contact; + } + } + return null; + } } diff --git a/src/quicksy/java/eu/siacs/conversations/entities/Entry.java b/src/quicksy/java/eu/siacs/conversations/entities/Entry.java index 6ff19f09d9303b240410c1e9e891baf2fa6e990a..c202be4707a0344143a3e6be3553e062aaa7d33d 100644 --- a/src/quicksy/java/eu/siacs/conversations/entities/Entry.java +++ b/src/quicksy/java/eu/siacs/conversations/entities/Entry.java @@ -2,6 +2,9 @@ package eu.siacs.conversations.entities; import android.util.Base64; +import com.google.common.base.Charsets; +import com.google.common.hash.Hashing; + import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -63,20 +66,15 @@ public class Entry implements Comparable { builder.append(jid.asBareJid().toEscapedString()); } } - MessageDigest md; - try { - md = MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - return ""; - } - byte[] sha1 = md.digest(builder.toString().getBytes()); + @SuppressWarnings("deprecation") + final byte[] sha1 = Hashing.sha1().hashString(builder.toString(), Charsets.UTF_8).asBytes(); return new String(Base64.encode(sha1, Base64.DEFAULT)).trim(); } private static List ofPhoneNumberContactsAndContacts(final Collection phoneNumberContacts, Collection systemContacts) { - ArrayList entries = new ArrayList<>(); + final ArrayList entries = new ArrayList<>(); for(Contact contact : systemContacts) { - PhoneNumberContact phoneNumberContact = PhoneNumberContact.findByUri(phoneNumberContacts, contact.getSystemAccount()); + final PhoneNumberContact phoneNumberContact = PhoneNumberContact.findByUri(phoneNumberContacts, contact.getSystemAccount()); if (phoneNumberContact != null && phoneNumberContact.getPhoneNumber() != null) { Entry entry = findOrCreateByPhoneNumber(entries, phoneNumberContact.getPhoneNumber()); entry.jids.add(contact.getJid().asBareJid()); diff --git a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java index f977758f2ae547e0a9a9953d16370378379c6544..14a2c1734cfb8fcd013165f57de222958e3bb49b 100644 --- a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java +++ b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java @@ -382,9 +382,13 @@ public class QuickConversationsService extends AbstractQuickConversationsService if (uri == null) { continue; } - PhoneNumberContact phoneNumberContact = PhoneNumberContact.findByUri(contacts, uri); + final String number = getNumber(contact); + final PhoneNumberContact phoneNumberContact = PhoneNumberContact.findByUriOrNumber(contacts, uri, number); final boolean needsCacheClean; if (phoneNumberContact != null) { + if (!uri.equals(phoneNumberContact.getLookupUri())) { + Log.d(Config.LOGTAG, "lookupUri has changed from " + uri + " to " + phoneNumberContact.getLookupUri()); + } needsCacheClean = contact.setPhoneContact(phoneNumberContact); } else { needsCacheClean = contact.unsetPhoneContact(PhoneNumberContact.class); @@ -396,6 +400,14 @@ public class QuickConversationsService extends AbstractQuickConversationsService } } + private static String getNumber(final Contact contact) { + final Jid jid = contact.getJid(); + if (jid.getLocal() != null && Config.QUICKSY_DOMAIN.equals(jid.getDomain())) { + return jid.getLocal(); + } + return null; + } + private boolean considerSync(final Account account, final Map contacts, final boolean forced) { final int hash = contacts.keySet().hashCode(); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": consider sync of " + hash); From 64a6edd3fb42e51db54a68f098004b20d665efc7 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 Sep 2021 11:41:35 +0200 Subject: [PATCH 066/145] Revert "Migrate Fragments to AndroidX" This reverts commit 231d97ea81bb2249afa432b43d78ac963b80594a. Migrating to AndroidX Fragments seems to have some unforseen side effects and no clear benefits --- .../ui/ConversationFragment.java | 43 +++++++-------- .../ui/ConversationsActivity.java | 54 +++++++++---------- .../ui/ConversationsOverviewFragment.java | 15 +++--- .../siacs/conversations/ui/XmppFragment.java | 3 +- .../ui/adapter/ConversationAdapter.java | 2 +- .../ui/adapter/MessageAdapter.java | 4 +- .../ui/util/MucDetailsContextMenuHelper.java | 2 +- 7 files changed, 61 insertions(+), 62 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index bf9aa93d3e3369689fbc0c84e014c78dbc3acbaa..5bc24fee371008584f465c1afda8b8fb9e210eda 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -3,6 +3,8 @@ package eu.siacs.conversations.ui; import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; @@ -50,8 +52,6 @@ import androidx.appcompat.app.AlertDialog; import androidx.core.view.inputmethod.InputConnectionCompat; import androidx.core.view.inputmethod.InputContentInfoCompat; import androidx.databinding.DataBindingUtil; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; import com.google.common.base.Optional; @@ -467,41 +467,41 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke private boolean firstWord = false; private Message mPendingDownloadableMessage; - private static ConversationFragment findConversationFragment(FragmentManager fragmentManager) { - Fragment fragment = fragmentManager.findFragmentById(R.id.main_fragment); + private static ConversationFragment findConversationFragment(Activity activity) { + Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); if (fragment instanceof ConversationFragment) { return (ConversationFragment) fragment; } - fragment = fragmentManager.findFragmentById(R.id.secondary_fragment); + fragment = activity.getFragmentManager().findFragmentById(R.id.secondary_fragment); if (fragment instanceof ConversationFragment) { return (ConversationFragment) fragment; } return null; } - public static void startStopPending(FragmentManager fragmentManager) { - ConversationFragment fragment = findConversationFragment(fragmentManager); + public static void startStopPending(Activity activity) { + ConversationFragment fragment = findConversationFragment(activity); if (fragment != null) { fragment.messageListAdapter.startStopPending(); } } - public static void downloadFile(FragmentManager fragmentManager, Message message) { - ConversationFragment fragment = findConversationFragment(fragmentManager); + public static void downloadFile(Activity activity, Message message) { + ConversationFragment fragment = findConversationFragment(activity); if (fragment != null) { fragment.startDownloadable(message); } } - public static void registerPendingMessage(FragmentManager fragmentManager, Message message) { - ConversationFragment fragment = findConversationFragment(fragmentManager); + public static void registerPendingMessage(Activity activity, Message message) { + ConversationFragment fragment = findConversationFragment(activity); if (fragment != null) { fragment.pendingMessage.push(message); } } - public static void openPendingMessage(FragmentManager fragmentManager) { - ConversationFragment fragment = findConversationFragment(fragmentManager); + public static void openPendingMessage(Activity activity) { + ConversationFragment fragment = findConversationFragment(activity); if (fragment != null) { Message message = fragment.pendingMessage.pop(); if (message != null) { @@ -510,12 +510,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } - public static Conversation getConversation(FragmentManager fragmentManager) { - return getConversation(fragmentManager, R.id.secondary_fragment); + public static Conversation getConversation(Activity activity) { + return getConversation(activity, R.id.secondary_fragment); } - private static Conversation getConversation(FragmentManager fragmentManager, @IdRes int res) { - final Fragment fragment = fragmentManager.findFragmentById(res); + private static Conversation getConversation(Activity activity, @IdRes int res) { + final Fragment fragment = activity.getFragmentManager().findFragmentById(res); if (fragment instanceof ConversationFragment) { return ((ConversationFragment) fragment).getConversation(); } else { @@ -523,7 +523,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } - public static ConversationFragment get(FragmentManager fragmentManager) { + public static ConversationFragment get(Activity activity) { + FragmentManager fragmentManager = activity.getFragmentManager(); Fragment fragment = fragmentManager.findFragmentById(R.id.main_fragment); if (fragment instanceof ConversationFragment) { return (ConversationFragment) fragment; @@ -533,12 +534,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } - public static Conversation getConversationReliable(FragmentManager fragmentManager) { - final Conversation conversation = getConversation(fragmentManager, R.id.secondary_fragment); + public static Conversation getConversationReliable(Activity activity) { + final Conversation conversation = getConversation(activity, R.id.secondary_fragment); if (conversation != null) { return conversation; } - return getConversation(fragmentManager, R.id.main_fragment); + return getConversation(activity, R.id.main_fragment); } private static boolean scrolledToBottom(AbsListView listView) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index 21032ea04d94407674a20a5a744ab02665ebf8d4..fbdba5724853353d666049555af517418fa6d007 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -34,6 +34,9 @@ import static eu.siacs.conversations.ui.ConversationFragment.REQUEST_DECRYPT_PGP import android.annotation.SuppressLint; import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; @@ -52,9 +55,6 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; import org.openintents.openpgp.util.OpenPgpApi; @@ -165,8 +165,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } invalidateActionBarTitle(); - if (binding.secondaryFragment != null && ConversationFragment.getConversation(getSupportFragmentManager()) == null) { - Conversation conversation = ConversationsOverviewFragment.getSuggestion(getSupportFragmentManager()); + if (binding.secondaryFragment != null && ConversationFragment.getConversation(this) == null) { + Conversation conversation = ConversationsOverviewFragment.getSuggestion(this); if (conversation != null) { openConversation(conversation, null); } @@ -202,7 +202,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (xmppConnectionService == null) { return; } - final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_fragment); + final Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment); if (fragment instanceof ConversationsOverviewFragment) { if (ExceptionHelper.checkForCrash(this)) { return; @@ -255,14 +255,14 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void notifyFragmentOfBackendConnected(@IdRes int id) { - final Fragment fragment = getSupportFragmentManager().findFragmentById(id); + final Fragment fragment = getFragmentManager().findFragmentById(id); if (fragment instanceof OnBackendConnected) { ((OnBackendConnected) fragment).onBackendConnected(); } } private void refreshFragment(@IdRes int id) { - final Fragment fragment = getSupportFragmentManager().findFragmentById(id); + final Fragment fragment = getFragmentManager().findFragmentById(id); if (fragment instanceof XmppFragment) { ((XmppFragment) fragment).refresh(); } @@ -288,10 +288,10 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio switch (requestCode) { case REQUEST_OPEN_MESSAGE: refreshUiReal(); - ConversationFragment.openPendingMessage(getSupportFragmentManager()); + ConversationFragment.openPendingMessage(this); break; case REQUEST_PLAY_PAUSE: - ConversationFragment.startStopPending(getSupportFragmentManager()); + ConversationFragment.startStopPending(this); break; } } @@ -318,7 +318,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void handleNegativeActivityResult(int requestCode) { - Conversation conversation = ConversationFragment.getConversationReliable(getSupportFragmentManager()); + Conversation conversation = ConversationFragment.getConversationReliable(this); switch (requestCode) { case REQUEST_DECRYPT_PGP: if (conversation == null) { @@ -333,7 +333,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void handlePositiveActivityResult(int requestCode, final Intent data) { - Conversation conversation = ConversationFragment.getConversationReliable(getSupportFragmentManager()); + Conversation conversation = ConversationFragment.getConversationReliable(this); if (conversation == null) { Log.d(Config.LOGTAG, "conversation not found"); return; @@ -365,8 +365,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio this.binding = DataBindingUtil.setContentView(this, R.layout.activity_conversations); setSupportActionBar(binding.toolbar); configureActionBar(getSupportActionBar()); - this.getSupportFragmentManager().addOnBackStackChangedListener(this::invalidateActionBarTitle); - this.getSupportFragmentManager().addOnBackStackChangedListener(this::showDialogsIfMainIsOverview); + this.getFragmentManager().addOnBackStackChangedListener(this::invalidateActionBarTitle); + this.getFragmentManager().addOnBackStackChangedListener(this::showDialogsIfMainIsOverview); this.initializeFragments(); this.invalidateActionBarTitle(); final Intent intent; @@ -387,7 +387,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio final MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code); if (qrCodeScanMenuItem != null) { if (isCameraFeatureAvailable()) { - Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_fragment); + Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment); boolean visible = getResources().getBoolean(R.bool.show_qr_code_scan) && fragment instanceof ConversationsOverviewFragment; qrCodeScanMenuItem.setVisible(visible); @@ -401,7 +401,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio @Override public void onConversationSelected(Conversation conversation) { clearPendingViewIntent(); - if (ConversationFragment.getConversation(getSupportFragmentManager()) == conversation) { + if (ConversationFragment.getConversation(this) == conversation) { Log.d(Config.LOGTAG, "ignore onConversationSelected() because conversation is already open"); return; } @@ -429,7 +429,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void openConversation(Conversation conversation, Bundle extras) { - final FragmentManager fragmentManager = getSupportFragmentManager(); + final FragmentManager fragmentManager = getFragmentManager(); executePendingTransactions(fragmentManager); ConversationFragment conversationFragment = (ConversationFragment) fragmentManager.findFragmentById(R.id.secondary_fragment); final boolean mainNeedsRefresh; @@ -489,7 +489,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } switch (item.getItemId()) { case android.R.id.home: - FragmentManager fm = getSupportFragmentManager(); + FragmentManager fm = getFragmentManager(); if (fm.getBackStackEntryCount() > 0) { try { fm.popBackStack(); @@ -506,7 +506,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio startActivity(new Intent(this, SearchActivity.class)); return true; case R.id.action_search_this_conversation: - final Conversation conversation = ConversationFragment.getConversation(getSupportFragmentManager()); + final Conversation conversation = ConversationFragment.getConversation(this); if (conversation == null) { return true; } @@ -521,7 +521,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio @Override public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { if (keyCode == KeyEvent.KEYCODE_DPAD_UP && keyEvent.isCtrlPressed()) { - final ConversationFragment conversationFragment = ConversationFragment.get(getSupportFragmentManager()); + final ConversationFragment conversationFragment = ConversationFragment.get(this); if (conversationFragment != null && conversationFragment.onArrowUpCtrlPressed()) { return true; } @@ -576,14 +576,14 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void initializeFragments() { - final FragmentManager fragmentManager = getSupportFragmentManager(); + final FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction transaction = fragmentManager.beginTransaction(); final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment); if (mainFragment != null) { if (binding.secondaryFragment != null) { if (mainFragment instanceof ConversationFragment) { - getSupportFragmentManager().popBackStack(); + getFragmentManager().popBackStack(); transaction.remove(mainFragment); transaction.commit(); fragmentManager.executePendingTransactions(); @@ -597,7 +597,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (secondaryFragment instanceof ConversationFragment) { transaction.remove(secondaryFragment); transaction.commit(); - getSupportFragmentManager().executePendingTransactions(); + getFragmentManager().executePendingTransactions(); transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.main_fragment, secondaryFragment); transaction.addToBackStack(null); @@ -619,7 +619,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (actionBar == null) { return; } - final FragmentManager fragmentManager = getSupportFragmentManager(); + final FragmentManager fragmentManager = getFragmentManager(); final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); if (mainFragment instanceof ConversationFragment) { final Conversation conversation = ((ConversationFragment) mainFragment).getConversation(); @@ -656,7 +656,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (performRedirectIfNecessary(conversation, false)) { return; } - final FragmentManager fragmentManager = getSupportFragmentManager(); + final FragmentManager fragmentManager = getFragmentManager(); final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); if (mainFragment instanceof ConversationFragment) { try { @@ -670,7 +670,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment); if (secondaryFragment instanceof ConversationFragment) { if (((ConversationFragment) secondaryFragment).getConversation() == conversation) { - Conversation suggestion = ConversationsOverviewFragment.getSuggestion(getSupportFragmentManager(), conversation); + Conversation suggestion = ConversationsOverviewFragment.getSuggestion(this, conversation); if (suggestion != null) { openConversation(suggestion, null); } @@ -680,7 +680,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio @Override public void onConversationsListItemUpdated() { - Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_fragment); + Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment); if (fragment instanceof ConversationsOverviewFragment) { ((ConversationsOverviewFragment) fragment).refresh(); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java index ff33c61557eddfdbec95e1881d108b46c3078392..eebd94df500256ce883a91ead1b0b66aae7a7ada 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java @@ -31,6 +31,7 @@ package eu.siacs.conversations.ui; import android.app.Activity; import android.app.AlertDialog; +import android.app.Fragment; import android.content.Intent; import android.graphics.Canvas; import android.graphics.Paint; @@ -45,8 +46,6 @@ import android.view.ViewGroup; import android.widget.Toast; import androidx.databinding.DataBindingUtil; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -140,7 +139,7 @@ public class ConversationsOverviewFragment extends XmppFragment { activity.xmppConnectionService.archiveConversation(c); return; } - final boolean formerlySelected = ConversationFragment.getConversation(requireActivity().getSupportFragmentManager()) == swipedConversation.peek(); + final boolean formerlySelected = ConversationFragment.getConversation(getActivity()) == swipedConversation.peek(); if (activity instanceof OnConversationArchived) { ((OnConversationArchived) activity).onConversationArchived(swipedConversation.peek()); } @@ -203,19 +202,19 @@ public class ConversationsOverviewFragment extends XmppFragment { private ItemTouchHelper touchHelper; - public static Conversation getSuggestion(FragmentManager fragmentManager) { + public static Conversation getSuggestion(Activity activity) { final Conversation exception; - Fragment fragment = fragmentManager.findFragmentById(R.id.main_fragment); + Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); if (fragment instanceof ConversationsOverviewFragment) { exception = ((ConversationsOverviewFragment) fragment).swipedConversation.peek(); } else { exception = null; } - return getSuggestion(fragmentManager, exception); + return getSuggestion(activity, exception); } - public static Conversation getSuggestion(FragmentManager fragmentManager, Conversation exception) { - Fragment fragment = fragmentManager.findFragmentById(R.id.main_fragment); + public static Conversation getSuggestion(Activity activity, Conversation exception) { + Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); if (fragment instanceof ConversationsOverviewFragment) { List conversations = ((ConversationsOverviewFragment) fragment).conversations; if (conversations.size() > 0) { diff --git a/src/main/java/eu/siacs/conversations/ui/XmppFragment.java b/src/main/java/eu/siacs/conversations/ui/XmppFragment.java index a16d0ae7ffa56381e1d008d1783900fa1243b73a..30524d2f5441f3e0405ec38d1807e9a7d3294080 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppFragment.java @@ -30,8 +30,7 @@ package eu.siacs.conversations.ui; import android.app.Activity; - -import androidx.fragment.app.Fragment; +import android.app.Fragment; import eu.siacs.conversations.ui.interfaces.OnBackendConnected; diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index 496c087bd263aed49e6aad845480ab15c8e86020..049703597d2409235059ba15ce1625d47648b74d 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -60,7 +60,7 @@ public class ConversationAdapter extends RecyclerView.Adapter { viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.VISIBLE); viewHolder.download_button.setText(text); - viewHolder.download_button.setOnClickListener(v -> ConversationFragment.downloadFile(activity.getSupportFragmentManager(), message)); + viewHolder.download_button.setOnClickListener(v -> ConversationFragment.downloadFile(activity, message)); } private void displayOpenableMessage(ViewHolder viewHolder, final Message message, final boolean darkBackground) { @@ -872,7 +872,7 @@ public class MessageAdapter extends ArrayAdapter { public void openDownloadable(Message message) { if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - ConversationFragment.registerPendingMessage(activity.getSupportFragmentManager(), message); + ConversationFragment.registerPendingMessage(activity, message); ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ConversationsActivity.REQUEST_OPEN_MESSAGE); return; } 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 6f928723676c1fb092467ca03d9ce5ba9c9e1b6e..da1ac7a4448043ec4910b9cf720dfe4f6bf8fdc7 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/MucDetailsContextMenuHelper.java @@ -172,7 +172,7 @@ public final class MucDetailsContextMenuHelper { return true; case R.id.send_private_message: if (activity instanceof ConversationsActivity) { - ConversationFragment conversationFragment = ConversationFragment.get(activity.getSupportFragmentManager()); + ConversationFragment conversationFragment = ConversationFragment.get(activity); if (conversationFragment != null) { conversationFragment.privateMessageWith(user.getFullJid()); return true; From bd4d939a29446f963d916f2626cc11de9eff6b3b Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 Sep 2021 11:55:37 +0200 Subject: [PATCH 067/145] backport requireActivity method --- .../eu/siacs/conversations/ui/ConversationFragment.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 5bc24fee371008584f465c1afda8b8fb9e210eda..a019f282ab7d2fedfdd05cb3033a75a714d028e3 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -3051,4 +3051,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } activity.switchToAccount(message.getConversation().getAccount(), fingerprint); } + + private Activity requireActivity() { + final Activity activity = getActivity(); + if (activity == null) { + throw new IllegalStateException("Activity not attached"); + } + return activity; + } } From 90a0d36362a3ec758e33f93de8e6b624b6bcb6c5 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 24 Sep 2021 09:15:21 +0200 Subject: [PATCH 068/145] fix not recognizing message as download. fixes #4178 --- .../java/eu/siacs/conversations/entities/Conversation.java | 5 ++++- .../eu/siacs/conversations/http/HttpDownloadConnection.java | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 20db15da23f2137b6fed0a75ea4ae7b688c84ab8..5ddd338b375d03ed5bf8516d62118f30605a269f 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -28,6 +28,7 @@ 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.MessageUtils; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.chatstate.ChatState; @@ -258,9 +259,11 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl public Message findMessageWithFileAndUuid(final String uuid) { synchronized (this.messages) { for (final Message message : this.messages) { + final Transferable transferable = message.getTransferable(); + final boolean unInitiatedButKnownSize = MessageUtils.unInitiatedButKnownSize(message); if (message.getUuid().equals(uuid) && message.getEncryption() != Message.ENCRYPTION_PGP - && (message.isFileOrImage() || message.treatAsDownloadable())) { + && (message.isFileOrImage() || message.treatAsDownloadable() || unInitiatedButKnownSize || (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING))) { return message; } } diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 349886115fb87513da3885df53ae0dc84d71aade..15dc6eac692a96dc51e5e618764accb3e0fb6b75 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -131,7 +131,6 @@ public class HttpDownloadConnection implements Transferable { } private void download(final boolean interactive) { - Log.d(Config.LOGTAG,"download()",new Exception()); EXECUTOR.execute(new FileDownloader(interactive)); } From b9ceb6710497372f020563fc3bec072cc6ce1f08 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 24 Sep 2021 09:25:27 +0200 Subject: [PATCH 069/145] version bump to 2.10.1 + changelog --- CHANGELOG.md | 5 +++++ build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/42018.txt | 3 +++ fastlane/metadata/android/en-US/changelogs/42019.txt | 2 ++ 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/42018.txt create mode 100644 fastlane/metadata/android/en-US/changelogs/42019.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 48a5feff5391bd9d9a44a3de7a71a7ffaafe4e16..b9227af42550791448810f3bb57b18218ada4aab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Version 2.10.1 + +* Fix issue with some videos not being compressed +* Fix rare crash when opening notification + ### Version 2.10.0 * Show black bars when remote video does not match aspect ratio of screen diff --git a/build.gradle b/build.gradle index 6dc3fdbcb92ffc5b8fae647ff4c52ca28e17b745..5ee25ed01784bb742e7c0945a42b53218cbff7e8 100644 --- a/build.gradle +++ b/build.gradle @@ -94,8 +94,8 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 42018 - versionName "2.10.0" + versionCode 42019 + versionName "2.10.1" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/42018.txt b/fastlane/metadata/android/en-US/changelogs/42018.txt new file mode 100644 index 0000000000000000000000000000000000000000..8f4d66caab71deced9f02d1258690ef06aecc2ca --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/42018.txt @@ -0,0 +1,3 @@ +* Show black bars when remote video does not match aspect ratio of screen +* Improve search performance +* Add setting to prevent screenshots diff --git a/fastlane/metadata/android/en-US/changelogs/42019.txt b/fastlane/metadata/android/en-US/changelogs/42019.txt new file mode 100644 index 0000000000000000000000000000000000000000..eaaa190faabe6799d5fc22f80d7b6a5ccbd87805 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/42019.txt @@ -0,0 +1,2 @@ +* Fix issue with some videos not being compressed +* Fix rare crash when opening notification From 3de8147b4159f40d4cd000bb1f4102cef976e831 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 27 Sep 2021 10:48:04 +0200 Subject: [PATCH 070/145] pulled translations from transifex --- src/conversations/res/values-ja/strings.xml | 4 +- src/main/res/values-gl/strings.xml | 4 +- src/main/res/values-ja/strings.xml | 101 ++++++++++---------- src/main/res/values-sk/strings.xml | 95 +++++++++++++++++- src/quicksy/res/values-sk/strings.xml | 12 +++ 5 files changed, 162 insertions(+), 54 deletions(-) create mode 100644 src/quicksy/res/values-sk/strings.xml diff --git a/src/conversations/res/values-ja/strings.xml b/src/conversations/res/values-ja/strings.xml index 0ab18b6dee9118748b48424dc162c5f4e783c70c..a36b4a119f154f07884c8dc1939214891f146484 100644 --- a/src/conversations/res/values-ja/strings.xml +++ b/src/conversations/res/values-ja/strings.xml @@ -2,8 +2,8 @@ XMPP プロバイダーを選択してください conversations.im を利用する - 新しいアカウントを作成 - XMPP アカウントをお持ちですか?既にほかの XMPP クライアントを利用しているか、 Conversations を利用したことがある場合はこちら。初めての方は、今すぐ新しい XMPP アカウントを作成できます。\nヒント: e メールのプロバイダーが XMPP アカウントも提供している場合があります。 + 新規アカウントを作成 + XMPP アカウントをお持ちですか?既にほかの XMPP クライアントを利用しているか、 Conversations を利用したことがある場合はこちら。初めての方は、今すぐ新規 XMPP アカウントを作成できます。\nヒント: e メールのプロバイダーが XMPP アカウントも提供している場合があります。 XMPP は、プロバイダーに依存しないインスタントメッセージのプロトコルです。 XMPP サーバーならどこでも、このクライアントを使用することができます。\nよろしければ、 Conversations に最適化されたプロバイダー conversations.im¹ で簡単にアカウントを作成することもできます。 %1$s へ招待されました。アカウント作成手順をご案内します。 \n%1$s をプロバイダーに選択してほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。 %1$s へ招待されました。ユーザー名は既に選択されています。アカウント作成手順をご案内します。 \nほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。 diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index a47a27885af476a6e92f0e4eebddcb38bb23a263..b272a1b4a02c8b1ba12190bf42b9ee84f61c07f9 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -58,7 +58,7 @@ Cambiar o contrasinal no servidor Compartir con Comezar conversa - Invitar contacto + Convidar contacto Convidar Contactos Contacto @@ -559,7 +559,7 @@ Fallo no rexistro: contrasinal moi feble Escoller participantes Creando unha conversa en grupo... - Invitar de novo + Convidar de novo Desactivar Breve Medio diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index a6d2842ce75ff507148690945fc43fceecc343e7..9037dd4711375ad7a81a655333ff5d3331815c1e 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -30,7 +30,7 @@ 1分前 %d分前 - 未読%d件 + %d件の未読の会話 送信中… @@ -47,11 +47,11 @@ %s からあなたに送信されるメッセージをブロックしますか? %s のブロックを解除し、あなたにメッセージを送信できるようにしますか? %s からの連絡をすべてブロックしますか? - %s からの連絡をすべてブロック解除しますか? + %s からすべての連絡先のブロックを解除しますか? 連絡先をブロックしました ブロックしました %s のブックマークを削除しますか? このブックマークとの会話は削除されません。 - サーバーに新しいアカウントを登録 + サーバーに新規アカウントを登録 サーバーのパスワードを変更 …で共有 会話を始める @@ -59,7 +59,7 @@ 招待 連絡先 連絡先 - キャンセル + 中止 設定 追加 編集 @@ -105,9 +105,9 @@ OpenKeychain をインストールしてください 依頼中… 待機中… - OpenPGP の鍵はありません + OpenPGP 鍵が見つかりません 連絡先が公開鍵を通知しないため、あなたのメッセージを暗号化することができません。\n\n連絡先に OpenPGP をセットアップするように依頼してください。 - OpenPGP の鍵はありません + OpenPGP 鍵が見つかりません 連絡先が公開鍵を通知しないため、あなたのメッセージを暗号化することができません。\n\n連絡先に OpenPGP をセットアップするように依頼してください。 全般 ファイルを受取 @@ -115,9 +115,9 @@ 添付ファイル 通知 振動 - 新しいメッセージが届いたときに振動します + 新着メッセージが届いたときに振動します LED 通知 - 新しいメッセージが届いたときに通知ライトを点滅します + 新着メッセージが届いたときに通知ライトを点滅します 着信音 通知音 新着メッセージの通知音 @@ -128,7 +128,7 @@ クラッシュレポートを送信しない スタックトレースを送信すると、 Conversations の開発を支援します メッセージを確認 - あなたがメッセージを受信して読んだときに、連絡先に知らせます + あなたがメッセージを受信して読んだときに、連絡先に知らせる スクリーンショットを防ぐ アプリスイッチャーでアプリの内容を隠し、スクリーンショットを防ぐ UI @@ -138,9 +138,9 @@ エラーが発生しました エラー あなたのアカウント - 参加アップデートを送信 - 参加アップデートを受信 - 参加アップデートを問合せ + 出席情報アップデートを送信 + 出席情報アップデートを受信 + 出席情報アップデートを求める 画像を選択 写真を撮影 サブスクリプション要求を事前に付与する @@ -162,7 +162,7 @@ ユーザー名は既に使用されています 登録が完了しました サーバーは登録をサポートしていません - トークンが無効です + 登録トークンが無効です TLS ネゴシエーションに失敗しました 検証不可能なドメイン ポリシー違反 @@ -174,13 +174,13 @@ OpenPGP OMEMO アカウントを削除 - 一時的に無効にする + 一時的に無効化 アバターを公開 OpenPGP 公開鍵を公開 OpenPGP 公開鍵を削除 - 存在告知から OpenPGP 公開鍵を削除してもよろしいですか?\n連絡先はあなたに OpenPGP 暗号化メッセージを送信できなくなります。 + 出席情報告知から OpenPGP 公開鍵を削除してもよろしいですか?\n連絡先はあなたに OpenPGP 暗号化メッセージを送信できなくなります。 OpenPGP 公開鍵を公開しました。 - アカウントを有効にする + アカウントを有効化 よろしいですか? アカウントを削除すると会話履歴がすべて消去されます 音声を録音 @@ -213,7 +213,7 @@ 1日前に会いました %d日前に会いました 暗号化されたメッセージです。復号するには OpenKeychain をインストールしてください。 - 新しい OpenPGP で暗号化されたメッセージが見つかりました + 新規の OpenPGP で暗号化されたメッセージが見つかりました OpenPGP 鍵 ID OMEMO フィンガープリント v\\OMEMO フィンガープリント @@ -251,7 +251,7 @@ 戻りを追加 %s はここまで読みました %s はここまで読みました - %1$s +%2$d 全員ここまで読みました + %1$s +%2$d人がここまで読みました 全員がここまで読みました 公開 アバターをタップしてギャラリーから画像を選択します @@ -269,14 +269,14 @@ 次へ セッションが確立 スキップ - 通知を無効にする + 通知を無効化 有効 グループチャットにはパスワードが必要 パスワードを入力してください - 最初に連絡先から参加アップデートを要求してください。\n\nこれは、連絡先が何のクライアントを使用しているかを決めるために使用されます。 + 最初に、連絡先から出席情報アップデートを要求してください。\n\nこれは、連絡先が何のクライアントを使用しているかを特定するために使用されます。 今すぐ要求 無視 - 警告: 相互の参加アップデートなしにこれを送信すると、予期しない問題が発生する可能性があります。\n\nあなたの参加サブスクリプションを検証するために、“連絡先”の詳細に移動します。 + 警告: 相互の出席情報アップデートなしにこれを送信すると、予期しない問題が発生する可能性があります。\n\nあなたの出席情報サブスクリプションを検証するために、“連絡先の詳細”に移動します。 セキュリティ メッセージの修正を許可 連絡先が、遡及的に自分のメッセージを編集することを許可します @@ -286,7 +286,7 @@ 消音時間 開始時刻 終了時刻 - 消音時間を有効にする + 消音時間を有効化 消音時間の間、通知は無音になります その他 ブックマークと同期 @@ -319,7 +319,7 @@ ブロック一覧を表示 アカウントの詳細 確認 - 後でもう一度お試しください + 再試行 フォアグラウンドサービス オペレーティングシステムが接続を切断するのを防止します バックアップを作成 @@ -329,7 +329,7 @@ バックアップファイルは %s に保存されました バックアップを復元 バックアップを復元しました - アカウントを有効にしてください。 + アカウントを有効化してください。 ファイルを選択 %1$s 受信中 (%2$d%% 完了) %s をダウンロード @@ -339,16 +339,16 @@ 送信中 (%1$d%% 完了) 転送用ファイルの準備中 %s ダウンロード依頼中 - 転送をキャンセル + 転送を中止 ファイル転送に失敗しました - 転送をキャンセルしました + ファイル転送を中止しました ファイルを削除しました ファイルを開くアプリケーションが見つかりません リンクを開くアプリケーションが見つかりません 連絡先を表示するアプリケーションが見つかりません ダイナミック タグ 連絡先の下に、読み取り専用タグを表示します - 通知を有効にする + 通知を有効化 グループチャットのサーバーが見つかりませんでした グループチャットを作成できません アカウントのアバター @@ -357,7 +357,7 @@ デバイスを消去 OMEMO の告知から他のすべてのデバイスを消去してもよろしいですか? お使いのデバイスが次回接続したとき、それらのデバイスは自分自身を再告知しますが、その間に送信されたメッセージを受信できない場合があります。 この連絡先で使用可能な鍵がありません。\nサーバーから新しい鍵を取得できませんでした。連絡先のサーバーに問題がある可能性があります。 - この連絡先で利用可能な鍵はありません。\n双方に存在サブスクリプションがあることを確認してください。 + この連絡先で利用可能な鍵はありません。\n双方に出席情報サブスクリプションがあることを確認してください。 何か問題が発生しました サーバーから履歴を取得中 サーバーにこれ以上履歴がありません @@ -368,8 +368,8 @@ 現在のパスワード 新しいパスワード パスワードは空にできません - すべてのアカウントを有効にする - すべてのアカウントを無効にする + すべてのアカウントを有効化 + すべてのアカウントを無効化 アクションを実行... 所属なし オフライン @@ -396,8 +396,8 @@ XMPPアドレスを誰でも見れるようにする 談話室の調停をする あなたは参加していません - グループチャットのオプションが変更されました! - グループチャットのオプションを変更できませんでした + グループチャットの設定が変更されました! + グループチャットの設定を変更できませんでした なし 通知が来るまで スヌーズ @@ -424,7 +424,7 @@ %s さんが入力中… %s さんが入力を止めました 入力中通知 - あなたがメッセージを書いているときに、連絡先に知らせます + あなたがメッセージを書いているときに、連絡先に知らせる 位置を送信 位置を表示 位置を表示するアプリケーションが見つかりません @@ -518,8 +518,8 @@ 常に 大きい画像のみ 電池最適化が有効 - お使いのデバイスは、%1$s で通知の遅延やメッセージの損失につながる可能性のある、重い電池の最適化を使用しています。\nそれを無効にすることをお勧めします。 - お使いのデバイスは、%1$s で通知の遅延やメッセージの損失につながる可能性のある、重い電池の最適化を使用しています。\n\n今、それらを無効にするように求められます。 + お使いのデバイスは、%1$s で通知の遅延やメッセージの損失につながる可能性のある、重い電池の最適化を使用しています。\nそれを無効化することをお勧めします。 + お使いのデバイスは、%1$s で通知の遅延やメッセージの損失につながる可能性のある、重い電池の最適化を使用しています。\n\n今、それらを無効化するように求められます。 無効 選択した範囲が大きすぎます (アクティベートしたアカウントはありません) @@ -527,7 +527,7 @@ メッセージを修正 修正したメッセージを送信 あなたは信頼を確認するために、この人の指紋を安全に検証しました。“完了”を選択すると、 %s がこのグループチャットの一員であることを確認したことになります。 - このアカウントを無効にしました + このアカウントを無効化しました セキュリティエラー: 不正なファイルアクセス! URI を共有するアプリが見つかりません …で URI を共有 @@ -577,11 +577,11 @@ 支払が必要です インターネット使用権限の付与 自分 - 連絡先が、参加サブスクリプションを問い合わせしています + 連絡先が出席情報サブスクリプションを求めています 許可 %s にアクセスする権限がありません リモートサーバーが見つかりません - リモートサーバーのタイムアウト + リモートサーバーがタイムアウト アカウントを更新できません この XMPP アドレスをスパムとして報告する。 OMEMO ID を削除 @@ -590,8 +590,8 @@ アバターを公開するには接続する必要があります。 エラーメッセージを表示 エラーメッセージ - データセーバーを有効にしました - お使いのオペレーティングシステムは、%1$s がバックグラウンドのときにインターネットにアクセスすることを制限しています。新しいメッセージの通知を受信するには、“データセーバー”がオンならば、%1$s に無制限のアクセスを許可する必要があります。\n%1$s は可能なときにデータを保存するための努力をします。 + データセーバーを有効化しました + お使いのオペレーティングシステムは、%1$s がバックグラウンドのときにインターネットにアクセスすることを制限しています。新着メッセージの通知を受信するには、“データセーバー”がオンならば、%1$s に無制限のアクセスを許可する必要があります。\n%1$s は可能なときにデータを保存するための努力をします。 お使いのデバイスは、%1$s のデータセーバーを無効にできません。 一時ファイルを作成できません このデバイスは検証済です @@ -647,7 +647,7 @@ 連絡先をブロックしました 見知らぬ人からの通知 見知らぬ人から受信したメッセージと通話を通知します。 - 見知らぬ人からメッセージを受け取りました + 見知らぬ人からメッセージを受信しました 見知らぬ人をブロック ドメイン全体をブロック 今すぐオンライン @@ -669,12 +669,12 @@ クリップボードにコピー メッセージをクリップボードにコピーしました メッセージ - 非公開メッセージを無効にしました + 非公開メッセージを無効化しました 保護されたアプリ 画面がオフになっているときでも通知を受信し続けるには、保護されたアプリの一覧に Conversations を追加する必要があります。 未知の証明書を受け入れますか? サーバー証明書が既知の認証局によって署名されていません。 - サーバー名の不一致を受け入れますか? + 不一致のサーバー名を受け入れますか? サーバーは\"%s\"として認証できませんでした。証明書は次の場合にのみ有効です: それでも接続を希望しますか? 証明書の詳細: @@ -689,7 +689,7 @@ デバイスの一覧を取得できません 暗号化の鍵を取得できません ヒント: お互いが連絡先名簿に加えれば解決するでしょう。 - この会話で OMEMO の暗号化を無効にしてもよろしいですか?\nこれにより、サーバー管理者がメッセージを読むことが可能になりますが、時代遅れのクライアントを使っている人と連絡をとるには、この方法しかないかもしれません。 + この会話で OMEMO の暗号化を無効化してもよろしいですか?\nこれにより、サーバー管理者がメッセージを読むことが可能になりますが、時代遅れのクライアントを使っている人と連絡をとるには、この方法しかないかもしれません。 今すぐ無効化 下書き: OMEMO 暗号化 @@ -763,6 +763,7 @@ 質が低い程、ファイルは小さくなります 中 (360p) 高 (720p) + 中止しました あなたは既にメッセージを作成中です。 実装されてない機能 不正な国コード @@ -784,7 +785,7 @@ 戻る クリップボードから可能な pin を自動的に貼り付ける。 6桁の pin を入力してください。 - 本当に登録手続きを中止してもよろしいのですか? + 登録手続きを中止してもよろしいのですか? はい いいえ 検証しています… @@ -816,7 +817,7 @@ マーケットアプリがインストールされていません。 この談話室では、あなたの XMPP アドレスを公開します 電子書籍 - 原物 (非圧縮) + そのまま (非圧縮) …で開く Conversations プロフィール画像 アカウントを選択 @@ -862,7 +863,7 @@ search.jabber.networkを利用します。

この機能を使うと、あなたののIPアドレスや検索キーワードがそのサービスに送信されます。詳しくは、プライバシーポリシーをご覧ください。]]>
既にアカウントを持っています 既存アカウントを追加 - 新しいアカウントを登録 + 新規アカウントを登録 これはドメインアドレスのようです とにかく追加 これは談話室アドレスのようです @@ -881,7 +882,8 @@ ローカルサーバー ほとんどのユーザーは、公開されている XMPP エコシステム全体からより良い提案を得るために、‘jabber.network’を選択するはずです。 談話室の発見方法 - アカウントを有効にしてください + バックアップ + アカウントを有効化してください 通話をする 着信通話 着信映像通話 @@ -897,11 +899,12 @@ 通話に接続できません 接続切断 撤回された通話 + アプリの失敗 検証に問題 電話を切る 継続中の通話 継続中の映像通話 - 通話するのに Tor を無効にする + 通話するのに Tor を無効化 着信通話 着信通話・%s 不在着信通話・%s diff --git a/src/main/res/values-sk/strings.xml b/src/main/res/values-sk/strings.xml index 792420be4e166e0c5900c599401c4ea1813eca63..25e10cd611ca10f39f246df3bcfd7cd4224b7fd5 100644 --- a/src/main/res/values-sk/strings.xml +++ b/src/main/res/values-sk/strings.xml @@ -131,11 +131,14 @@ Zvuk oznámenia nových správ Zvonenie pre prichádzajúce hovory Ochranná doba + Doba, počas ktorej budú oznámenia stíšené po detekcii aktivity na jednom z vašich ostatných zariadení. Pokročilé Neodosielať detaily o zlyhaní aplikácie Keď pošlete detaily o dôvode zlyhania, pomáhate vývoju Potvrdzovať správy Dajte vedieť svojim kontaktom, keď prijmete a prečítate si správy + Zakázať snímok obrazovky + Skryje obsah aplikácie v posledných aplikáciách a zablokuje snímky obrazovky. Prostredie Nesprávny kľúč na šifrovanie. Prijať @@ -152,6 +155,7 @@ Nemohol som konvertovať obrázkový súbor Súbor sa nenašiel Všeobecná I/O chyba. Možno už nie je voľné miesto? + Aplikácia, ktorú ste použili pre výber obrázka neposkytla dostatočné oprávnenia na prečítanie súboru.\n\nSkúste použiť iného správcu súborov pre výber obrázka Aplikácia, ktorú ste použili na zdieľanie tohto súboru neposkytla dostatočné povolenia. Neznámy Dočasne vypnutý @@ -166,6 +170,7 @@ Registrácia ukončená Registrácia nie je podporovaná serverom. Neplatný registračný token + Nadviazanie spojenia TLS zlyhalo Doména sa nedá overiť Porušenie pravidiel Nekompatibilný server @@ -177,6 +182,7 @@ Dočasne vypnúť Zverejniť avatar Zverejniť OpenPGP kľúč + Odstrániť OpenPGP  verejný kľúč Povoliť účet Ste si istý? Nahrať hlas @@ -219,6 +225,7 @@ Dešifrovať Záložky Hľadať + Vložiť Kontakt Zmazať kontakt Zobraziť detaily kontaktu Zablokovať kontakt @@ -233,6 +240,7 @@ Vymazať záložku Vymazať skupinový rozhovor Vymazať kanál + Ste si istý, že chcete zrušiť tento skupinový rozhovor?\n\nUpozornenie: Skupinový rozhovor bude kompletne vymazaný zo servera. Nemohol som vymazať skupinový rozhovor Nemohol som vymazať kanál Upraviť predmet skupinového rozhovoru @@ -259,9 +267,11 @@ Pripojiť Tento účet už existuje Ďalší + Spojenie naviazané Preskočiť Vypnúť upozornenia Povoliť + Skupinový rozhovor požaduje heslo Vložiť heslo Ihneď vyžiadať Ignorovať @@ -282,6 +292,7 @@ OMEMO odtlačok skopírovaný do schránky Ste zakázaný na tomto skupinovom rozhovore Skupinový rozhovor len pre členov + Boli ste vyhodení z tohto skupinového rozhovoru Skupinový rozhovor bol zastavený Už viac nie ste v tomto skupinovom rozhovore Používa sa účet %s @@ -306,18 +317,34 @@ Detaily účtu Potvrdiť Skúste znova + Služba v popredí Zamedzí operačnému systému ukončiť pripojenie Vytvoriť zálohu + Súbory zálohy budú uložené v %s + Vytváram súbor zálohy + Vaša záloha bola vytvorená + Súbor zálohy bol uložený v %s + Obnovujem zálohu Vaša záloha bola obnovená + Nezabudnite si zapnúť konto. Vybrať súbor Prijímam %1$s (%2$d%% ukončený) Stiahnuť %s + Zmazať %s súbor Otvoriť %s posielam (%1$d%% ukončený) + Pripravuje sa zdieľanie súboru %s ponúknutý na stiahnutie Zrušiť prenos - Zobraziť etikety na čítanie pod kontakty + Nedá sa zdieľať súbor + prenos súboru zrušený + Súbor zmazaný + Nebola nájdená aplikácia na otvorenie súboru + Nebola nájdená aplikácia na otvorenie odkazu + Nebola nájdená aplikácia na prezretie kontaktu + Dynamické štítky + Zobraziť štítky pod kontaktmi Povoliť upozornenia Avatar účtu Skopírovať OMEMO identifikátor do schránky @@ -361,8 +388,12 @@ Posielam %s Ponúkam %s Skryť neprihlásených + %s píše... %s prestal písať + %s píšu... + %s prestali písať Upozornenia pri písaní + Dajte svojim kontaktom vedieť že im práve píšete správu. Poslať polohu Zobraziť polohu Poloha @@ -389,37 +420,95 @@ Užívateľské meno Užívateľské meno Toto nie je platné užívateľské meno + Prihlásiť sa s certifikátom Obnoviť certifikát Chyba pri načítaní OMEMO kľúča! kľúč OMEMO overený certifikátom! Pripojiť cez Tor %1$dz%2$dúčtov pripojených + + %dspráva + %dsprávy + %d správ + %dspráv + + Načítať viac správ + Súbor zdieľaný s %s + Obrázok zdieľaný s %s + Obrázky zdieľané s %s + Text zdieľaný s %s + Oznamovať na všetkých správach + Vždy + Deaktivovať (Žiadne aktivované účty) + Nebola nájdená žiadna aplikácia na zdieľanie URI + Zdieľať URI s... + Nastaviť vašu dostupnosť pri úprave vašej status správy. Online + Zaneprázdnený + Deaktivovať + Oznamovať používanie + Súkromie + Téma + Zvoľte si farebnú schému + Zelené pozadie + Použiť zelené pozadie pre prijaté správy Vymazať OMEMO identifikátory Re-generuje vaše kľúče OMEMO. Všetky vaše kontakty vás budú musieť znova overiť. Použite to ako poslednú možnosť. + Odstrániť označené kľúče Overili ste všetky kľúče OMEMO vo vašom vlastníctve. + Zdieľať ako qr kód + Zdieľať ako XMPP URI + Zdieľať ako HTTP odkaz Overiť kľúče OMEMO + Automaticky vymazávať správy z tohto zariadenia, ktoré sú staršie ako nastavené časové obdobie. online práve teraz + Nahrať video + Kopírovať do schránky Správa skopírovaná do schránky + Upraviť status správu + Upraviť status správu OMEMO šifrovanie OMEMO bude vždy používané pre individuálne a súkromné skupinové rozhovory. OMEMO bude predvolene zapnuté pre všetky rozhovory. Veľkosť písma + Predvolene zapnuté + Predvolene vypnuté Nepodarilo sa dešifrovať OMEMO správu. + Zdieľať Polohu + Zdieľať polohu Zobraziť polohu + Zdieľať + Prehľadávať správy + Plugin na Zdieľanie Polohy + Používať Plugin na Zdieľanie Polohy namiesto vstavanej mapy. + Služba v popredí + Správy Hovory + Správy Prichádzajúce hovory Prebiehajúce hovory + Zlyhané doručenia Nastavenia oznámení prichádzajúcich hovorov + Prosím vložte vaše meno, aby ľudia, ktorí vás nemajú v adresári, vedeli kto ste. + Vaše meno + Vložte vaše meno + Pre nastavenie vášho mena, použite tlačidlo Upraviť. Obnoviť zálohu + XMPP adresa Priložiť + Zdieľať súbory záloh + Zálohy + O aplikácii Prichádzajúci hovor Prichádzajúci video hovor Prijímam hovor Ukončujem hovor + Prijať + Odmietnuť Vyhľadávanie zariadení Zvoní + Zaneprázdnený Nedá sa pripojiť hovor Prebiehajúci hovor Prebiehajúci video hovor @@ -433,5 +522,9 @@ Video hovor Naraz môžete mať iba jeden hovor. Vrátiť sa do prebiehajúceho hovoru + Pripnúť na vrch + Odopnúť z vrchu Zašifrované s OMEMO + Zlyhané doručenia + Viac možnosťí
diff --git a/src/quicksy/res/values-sk/strings.xml b/src/quicksy/res/values-sk/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..df1b5a1e059e7bd7b309f933b7b22f523753a321 --- /dev/null +++ b/src/quicksy/res/values-sk/strings.xml @@ -0,0 +1,12 @@ + + + Doba, počas ktorej bude Quicksy stíšený po detekcii aktivity na inom zariadení. + Zaslaním detailov o dôvode zlyhania pomáhate ďalšiemu vývoju aplikácie Quicksy + Dajte svojim kontaktom vedieť, keď používate Quicksy + Aby ste dostávali oznámenia aj pri vypnutej obrazovke, pridajte Quicksy medzi chránené aplikácie. + Quicksy profilový obrázok + Quicksy nie je dostupné vo vašej krajine. + Nemôžem overiť identitu servera. + Neznáma bezpečnostná chyba. + Vypršal časový limit pri pripájaní k serveru. + From e791e1926579cea262396d1786b45675aced8c9f Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 27 Sep 2021 11:15:56 +0200 Subject: [PATCH 071/145] ignore non letters when parsing action from xmpp uri --- src/main/java/eu/siacs/conversations/utils/XmppUri.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java index d0f4cf4217d20057ae98935bec2365225d6b99c4..ebd596d6c2b20df5b06454f6da955d1138fedf8b 100644 --- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java +++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java @@ -4,6 +4,8 @@ import android.net.Uri; import androidx.annotation.NonNull; +import com.google.common.base.CharMatcher; +import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -189,7 +191,10 @@ public class XmppUri { } public boolean isAction(final String action) { - return parameters.containsKey(action); + return Collections2.transform( + parameters.keySet(), + s -> CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('A', 'z')).retainFrom(s) + ).contains(action); } public Jid getJid() { From ea9b73c1fe761d2b0279c5b87a7324d293469550 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 29 Sep 2021 10:42:26 +0200 Subject: [PATCH 072/145] Quicksy: fix drawables not being styled in enter phone number screen --- .../ui/EnterPhoneNumberActivity.java | 4 +++- .../conversations/ui/drawable/TextDrawable.java | 15 +++++++++------ src/quicksy/res/layout/activity_enter_number.xml | 5 +++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/quicksy/java/eu/siacs/conversations/ui/EnterPhoneNumberActivity.java b/src/quicksy/java/eu/siacs/conversations/ui/EnterPhoneNumberActivity.java index e146ab3be249d290d87a34d33890c3a1efb4aa30..52f61a96b4980a56711aa745e828d738061bdce7 100644 --- a/src/quicksy/java/eu/siacs/conversations/ui/EnterPhoneNumberActivity.java +++ b/src/quicksy/java/eu/siacs/conversations/ui/EnterPhoneNumberActivity.java @@ -14,6 +14,8 @@ import android.view.KeyEvent; import android.view.View; import android.widget.EditText; +import org.jetbrains.annotations.NotNull; + import java.util.concurrent.atomic.AtomicBoolean; import eu.siacs.conversations.Config; @@ -126,7 +128,7 @@ public class EnterPhoneNumberActivity extends XmppActivity implements QuickConve } @Override - public void onSaveInstanceState(Bundle savedInstanceState) { + public void onSaveInstanceState(@NotNull Bundle savedInstanceState) { if (this.region != null) { savedInstanceState.putString("region", this.region); } diff --git a/src/quicksy/java/eu/siacs/conversations/ui/drawable/TextDrawable.java b/src/quicksy/java/eu/siacs/conversations/ui/drawable/TextDrawable.java index 3b49962a601b2ef2c62976fbfbc019ebbe2a842e..39ec59235de20a6d4945e94289740e9ae4da1810 100644 --- a/src/quicksy/java/eu/siacs/conversations/ui/drawable/TextDrawable.java +++ b/src/quicksy/java/eu/siacs/conversations/ui/drawable/TextDrawable.java @@ -26,6 +26,8 @@ import android.widget.TextView; import java.lang.ref.WeakReference; +import eu.siacs.conversations.ui.util.StyledAttributes; + public class TextDrawable extends Drawable implements TextWatcher { private WeakReference ref; private String mText; @@ -62,6 +64,7 @@ public class TextDrawable extends Drawable implements TextWatcher { */ public TextDrawable(TextView tv, String initialText, boolean bindToViewsText, boolean bindToViewsPaint) { this(tv.getPaint(), initialText); + mPaint.setColor(StyledAttributes.getColor(tv.getContext(), android.R.attr.textColorPrimary)); ref = new WeakReference<>(tv); if (bindToViewsText || bindToViewsPaint) { if (bindToViewsText) { @@ -157,6 +160,10 @@ public class TextDrawable extends Drawable implements TextWatcher { setBounds(bounds); } + public Paint getPaint() { + return mPaint; + } + public void setPaint(Paint paint) { mPaint = new Paint(paint); //Since this can change the font used, we need to recalculate bounds. @@ -168,8 +175,8 @@ public class TextDrawable extends Drawable implements TextWatcher { invalidateSelf(); } - public Paint getPaint() { - return mPaint; + public String getText() { + return mText; } public void setText(String text) { @@ -183,10 +190,6 @@ public class TextDrawable extends Drawable implements TextWatcher { invalidateSelf(); } - public String getText() { - return mText; - } - @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { diff --git a/src/quicksy/res/layout/activity_enter_number.xml b/src/quicksy/res/layout/activity_enter_number.xml index d54ead3bfbf1074b27506dfa6f7b67dc0292cdec..b6898d8751980a4ed0d651879a2d71909c0c5a44 100644 --- a/src/quicksy/res/layout/activity_enter_number.xml +++ b/src/quicksy/res/layout/activity_enter_number.xml @@ -1,5 +1,6 @@ - + Date: Wed, 29 Sep 2021 10:51:25 +0200 Subject: [PATCH 073/145] Quicksy: theme choose country activity --- .../ui/ChooseCountryActivity.java | 25 +++++++++++-------- .../conversations/ui/EnterNameActivity.java | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/quicksy/java/eu/siacs/conversations/ui/ChooseCountryActivity.java b/src/quicksy/java/eu/siacs/conversations/ui/ChooseCountryActivity.java index 3e1d39db02b0199e1b88eca0d944d808bd79dcb8..77c29e60347625f3f8362ce3eb2a82a93324ffc5 100644 --- a/src/quicksy/java/eu/siacs/conversations/ui/ChooseCountryActivity.java +++ b/src/quicksy/java/eu/siacs/conversations/ui/ChooseCountryActivity.java @@ -2,10 +2,7 @@ package eu.siacs.conversations.ui; import android.content.Context; import android.content.Intent; -import androidx.databinding.DataBindingUtil; import android.os.Bundle; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.appcompat.widget.Toolbar; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; @@ -15,6 +12,10 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.TextView; +import androidx.appcompat.widget.Toolbar; +import androidx.databinding.DataBindingUtil; +import androidx.recyclerview.widget.LinearLayoutManager; + import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -25,6 +26,7 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityChooseCountryBinding; import eu.siacs.conversations.ui.adapter.CountryAdapter; import eu.siacs.conversations.utils.PhoneNumberUtilWrapper; +import eu.siacs.conversations.utils.ThemeHelper; public class ChooseCountryActivity extends ActionBarActivity implements CountryAdapter.OnCountryClicked { @@ -70,16 +72,17 @@ public class ChooseCountryActivity extends ActionBarActivity implements CountryA return true; } }; - private TextView.OnEditorActionListener mSearchDone = (v, actionId, event) -> { - if (countries.size() == 1) { - onCountryClicked(countries.get(0)); - } - return true; - }; + private TextView.OnEditorActionListener mSearchDone = (v, actionId, event) -> { + if (countries.size() == 1) { + onCountryClicked(countries.get(0)); + } + return true; + }; @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setTheme(ThemeHelper.find(this)); this.binding = DataBindingUtil.setContentView(this, R.layout.activity_choose_country); setSupportActionBar((Toolbar) this.binding.toolbar); configureActionBar(getSupportActionBar()); @@ -115,9 +118,9 @@ public class ChooseCountryActivity extends ActionBarActivity implements CountryA private void filterCountries(String needle) { List countries = PhoneNumberUtilWrapper.getCountries(this); Iterator iterator = countries.iterator(); - while(iterator.hasNext()) { + while (iterator.hasNext()) { final PhoneNumberUtilWrapper.Country country = iterator.next(); - if(needle != null && !country.getName().toLowerCase(Locale.getDefault()).contains(needle.toLowerCase(Locale.getDefault()))) { + if (needle != null && !country.getName().toLowerCase(Locale.getDefault()).contains(needle.toLowerCase(Locale.getDefault()))) { iterator.remove(); } } diff --git a/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java b/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java index a15419eba5e054c29c83624ce7f91bd874f63d45..30acdd2ed7c5c72f21da4e7c5d8ea8da847cceb3 100644 --- a/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java +++ b/src/quicksy/java/eu/siacs/conversations/ui/EnterNameActivity.java @@ -20,7 +20,7 @@ public class EnterNameActivity extends XmppActivity implements XmppConnectionSer private Account account; - private AtomicBoolean setNick = new AtomicBoolean(false); + private final AtomicBoolean setNick = new AtomicBoolean(false); @Override protected void onCreate(final Bundle savedInstanceState) { From da14f83a42bdc653dc81be06fa7bf90e23996ebc Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 2 Oct 2021 14:24:36 +0200 Subject: [PATCH 074/145] ensure all bytes are read in socks handshake. fixes #4188 --- .../utils/SocksSocketFactory.java | 162 +++++++++++------- .../xmpp/jingle/JingleSocks5Transport.java | 14 +- 2 files changed, 106 insertions(+), 70 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java index 0ef6ef55ea1a8d66330ae17ad1d119666343e957..2b9d42d7ad1c53215604d752ef24953555344555 100644 --- a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java +++ b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.utils; +import com.google.common.io.ByteStreams; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -12,76 +14,108 @@ import eu.siacs.conversations.Config; public class SocksSocketFactory { - private static final byte[] LOCALHOST = new byte[]{127,0,0,1}; + private static final byte[] LOCALHOST = new byte[]{127, 0, 0, 1}; + + public static void createSocksConnection(final Socket socket, final String destination, final int port) throws IOException { + //TODO use different Socks Addr Type if destination is IP or IPv6 + final InputStream proxyIs = socket.getInputStream(); + final OutputStream proxyOs = socket.getOutputStream(); + proxyOs.write(new byte[]{0x05, 0x01, 0x00}); + proxyOs.flush(); + final byte[] handshake = new byte[2]; + ByteStreams.readFully(proxyIs, handshake); + if (handshake[0] != 0x05 || handshake[1] != 0x00) { + throw new SocksConnectionException("Socks 5 handshake failed"); + } + final byte[] dest = destination.getBytes(); + final ByteBuffer request = ByteBuffer.allocate(7 + dest.length); + request.put(new byte[]{0x05, 0x01, 0x00, 0x03}); + request.put((byte) dest.length); + request.put(dest); + request.putShort((short) port); + proxyOs.write(request.array()); + proxyOs.flush(); + final byte[] response = new byte[4]; + ByteStreams.readFully(proxyIs, response); + final byte ver = response[0]; + if (ver != 0x05) { + throw new IOException(String.format("Unknown Socks version %02X ", ver)); + } + final byte status = response[1]; + final byte bndAddrType = response[3]; + final byte[] bndDestination = readDestination(bndAddrType, proxyIs); + final byte[] bndPort = new byte[2]; + if (bndAddrType == 0x03) { + final String receivedDestination = new String(bndDestination); + if (!receivedDestination.equalsIgnoreCase(destination)) { + throw new IOException(String.format("Destination mismatch. Received %s Expected %s", receivedDestination, destination)); + } + } + ByteStreams.readFully(proxyIs, bndPort); + if (status != 0x00) { + if (status == 0x04) { + throw new HostNotFoundException("Host unreachable"); + } + if (status == 0x05) { + throw new HostNotFoundException("Connection refused"); + } + throw new IOException(String.format("Unknown status code %02X ", status)); + } + } - public static void createSocksConnection(final Socket socket, final String destination, final int port) throws IOException { - final InputStream proxyIs = socket.getInputStream(); - final OutputStream proxyOs = socket.getOutputStream(); - proxyOs.write(new byte[]{0x05, 0x01, 0x00}); - proxyOs.flush(); - final byte[] handshake = new byte[2]; - proxyIs.read(handshake); - if (handshake[0] != 0x05 || handshake[1] != 0x00) { - throw new SocksConnectionException("Socks 5 handshake failed"); - } - final byte[] dest = destination.getBytes(); - final ByteBuffer request = ByteBuffer.allocate(7 + dest.length); - request.put(new byte[]{0x05, 0x01, 0x00, 0x03}); - request.put((byte) dest.length); - request.put(dest); - request.putShort((short) port); - proxyOs.write(request.array()); - proxyOs.flush(); - final byte[] response = new byte[7 + dest.length]; - proxyIs.read(response); - if (response[1] != 0x00) { - if (response[1] == 0x04) { - throw new HostNotFoundException("Host unreachable"); - } - if (response[1] == 0x05) { - throw new HostNotFoundException("Connection refused"); - } - throw new SocksConnectionException("Unable to connect to destination "+(int) (response[1])); - } - } + private static byte[] readDestination(final byte type, final InputStream inputStream) throws IOException { + final byte[] bndDestination; + if (type == 0x01) { + bndDestination = new byte[4]; + } else if (type == 0x03) { + final int length = inputStream.read(); + bndDestination = new byte[length]; + } else if (type == 0x04) { + bndDestination = new byte[16]; + } else { + throw new IOException(String.format("Unknown Socks address type %02X ", type)); + } + ByteStreams.readFully(inputStream, bndDestination); + return bndDestination; + } - public static boolean contains(byte needle, byte[] haystack) { - for(byte hay : haystack) { - if (hay == needle) { - return true; - } - } - return false; - } + public static boolean contains(byte needle, byte[] haystack) { + for (byte hay : haystack) { + if (hay == needle) { + return true; + } + } + return false; + } - private static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException { - Socket socket = new Socket(); - try { - socket.connect(address, Config.CONNECT_TIMEOUT * 1000); - } catch (IOException e) { - throw new SocksProxyNotFoundException(); - } - createSocksConnection(socket, destination, port); - return socket; - } + private static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException { + Socket socket = new Socket(); + try { + socket.connect(address, Config.CONNECT_TIMEOUT * 1000); + } catch (IOException e) { + throw new SocksProxyNotFoundException(); + } + createSocksConnection(socket, destination, port); + return socket; + } - public static Socket createSocketOverTor(String destination, int port) throws IOException { - return createSocket(new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050), destination, port); - } + public static Socket createSocketOverTor(String destination, int port) throws IOException { + return createSocket(new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050), destination, port); + } - private static class SocksConnectionException extends IOException { - SocksConnectionException(String message) { - super(message); - } - } + private static class SocksConnectionException extends IOException { + SocksConnectionException(String message) { + super(message); + } + } - public static class SocksProxyNotFoundException extends IOException { + public static class SocksProxyNotFoundException extends IOException { - } + } - public static class HostNotFoundException extends SocksConnectionException { - HostNotFoundException(String message) { - super(message); - } - } + public static class HostNotFoundException extends SocksConnectionException { + HostNotFoundException(String message) { + super(message); + } + } } 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 6b9a6429ba3adaa85584f5223df731c003b67525..061da64b99e906640990305a8eb54e78fb53487c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -3,6 +3,8 @@ package eu.siacs.conversations.xmpp.jingle; import android.os.PowerManager; import android.util.Log; +import com.google.common.io.ByteStreams; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -114,26 +116,26 @@ public class JingleSocks5Transport extends JingleTransport { final byte[] authBegin = new byte[2]; final InputStream inputStream = socket.getInputStream(); final OutputStream outputStream = socket.getOutputStream(); - inputStream.read(authBegin); + ByteStreams.readFully(inputStream, authBegin); if (authBegin[0] != 0x5) { socket.close(); } final short methodCount = authBegin[1]; final byte[] methods = new byte[methodCount]; - inputStream.read(methods); + ByteStreams.readFully(inputStream, methods); if (SocksSocketFactory.contains((byte) 0x00, methods)) { outputStream.write(new byte[]{0x05, 0x00}); } else { outputStream.write(new byte[]{0x05, (byte) 0xff}); } - byte[] connectCommand = new byte[4]; - inputStream.read(connectCommand); + final byte[] connectCommand = new byte[4]; + ByteStreams.readFully(inputStream, connectCommand); if (connectCommand[0] == 0x05 && connectCommand[1] == 0x01 && connectCommand[3] == 0x03) { int destinationCount = inputStream.read(); final byte[] destination = new byte[destinationCount]; - inputStream.read(destination); + ByteStreams.readFully(inputStream, destination); final byte[] port = new byte[2]; - inputStream.read(port); + ByteStreams.readFully(inputStream, port); final String receivedDestination = new String(destination); final ByteBuffer response = ByteBuffer.allocate(7 + destination.length); final byte[] responseHeader; From d2a387e82f29d9d8ccd213302bc3809abea04ebd Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 2 Oct 2021 16:44:36 +0200 Subject: [PATCH 075/145] correctly calculate socks destination --- .../xmpp/jingle/JingleSocks5Transport.java | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) 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 061da64b99e906640990305a8eb54e78fb53487c..44591090ed3216a9c795ebf69862e25596374e3e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -60,22 +60,12 @@ public class JingleSocks5Transport extends JingleTransport { } else { destBuilder.append(this.connection.getTransportId()); } - if (candidate.getType() == JingleCandidate.TYPE_PROXY) { - if (candidate.isOurs()) { - destBuilder.append(this.account.getJid()); - destBuilder.append(this.connection.getId().with); - } else { - destBuilder.append(this.connection.getId().with); - destBuilder.append(this.account.getJid()); - } + if (candidate.isOurs()) { + destBuilder.append(this.account.getJid()); + destBuilder.append(this.connection.getId().with); } else { - if (connection.isInitiator()) { - destBuilder.append(this.account.getJid()); - destBuilder.append(this.connection.getId().with); - } else { - destBuilder.append(this.connection.getId().with); - destBuilder.append(this.account.getJid()); - } + destBuilder.append(this.connection.getId().with); + destBuilder.append(this.account.getJid()); } messageDigest.reset(); this.destination = CryptoHelper.bytesToHex(messageDigest.digest(destBuilder.toString().getBytes())); @@ -189,7 +179,8 @@ public class JingleSocks5Transport extends JingleTransport { socket.setSoTimeout(0); isEstablished = true; callback.established(); - } catch (IOException e) { + } catch (final IOException e) { + Log.d(Config.LOGTAG, "unable to establish connection to candidate", e); callback.failed(); } }).start(); From 3ede2d00bdfe872bfd476cbf7d85acf56d467ce4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 2 Oct 2021 16:54:19 +0200 Subject: [PATCH 076/145] remove logging --- .../siacs/conversations/xmpp/jingle/JingleSocks5Transport.java | 1 - 1 file changed, 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 44591090ed3216a9c795ebf69862e25596374e3e..a57f4927ff8d65108d4563e14af7882eda8d5cc9 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -180,7 +180,6 @@ public class JingleSocks5Transport extends JingleTransport { isEstablished = true; callback.established(); } catch (final IOException e) { - Log.d(Config.LOGTAG, "unable to establish connection to candidate", e); callback.failed(); } }).start(); From b8eec6ae5beed3deb78d73c58d6ec5cba1c1eede Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 2 Oct 2021 16:59:39 +0200 Subject: [PATCH 077/145] pulled translations from transifex --- src/conversations/res/values-it/strings.xml | 14 +-- src/main/res/values-it/strings.xml | 118 ++++++++++---------- 2 files changed, 66 insertions(+), 66 deletions(-) diff --git a/src/conversations/res/values-it/strings.xml b/src/conversations/res/values-it/strings.xml index 45800a7b8714a11fe2ae8f5a3e68c1d9a4940e85..6e68c5eaa11932b62970ee99d8018d8e8e8eab06 100644 --- a/src/conversations/res/values-it/strings.xml +++ b/src/conversations/res/values-it/strings.xml @@ -1,14 +1,14 @@ - Scegli il tuo provider XMPP + Scegli il tuo fornitore XMPP Usa conversations.im - Crea un nuovo account - Possiedi già un account XMPP? Questo succede se stai già usando un diverso client XMPP o hai già usato prima Conversations. In caso negativo puoi creare un account XMPP adesso. + Crea un nuovo profilo + Possiedi già un profilo XMPP? Questo succede se stai già usando un diverso client XMPP o hai già usato prima Conversations. In caso negativo puoi creare un profilo XMPP adesso. Suggerimento: alcuni provider di email forniscono anche un account XMPP. - XMPP è una rete di instant messaging indipendente dal provider. Puoi usare questo client con qualsiasi server XMPP. -In ogni caso per facilitare puoi creare facilmente un account su conversations.im, un provider pensato apposta per essere usato con Conversations. - Sei stato invitato su %1$s. Ti guideremo nel procedimento per creare un account.\nQuando scegli %1$s come fornitore sarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo. - Sei stato invitato su %1$s. È già stato scelto un nome utente per te. Ti guideremo nel procedimento per creare un account.\nSarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo. + XMPP è una rete di messaggistica istantanea indipendente dal fornitore. Puoi usare questo client con qualsiasi server XMPP. +In ogni caso per facilitare puoi creare facilmente un account su conversations.im, un fornitore pensato apposta per essere usato con Conversations. + Hai ricevuto un invito per %1$s. Ti guideremo nel procedimento per creare un profilo.\nQuando scegli %1$s come fornitore sarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo. + Hai ricevuto un invito per %1$s. È già stato scelto un nome utente per te. Ti guideremo nel procedimento per creare un profilo.\nSarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo. Il tuo invito al server Codice di approvvigionamento formattato male Tocca il pulsante condividi per inviare al contatto un invito per %1$s. diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index a1835ab4cb7add9238d34ba8c252f20678ddd483..3a760a44e8466ba90518b92d7d10547f7ac23908 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -2,13 +2,13 @@ Impostazioni Nuova conversazione - Gestisci account - Gestisci account + Gestisci profili + Gestisci profilo Chiudi conversazione Dettagli del contatto Dettagli chat di gruppo Dettagli canale - Aggiungi account + Aggiungi profilo Modifica il nome Aggiungi alla rubrica Cancella dalla lista @@ -18,7 +18,7 @@ Sblocca dominio Blocca partecipante Sblocca partecipante - Gestisci account + Gestisci profili Impostazioni Condividi con Conversation Inizia una conversazione @@ -54,7 +54,7 @@ Contatto bloccato Bloccato Vuoi rimuovere %s dai segnalibri? Le conversazioni con questo segnalibro non verranno rimosse. - Registra un nuovo account sul server + Registra un nuovo profilo sul server Cambia la password sul server Condividi con Inizia conversazione @@ -72,12 +72,12 @@ Salva OK Errore di %1$s - Usare il tuo account XMPP per inviare segnalazioni di errore aiuta lo sviluppo in corso di %1$s. + Usare il tuo profilo XMPP per inviare segnalazioni di errore aiuta lo sviluppo in corso di %1$s. Invia adesso Non chiedere più - Impossibile connettersi all\'account - Impossibile connettersi a più account - Tocca per gestire i tuoi account + Impossibile connettersi al profilo + Impossibile connettersi a più profili + Tocca per gestire i tuoi profili Allega file Aggiungere questo contatto alla lista dei contatti? Aggiungi contatto @@ -130,9 +130,9 @@ Avanzate Non inviare mai segnalazioni di errore Se scegli di inviare una segnalazione dell’errore aiuterai lo sviluppo - Conferma messaggi + Conferma i messaggi Fai sapere ai tuoi contatti quando hai ricevuto e letto i loro messaggi - Impedisci cattura schermo + Impedisci la cattura dello schermo Nascondi i contenuti dell\'app nell\'elenco recenti e blocca la cattura delle schermate Interfaccia utente OpenKeychain ha generato un errore. @@ -140,7 +140,7 @@ Accetta Si è verificato un errore Errore - Il tuo account + Il tuo profilo Invia aggiornamenti della presenza Ricevi aggiornamenti della presenza Chiedi aggiornamenti della presenza @@ -155,7 +155,7 @@ L\'app che hai usato per condividere questo file non ha fornito autorizzazioni sufficienti. Sconosciuto Disattivato temporaneamente - Online + In linea In connessione\u2026 Offline Non autorizzato @@ -176,16 +176,16 @@ OTR OpenPGP OMEMO - Elimina utente + Elimina profilo Disattiva temporaneamente Pubblica avatar Pubblica chiave pubblica OpenPGP Rimuovi chiave pubblica OpenPGP Sei sicuro di volere rimuovere la tua chiave pubblica OpenPGP dalla dichiarazione di presenza?\nI tuoi contatti non potranno più inviarti messaggi cifrati con OpenPGP. Chiave pubblica OpenPGP pubblicata. - Attiva utente + Attiva profilo Sei sicuro? - L\'eliminazione del tuo account cancellerà tutta la cronologia dielle conversazioni + L\'eliminazione del tuo profilo cancellerà tutta la cronologia dielle conversazioni Registra la voce Indirizzo XMPP Blocca indirizzo XMPP @@ -219,9 +219,9 @@ Nuovi messaggi cifrati con OpenPGP trovati ID chiave OpenPGP Impronta OMEMO - v\\OMEMO impronta - OMEMO fingerprint (messaggio originatore) - v\\OMEMO fingerprint (messaggio originatore) + v\\Impronta OMEMO + Impronta OMEMO (origine del messaggio) + v\\Impronta OMEMO (origine del messaggio) Altri dispositivi Fidati delle impronte OMEMO Ricezione chiavi... @@ -270,7 +270,7 @@ a %s Invia messaggio privato a %s Connetti - Questo utente esiste già + Questo profilo esiste già Successivo Sessione stabilita Salta @@ -285,7 +285,7 @@ Sicurezza Permetti correzione del messaggio Consenti ai tuoi contatti di modificare retroattivamente i loro messaggi - Impostazioni esperto + Impostazioni per esperti Fai attenzione con queste impostazioni Informazioni su %s Ore di quiete @@ -303,7 +303,7 @@ Sei stato buttato fuori da questa chat di gruppo La chat di gruppo è stata chiusa Non sei più in questa chat di gruppo - usando l’account %s + usando il profilo %s ospitato su %s Controllo %s su host HTTP Non sei connesso. Riprova più tardi @@ -322,19 +322,19 @@ Scansiona codice a barre 2D Mostra codice a barre 2D Mostra la lista nera - Dettagli account + Dettagli del profilo Conferma Prova di nuovo Servizio in primo piano Evita che il sistema operativo chiuda la connessione - Crea backup + Crea un backup I file di backup verranno salvati in %s - Creazione file di backup + Creazione dei file di backup Il tuo backup è stato creato I file di backup sono stati salvati in %s Ripristino backup Il tuo backup è stato ripristinato - Non dimenticare di attivare l\'account. + Non dimenticare di attivare il profilo. Scegli un file Ricezione di %1$s file (%2$d%% completato) Scarica %s @@ -356,7 +356,7 @@ Attiva le notifiche Nessun server per chat di gruppo trovato Impossibile creare la chat di gruppo - Avatar utente + Avatar del profilo Copia impronta OMEMO negli appunti Rigenera chiave OMEMO Pulisci dispositivi @@ -373,8 +373,8 @@ Password attuale Nuova password La password non può essere vuota - Attiva tutti gli account - Disattiva tutti gli account + Attiva tutti i profili + Disattiva tutti i profili Esegui azione con Nessuna affiliazione Offline @@ -409,7 +409,7 @@ Rispondi Segna come già letto Input - Invio spedisce + Il tasto Invio spedisce Usa il tasto Invio per spedire il messaggio. Puoi sempre usare Ctrl+Invio per spedire, anche se questa opzione è disattivata. Mostra il tasto invio Cambia il tasto delle faccine nel tasto di invio @@ -477,7 +477,7 @@ Tratta vibrazione come modalità silenziosa Imposta come occupato quando il dispositivo è in modalità vibrazione Impostazioni estese di connessione - Mostra nome host e impostazioni della porta quando configuri un account + Mostra nome host e impostazioni della porta quando configuri un profilo xmpp.esempio.it Accedi con certificato Impossibile analizzare il certificato @@ -501,7 +501,7 @@ Indirizzo server o .onion Questo non è un numero di porta valido Questo non è un nome host valido - %1$d su %2$d account connessi + %1$d su %2$d profili connessi %d messaggio %d messaggi @@ -520,7 +520,7 @@ Notifica solo quando menzionato Notifiche disattivate Notifiche in pausa - Compressione immagini + Compressione delle immagini Suggerimento: usa \"Scegli un file\" invece di \"Scegli un\'immagine\" per inviare singole immagini non compresse a prescindere da questa impostazione. Sempre Solo immagini grandi @@ -529,27 +529,27 @@ Il tuo dispositivo sta facendo delle ingenti ottimizzazioni della batteria per %1$s che potrebbero portare ritardi alle notifiche o anche perdita di messaggi.\n\nTi verrà ora chiesto di disattivarle. Disattiva L\'area selezionata è troppo grande - (Nessun account attivo) + (Nessun profilo attivo) Questo campo è obbligatorio Correggi messaggio Invia messaggio corretto Hai già validato l\'impronta di questa persona in modo sicuro per confermarne la fiducia. Selezionando “Fatto” stai solo confermando che %s fa parte di questa chat di gruppo. - Hai disattivato questo account + Hai disattivato questo profilo Errore di sicurezza: accesso file non valido! Nessuna app trovata per condividere l\'URI Condividi l\'URI con...
Ti registri con il tuo numero di telefono e Quicksy ti suggerirà—in base ai numeri di telefono nella tua rubrica—automaticamente i possibili contatti.

Registrandoti accetti la nostra politica sulla privacy.]]>
Accetta e continua - È disponibile una guida per la creazione di un account su conversations.im.¹\nQuando scegli conversations.im come fornitore potrai comunicare con utenti di altri fornitori dando il tuo indirizzo XMPP completo. + È disponibile una guida per la creazione di un profilo su conversations.im.¹\nQuando scegli conversations.im come fornitore potrai comunicare con utenti di altri fornitori dando il tuo indirizzo XMPP completo. Il tuo indirizzo XMPP completo sarà: %s - Crea account - Usa un altro provider + Crea profilo + Usa un altro fornitore Scegli il tuo nome utente Gestisci manualmente la disponibilità Imposta la tua disponibilità quando modifichi il messaggio di stato. Messaggio di stato Disponibile a chattare - Online + In linea Assente Non disponibile Occupato @@ -564,7 +564,7 @@ Breve Medio Lungo - Trasmissione + Trasmetti l\'utilizzo Fa sapere ai tuoi contatti quando usi Conversations Privacy Tema @@ -573,7 +573,7 @@ Chiaro Scuro Sfondo verde - Usa sfondo verde per messaggi ricevuti + Usa uno sfondo verde per i messaggi ricevuti Impossibile connettersi a OpenKeychain Questo dispositivo non è più in uso Computer @@ -589,9 +589,9 @@ Nessuna autorizzazione per accedere a %s Server remoto non trovato Scadenza server remoto - Impossibile aggiornare l\'account + Impossibile aggiornare il profilo Segnala questo indirizzo XMPP per spam. - Elimina identità OMEMO + Elimina le identità OMEMO Rigenera le tue chiavi OMEMO. I tuoi contatti dovranno verificare un\'altra volta la tua identità. Usalo solo come ultima spiaggia. Cancella le chiavi selezionate Devi essere connesso per pubblicare l\'avatar. @@ -651,7 +651,7 @@ %d mese %d mesi
- Eliminazione automatica messaggi + Eliminazione automatica dei messaggi Elimina automaticamente da questo dispositivo i messaggi più vecchi del lasso di tempo configurato. Cifratura del messaggio Nessun recupero di messaggi a causa del periodo di conservazione locale. @@ -706,11 +706,11 @@ Disattiva adesso Bozza: Cifratura OMEMO - OMEMO verrà sempre usato per chat singole e di gruppi privati. + OMEMO verrà sempre usato per chat singole e gruppi privati. OMEMO verrà usato in modo predefinito nelle nuove conversazioni. OMEMO dovrà essere attivato a mano nelle nuove conversazioni. Crea scorciatoia - Dimensione carattere + Dimensione dei caratteri La dimensione dei caratteri usata all\'interno dell\'app. On in modo predefinito Off in modo predefinito @@ -756,7 +756,7 @@ Questa categoria di notifiche è usata per mostrare una notifica permanente per indicare che %1$s è in esecuzione. Informazioni di stato Problemi di connettività - Questa categoria di notifiche è usata per mostrare un notifica in caso si verifichi un problema nella connessione ad un account. + Questa categoria di notifiche è usata per mostrare un notifica in caso si verifichi un problema nella connessione ad un profilo. Messaggi Chiamate Messaggi @@ -773,7 +773,7 @@ Partecipanti Browser multimediale File omesso per violazione di sicurezza. - Qualità video + Qualità dei video Una qualità inferiore comporta file più piccoli Media (360p) Alta (720p) @@ -834,10 +834,10 @@ Originale (non compresso) Apri con… Immagine profilo di Conversations - Scegli account + Scegli un profilo Ripristina backup Ripristina - Inserisci la tua password per l\'account %s per ripristinare il backup. + Inserisci la tua password per il profilo %s per ripristinare il backup. Non usare la funzione di ripristino del backup tentando di clonare (eseguire simultaneamente) un\'installazione. Il ripristino di un backup è inteso solo per migrazioni o in caso di smarrimento del dispositivo. Impossibile ripristinare il backup. Impossibile decifrare il backup. La password è giusta? @@ -874,10 +874,10 @@ Individua i canali Cerca i canali Possibile violazione della privacy! - search.jabber.network.

L\'uso di questa funzione trasmetterà il tuo indirizzo IP e i termini di ricerca a quel servizio. Vedi la loro Informativa sulla Privacy per saperne di più.]]>
- Ho già un account - Aggiungi un account pre-esistente - Registra un nuovo account + search.jabber.network.

L\'uso di questa funzione trasmetterà il tuo indirizzo IP e i termini di ricerca a quel servizio. Vedi la loro informativa sulla privacy per maggiori informazioni.]]>
+ Ho già un profilo + Aggiungi un profilo esistente + Registra un nuovo profilo Questo sembra un indirizzo di dominio Aggiungere comunque Questo sembra un indirizzo di canale @@ -886,8 +886,8 @@ Evento Apri backup Il file selezionato non è un file di backup di Conversations - Questo account è già stato configurato - Inserisci la password per questo account + Questo profilo è già stato configurato + Inserisci la password per questo profilo Impossibile eseguire questa azione Entra in un canale pubblico... L\'app di condivisione non ha concesso l\'autorizzazione per accedere a questo file. @@ -895,10 +895,10 @@ jabber.network Server locale La maggior parte degli utenti dovrebbe scegliere ‘jabber.network’ per migliori suggerimenti dall\'intero ecosistema XMPP pubblico. - Metodo di scoperta canali + Metodo di scoperta dei canali Backup Al riguardo - Devi attivare un account + Devi attivare un profilo Chiama Chiamata in arrivo Chiamata video in arrivo @@ -964,7 +964,7 @@ Invita su Conversations Impossibile analizzare l\'invito Il server non supporta la generazione di inviti - Nessun account attivo supporta questa funzione + Nessun profilo attivo supporta questa funzione Il backup è iniziato. Riceverai una notifica una volta completato. Impossibile attivare il video. Documento di testo From daf1bbfca5a85e51c3dbcfb77c6f801fdb21dbbc Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 2 Oct 2021 19:49:18 +0200 Subject: [PATCH 078/145] bump version code --- build.gradle | 2 +- .../metadata/android/en-US/changelogs/{42019.txt => 42020.txt} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename fastlane/metadata/android/en-US/changelogs/{42019.txt => 42020.txt} (100%) diff --git a/build.gradle b/build.gradle index 5ee25ed01784bb742e7c0945a42b53218cbff7e8..df472de4477c25b3583a58dc4fb9a66fb46981e4 100644 --- a/build.gradle +++ b/build.gradle @@ -94,7 +94,7 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 42019 + versionCode 42020 versionName "2.10.1" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" diff --git a/fastlane/metadata/android/en-US/changelogs/42019.txt b/fastlane/metadata/android/en-US/changelogs/42020.txt similarity index 100% rename from fastlane/metadata/android/en-US/changelogs/42019.txt rename to fastlane/metadata/android/en-US/changelogs/42020.txt From f182fe6697a020051afc8bd97b74b1c1dd7f2466 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 3 Oct 2021 16:38:30 +0200 Subject: [PATCH 079/145] use PM on direct reply if last message in notifacation stack is PM --- .../conversations/entities/Conversation.java | 11 ++++++++ .../siacs/conversations/entities/Message.java | 27 ++++++++++++++----- .../services/NotificationService.java | 23 +++++++++------- .../services/XmppConnectionService.java | 11 +++++--- 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 5ddd338b375d03ed5bf8516d62118f30605a269f..aeba1f14c7f4418d6123643f14f9e986435c3d6b 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -271,6 +271,17 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl return null; } + public Message findMessageWithUuid(final String uuid) { + synchronized (this.messages) { + for (final Message message : this.messages) { + if (message.getUuid().equals(uuid)) { + return message; + } + } + } + return null; + } + public boolean markAsDeleted(final List uuids) { boolean deleted = false; final PgpDecryptionService pgpDecryptionService = account.getPgpDecryptionService(); diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 6c53134aac3c007989b92dda771da7e3ad9f4947..aa197aa4449c3ca0216c5742d8baaab8af6e5da5 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -984,13 +984,28 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } if (conversation.getMode() == Conversation.MODE_MULTI) { final Jid nextCounterpart = conversation.getNextCounterpart(); - if (nextCounterpart != null) { - message.setCounterpart(nextCounterpart); - message.setTrueCounterpart(conversation.getMucOptions().getTrueCounterpart(nextCounterpart)); - message.setType(isFile ? Message.TYPE_PRIVATE_FILE : Message.TYPE_PRIVATE); - return true; - } + return configurePrivateMessage(conversation, message, nextCounterpart, isFile); } return false; } + + public static boolean configurePrivateMessage(final Message message, final Jid counterpart) { + final Conversation conversation; + if (message.conversation instanceof Conversation) { + conversation = (Conversation) message.conversation; + } else { + return false; + } + return configurePrivateMessage(conversation, message, counterpart, false); + } + + private static boolean configurePrivateMessage(final Conversation conversation, final Message message, final Jid counterpart, final boolean isFile) { + if (counterpart == null) { + return false; + } + message.setCounterpart(counterpart); + message.setTrueCounterpart(conversation.getMucOptions().getTrueCounterpart(counterpart)); + message.setType(isFile ? Message.TYPE_PRIVATE_FILE : Message.TYPE_PRIVATE); + return true; + } } diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index bbde8e904a954b9dc82ce7ecbd52e85a5bb3318e..2f6f36c59f7bfa8bd764ea21bd8074c8371c0385 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -35,6 +35,7 @@ import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.IconCompat; import com.google.common.base.Strings; +import com.google.common.collect.Iterables; import java.io.File; import java.io.IOException; @@ -407,7 +408,7 @@ public class NotificationService { currentInterruptionFilter = 1; //INTERRUPTION_FILTER_ALL } if (currentInterruptionFilter != 1) { - Log.d(Config.LOGTAG,"do not ring or vibrate because interruption filter has been set to "+currentInterruptionFilter); + Log.d(Config.LOGTAG, "do not ring or vibrate because interruption filter has been set to " + currentInterruptionFilter); return; } final ScheduledFuture currentVibrationFuture = this.vibrationFuture; @@ -424,13 +425,13 @@ public class NotificationService { final Resources resources = mXmppConnectionService.getResources(); final String ringtonePreference = preferences.getString("call_ringtone", resources.getString(R.string.incoming_call_ringtone)); if (Strings.isNullOrEmpty(ringtonePreference)) { - Log.d(Config.LOGTAG,"ringtone has been set to none"); + Log.d(Config.LOGTAG, "ringtone has been set to none"); return; } final Uri uri = Uri.parse(ringtonePreference); this.currentlyPlayingRingtone = RingtoneManager.getRingtone(mXmppConnectionService, uri); if (this.currentlyPlayingRingtone == null) { - Log.d(Config.LOGTAG,"unable to find ringtone for uri "+uri); + Log.d(Config.LOGTAG, "unable to find ringtone for uri " + uri); return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { @@ -790,17 +791,18 @@ public class NotificationService { .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ) .setShowsUserInterface(false) .build(); - String replyLabel = mXmppConnectionService.getString(R.string.reply); - NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder( + final String replyLabel = mXmppConnectionService.getString(R.string.reply); + final String lastMessageUuid = Iterables.getLast(messages).getUuid(); + final NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder( R.drawable.ic_send_text_offline, replyLabel, - createReplyIntent(conversation, false)) + createReplyIntent(conversation, lastMessageUuid, false)) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY) .setShowsUserInterface(false) .addRemoteInput(remoteInput).build(); - NotificationCompat.Action wearReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply, + final NotificationCompat.Action wearReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply, replyLabel, - createReplyIntent(conversation, true)).addRemoteInput(remoteInput).build(); + createReplyIntent(conversation, lastMessageUuid, true)).addRemoteInput(remoteInput).build(); mBuilder.extend(new NotificationCompat.WearableExtender().addAction(wearReplyAction)); int addedActionsCount = 1; mBuilder.addAction(markReadAction); @@ -1066,13 +1068,14 @@ public class NotificationService { return PendingIntent.getService(mXmppConnectionService, 0, intent, 0); } - private PendingIntent createReplyIntent(Conversation conversation, boolean dismissAfterReply) { + private PendingIntent createReplyIntent(final Conversation conversation, final String lastMessageUuid, final boolean dismissAfterReply) { final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); intent.setAction(XmppConnectionService.ACTION_REPLY_TO_CONVERSATION); intent.putExtra("uuid", conversation.getUuid()); intent.putExtra("dismiss_notification", dismissAfterReply); + intent.putExtra("last_message_uuid", lastMessageUuid); final int id = generateRequestCode(conversation, dismissAfterReply ? 12 : 14); - return PendingIntent.getService(mXmppConnectionService, id, intent, 0); + return PendingIntent.getService(mXmppConnectionService, id, intent, PendingIntent.FLAG_UPDATE_CURRENT); } private PendingIntent createReadPendingIntent(Conversation conversation) { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 1c91c1ee867c2cfbd897690466d1ae7e9f5f82fa..4222b9faa36a9dfede035828e2c3d4cca26ccd1d 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -726,6 +726,7 @@ public class XmppConnectionService extends Service { } final CharSequence body = remoteInput.getCharSequence("text_reply"); final boolean dismissNotification = intent.getBooleanExtra("dismiss_notification", false); + final String lastMessageUuid = intent.getStringExtra("last_message_uuid"); if (body == null || body.length() <= 0) { break; } @@ -734,7 +735,7 @@ public class XmppConnectionService extends Service { restoredFromDatabaseLatch.await(); final Conversation c = findConversationByUuid(uuid); if (c != null) { - directReply(c, body.toString(), dismissNotification); + directReply(c, body.toString(), lastMessageUuid, dismissNotification); } } catch (InterruptedException e) { Log.d(Config.LOGTAG, "unable to process direct reply"); @@ -932,8 +933,12 @@ public class XmppConnectionService extends Service { } } - private void directReply(Conversation conversation, String body, final boolean dismissAfterReply) { - Message message = new Message(conversation, body, conversation.getNextEncryption()); + private void directReply(final Conversation conversation, final String body, final String lastMessageUuid, final boolean dismissAfterReply) { + final Message inReplyTo = lastMessageUuid == null ? null : conversation.findMessageWithUuid(lastMessageUuid); + final Message message = new Message(conversation, body, conversation.getNextEncryption()); + if (inReplyTo != null && inReplyTo.isPrivateMessage()) { + Message.configurePrivateMessage(message, inReplyTo.getCounterpart()); + } message.markUnread(); if (message.getEncryption() == Message.ENCRYPTION_PGP) { getPgpEngine().encrypt(message, new UiCallback() { From f8c59a7b75f85ea1eab6d8378c878cec22b664a8 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 3 Oct 2021 17:01:32 +0200 Subject: [PATCH 080/145] support imto://xmpp intents --- src/main/AndroidManifest.xml | 8 ++++++++ src/main/java/eu/siacs/conversations/utils/XmppUri.java | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index cc4959ebb1fea45a047ab5be76d14b8bfe5ae96d..0c6d87602bbd3b1e8323c155305195c295eb6583 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -142,6 +142,14 @@ + + + + + + + + Date: Sun, 3 Oct 2021 17:01:51 +0200 Subject: [PATCH 081/145] remove unused import in favor of fqn --- build.gradle | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index df472de4477c25b3583a58dc4fb9a66fb46981e4..9b6126e22c443dc51e52e413a481b5a3deb2ef8f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,3 @@ -import com.android.build.OutputFile - // Top-level build file where you can add configuration options common to all // sub-projects/modules. buildscript { @@ -277,7 +275,7 @@ android { android.applicationVariants.all { variant -> variant.outputs.each { output -> - def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI)) + def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(com.android.build.OutputFile.ABI)) if (baseAbiVersionCode != null) { output.versionCodeOverride = (100 * variant.versionCode) + baseAbiVersionCode } From 4a6df90f0c3198ec9c198d35d3ba528d5965b970 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 3 Oct 2021 17:26:24 +0200 Subject: [PATCH 082/145] attempt to read both jabber and xmpp IM fields from address book --- .../android/JabberIdContact.java | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/android/JabberIdContact.java b/src/main/java/eu/siacs/conversations/android/JabberIdContact.java index 195891d226429f25f61e713528f959db6bbd124a..0fd46148b7e9bbbbfb654ec25b9452026972323c 100644 --- a/src/main/java/eu/siacs/conversations/android/JabberIdContact.java +++ b/src/main/java/eu/siacs/conversations/android/JabberIdContact.java @@ -17,6 +17,21 @@ import eu.siacs.conversations.xmpp.Jid; public class JabberIdContact extends AbstractPhoneContact { + private static final String[] PROJECTION = new String[]{ContactsContract.Data._ID, + ContactsContract.Data.DISPLAY_NAME, + ContactsContract.Data.PHOTO_URI, + ContactsContract.Data.LOOKUP_KEY, + ContactsContract.CommonDataKinds.Im.DATA + }; + private static final String SELECTION = ContactsContract.Data.MIMETYPE + "=? AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? or (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? and " + ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL + "=?))"; + + private static final String[] SELECTION_ARGS = { + ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE, + String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER), + String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM), + "XMPP" + }; + private final Jid jid; private JabberIdContact(Cursor cursor) throws IllegalArgumentException { @@ -36,38 +51,26 @@ public class JabberIdContact extends AbstractPhoneContact { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { return Collections.emptyMap(); } - final String[] PROJECTION = new String[]{ContactsContract.Data._ID, - ContactsContract.Data.DISPLAY_NAME, - ContactsContract.Data.PHOTO_URI, - ContactsContract.Data.LOOKUP_KEY, - ContactsContract.CommonDataKinds.Im.DATA}; - - final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\"" - + ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE - + "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL - + "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER - + "\")"; - final Cursor cursor; - try { - cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null, null); - } catch (Exception e) { - return Collections.emptyMap(); - } - final HashMap contacts = new HashMap<>(); - while (cursor != null && cursor.moveToNext()) { - try { - final JabberIdContact contact = new JabberIdContact(cursor); - final JabberIdContact preexisting = contacts.put(contact.getJid(), contact); - if (preexisting == null || preexisting.rating() < contact.rating()) { - contacts.put(contact.getJid(), contact); + try (final Cursor cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, SELECTION_ARGS, null)) { + if (cursor == null) { + return Collections.emptyMap(); + } + final HashMap contacts = new HashMap<>(); + while (cursor.moveToNext()) { + try { + final JabberIdContact contact = new JabberIdContact(cursor); + final JabberIdContact preexisting = contacts.put(contact.getJid(), contact); + if (preexisting == null || preexisting.rating() < contact.rating()) { + contacts.put(contact.getJid(), contact); + } + } catch (final IllegalArgumentException e) { + Log.d(Config.LOGTAG, "unable to create jabber id contact"); } - } catch (IllegalArgumentException e) { - Log.d(Config.LOGTAG,"unable to create jabber id contact"); } + return contacts; + } catch (final Exception e) { + Log.d(Config.LOGTAG, "unable to query", e); + return Collections.emptyMap(); } - if (cursor != null) { - cursor.close(); - } - return contacts; } } From e664a27cd00436de1dac1341650dee78dcd4912f Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 3 Oct 2021 18:51:18 +0200 Subject: [PATCH 083/145] fix typo in action matcher --- src/main/java/eu/siacs/conversations/utils/XmppUri.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java index 45721ea60fc992d48ab22e95d76d3f0b7fb7cf70..084982e171cf6e550ca4de20572fd1352abea269 100644 --- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java +++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java @@ -194,7 +194,7 @@ public class XmppUri { public boolean isAction(final String action) { return Collections2.transform( parameters.keySet(), - s -> CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('A', 'z')).retainFrom(s) + s -> CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('A', 'Z')).retainFrom(s) ).contains(action); } From 86de21f6a842c624805df91c727b8fde055e9957 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 4 Oct 2021 14:17:01 +0200 Subject: [PATCH 084/145] allow encrypted backups. fixes #4190 --- src/main/AndroidManifest.xml | 3 ++- src/main/res/xml/backup_content.xml | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/main/res/xml/backup_content.xml diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 0c6d87602bbd3b1e8323c155305195c295eb6583..c46fad8b1e2e743123b8423487777fea32c3f19b 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -52,7 +52,8 @@ + + + + \ No newline at end of file From d0af5a002eaf46734d30e5b72e5ff0711c789003 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 4 Oct 2021 15:18:37 +0200 Subject: [PATCH 085/145] leave code comment about xmpp vs jabber vcard entry --- .../java/eu/siacs/conversations/ui/ContactDetailsActivity.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index e1ea08b5f66c15df5efff80b4fa415d1067fb9f4..7c1d08643cb8f52808a56d9464854d2b1b5dde1a 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -155,6 +155,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp } else { intent.putExtra(Intents.Insert.IM_HANDLE, value); intent.putExtra(Intents.Insert.IM_PROTOCOL, CommonDataKinds.Im.PROTOCOL_JABBER); + //TODO for modern use we want PROTOCOL_CUSTOM and an extra field with a value of 'XMPP' + // however we don’t have such a field and thus have to use the legacy PROTOCOL_JABBER } intent.putExtra("finishActivityOnSaveCompleted", true); try { @@ -249,6 +251,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults.length > 0) if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) { From 20e4d108d49f736e639d68b86999ce396d844cb3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 5 Oct 2021 15:43:05 +0200 Subject: [PATCH 086/145] fixed regression of not handling jingle content map parsing failures --- .../conversations/xmpp/jingle/JingleRtpConnection.java | 7 ++++++- .../eu/siacs/conversations/xmpp/jingle/RtpContentMap.java | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 6afcff336336be8241dc50a244a55a3b3a8eaa08..8c4d14843a96327f11b8892591c9d9a3dee0ab29 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -314,7 +314,12 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private ListenableFuture receiveRtpContentMap(final JinglePacket jinglePacket, final boolean expectVerification) { - final RtpContentMap receivedContentMap = RtpContentMap.of(jinglePacket); + final RtpContentMap receivedContentMap; + try { + receivedContentMap = RtpContentMap.of(jinglePacket); + } catch (final Exception e) { + return Futures.immediateFailedFuture(e); + } if (receivedContentMap instanceof OmemoVerifiedRtpContentMap) { final ListenableFuture> future = id.account.getAxolotlService().decrypt((OmemoVerifiedRtpContentMap) receivedContentMap, id.with); return Futures.transform(future, omemoVerifiedPayload -> { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java index 3e02cc29b77d7ba1a5b0085b4de2bef285066117..9baffcf81b2ac01fe8883eb91e54a951c1fc4ff6 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java @@ -159,7 +159,6 @@ public class RtpContentMap { } else if (description instanceof RtpDescription) { rtpDescription = (RtpDescription) description; } else { - Log.d(Config.LOGTAG, "description was " + description); throw new UnsupportedApplicationException("Content does not contain rtp description"); } if (transportInfo instanceof IceUdpTransportInfo) { From 495537d087c346076fa4ff58dff927d2a9a348e2 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 6 Oct 2021 12:18:58 +0200 Subject: [PATCH 087/145] minor code cleanup in UriHandlerActivity --- .../conversations/ui/UriHandlerActivity.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java index 57e1aadcb004d09d3258aca880f6d9c67060eec4..5715cd3f1634ea04d189fdcb03d2ac7348745b3b 100644 --- a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java @@ -147,13 +147,7 @@ public class UriHandlerActivity extends AppCompatActivity { final String body = xmppUri.getBody(); if (jid != null) { - Class clazz; - try { - clazz = Class.forName("eu.siacs.conversations.ui.ShareViaAccountActivity"); - } catch (ClassNotFoundException e) { - clazz = null; - - } + final Class clazz = findShareViaAccountClass(); if (clazz != null) { intent = new Intent(this, clazz); intent.putExtra("contact", jid.toEscapedString()); @@ -164,7 +158,6 @@ public class UriHandlerActivity extends AppCompatActivity { intent.setData(uri); intent.putExtra("account", accounts.get(0).toEscapedString()); } - } else { intent = new Intent(this, ShareWithActivity.class); intent.setAction(Intent.ACTION_SEND); @@ -191,6 +184,14 @@ public class UriHandlerActivity extends AppCompatActivity { startActivity(intent); } + private static Class findShareViaAccountClass() { + try { + return Class.forName("eu.siacs.conversations.ui.ShareViaAccountActivity"); + } catch (final ClassNotFoundException e) { + return null; + } + } + private void handleIntent(Intent data) { if (handled) { return; @@ -248,7 +249,7 @@ public class UriHandlerActivity extends AppCompatActivity { } private static boolean looksLikeJsonObject(final String input) { - final String trimmed = Strings.emptyToNull(input).trim(); + final String trimmed = Strings.nullToEmpty(input).trim(); return trimmed.charAt(0) == '{' && trimmed.charAt(trimmed.length() - 1) == '}'; } } \ No newline at end of file From cc489ef7bfdba59417e0098779d37c7a7a1bcce4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 6 Oct 2021 12:33:36 +0200 Subject: [PATCH 088/145] bump version code --- build.gradle | 2 +- .../metadata/android/en-US/changelogs/{42020.txt => 42022.txt} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename fastlane/metadata/android/en-US/changelogs/{42020.txt => 42022.txt} (100%) diff --git a/build.gradle b/build.gradle index 9b6126e22c443dc51e52e413a481b5a3deb2ef8f..3740db5d5a698190a24877c2fcbf2068827beece 100644 --- a/build.gradle +++ b/build.gradle @@ -92,7 +92,7 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 42020 + versionCode 42022 versionName "2.10.1" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" diff --git a/fastlane/metadata/android/en-US/changelogs/42020.txt b/fastlane/metadata/android/en-US/changelogs/42022.txt similarity index 100% rename from fastlane/metadata/android/en-US/changelogs/42020.txt rename to fastlane/metadata/android/en-US/changelogs/42022.txt From 6d2e406ee55be5213b41eeabf4175ca177128cef Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 7 Oct 2021 09:48:49 +0200 Subject: [PATCH 089/145] attempt to parse Link header from https url scanned from welcome screen --- .../conversations/ui/WelcomeActivity.java | 3 +- .../conversations/ui/UriHandlerActivity.java | 166 +++++++++++++----- .../eu/siacs/conversations/utils/XmppUri.java | 8 +- src/main/res/layout/activity_uri_handler.xml | 31 ++++ src/main/res/values/strings.xml | 2 + 5 files changed, 162 insertions(+), 48 deletions(-) create mode 100644 src/main/res/layout/activity_uri_handler.xml diff --git a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java index 9f3252ac1f309487af17a6bb77f2ccf36375ccf8..d61c64a9c38dc1deadd04016483efc503d3b5d7f 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java @@ -106,7 +106,8 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi } @Override - public void onNewIntent(Intent intent) { + public void onNewIntent(final Intent intent) { + super.onNewIntent(intent); if (intent != null) { setIntent(intent); } diff --git a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java index 5715cd3f1634ea04d189fdcb03d2ac7348745b3b..1e0cf41d35d38cb7734535fddd354f3cb1494812 100644 --- a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java @@ -7,24 +7,39 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.util.Log; +import android.view.View; import android.widget.Toast; +import androidx.annotation.StringRes; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; +import androidx.databinding.DataBindingUtil; import com.google.common.base.Strings; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.databinding.ActivityUriHandlerBinding; +import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.services.QuickConversationsService; import eu.siacs.conversations.utils.ProvisioningUtils; import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.HttpUrl; +import okhttp3.Request; +import okhttp3.Response; public class UriHandlerActivity extends AppCompatActivity { @@ -34,7 +49,9 @@ public class UriHandlerActivity extends AppCompatActivity { private static final int REQUEST_CAMERA_PERMISSIONS_TO_SCAN = 0x6789; private static final int REQUEST_CAMERA_PERMISSIONS_TO_SCAN_AND_PROVISION = 0x6790; private static final Pattern V_CARD_XMPP_PATTERN = Pattern.compile("\nIMPP([^:]*):(xmpp:.+)\n"); - private boolean handled = false; + private static final Pattern LINK_HEADER_PATTERN = Pattern.compile("<(.*?)>"); + private ActivityUriHandlerBinding binding; + private Call call; public static void scan(final Activity activity) { scan(activity, false); @@ -77,9 +94,7 @@ public class UriHandlerActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - this.handled = savedInstanceState != null && savedInstanceState.getBoolean("handled", false); - getLayoutInflater().inflate(R.layout.toolbar, findViewById(android.R.id.content)); - setSupportActionBar(findViewById(R.id.toolbar)); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_uri_handler); } @Override @@ -88,23 +103,17 @@ public class UriHandlerActivity extends AppCompatActivity { handleIntent(getIntent()); } - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - savedInstanceState.putBoolean("handled", this.handled); - super.onSaveInstanceState(savedInstanceState); - } - @Override public void onNewIntent(final Intent intent) { super.onNewIntent(intent); handleIntent(intent); } - private void handleUri(Uri uri) { - handleUri(uri, false); + private boolean handleUri(final Uri uri) { + return handleUri(uri, false); } - private void handleUri(Uri uri, final boolean scanned) { + private boolean handleUri(final Uri uri, final boolean scanned) { final Intent intent; final XmppUri xmppUri = new XmppUri(uri); final List accounts = DatabaseBackend.getInstance(this).getAccountJids(true); @@ -114,19 +123,22 @@ public class UriHandlerActivity extends AppCompatActivity { final Jid jid = xmppUri.getJid(); if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) { if (jid.getEscapedLocal() != null && accounts.contains(jid.asBareJid())) { - Toast.makeText(this, R.string.account_already_exists, Toast.LENGTH_LONG).show(); - return; + showError(R.string.account_already_exists); + return false; } intent = SignupUtils.getTokenRegistrationIntent(this, jid, preAuth); startActivity(intent); - return; + return true; } if (accounts.size() == 0 && xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter(XmppUri.PARAMETER_IBR))) { intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preAuth); intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); startActivity(intent); - return; + return true; } + } else if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) { + showError(R.string.account_registrations_are_not_supported); + return false; } if (accounts.size() == 0) { @@ -134,15 +146,14 @@ public class UriHandlerActivity extends AppCompatActivity { intent = SignupUtils.getSignUpIntent(this); intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); startActivity(intent); + return true; } else { - Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show(); + showError(R.string.invalid_jid); + return false; } - - return; } if (xmppUri.isAction(XmppUri.ACTION_MESSAGE)) { - final Jid jid = xmppUri.getJid(); final String body = xmppUri.getBody(); @@ -177,11 +188,57 @@ public class UriHandlerActivity extends AppCompatActivity { intent.putExtra("scanned", scanned); intent.setData(uri); } else { - Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show(); - return; + showError(R.string.invalid_jid); + return false; } - startActivity(intent); + return true; + } + + private void checkForLinkHeader(final HttpUrl url) { + Log.d(Config.LOGTAG, "checking for link header on " + url); + this.call = HttpConnectionManager.OK_HTTP_CLIENT.newCall(new Request.Builder() + .url(url) + .head() + .build()); + this.call.enqueue(new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + Log.d(Config.LOGTAG, "unable to check HTTP url", e); + showError(R.string.no_xmpp_adddress_found); + } + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + if (response.isSuccessful()) { + final String linkHeader = response.header("Link"); + if (linkHeader != null && processLinkHeader(linkHeader)) { + return; + } + } + showError(R.string.no_xmpp_adddress_found); + } + }); + + } + + private boolean processLinkHeader(final String header) { + final Matcher matcher = LINK_HEADER_PATTERN.matcher(header); + if (matcher.find()) { + final String group = matcher.group(); + final String link = group.substring(1, group.length() - 1); + if (handleUri(Uri.parse(link))) { + finish(); + return true; + } + } + return false; + } + + private void showError(@StringRes int error) { + this.binding.progress.setVisibility(View.INVISIBLE); + this.binding.error.setText(error); + this.binding.error.setVisibility(View.VISIBLE); } private static Class findShareViaAccountClass() { @@ -192,29 +249,33 @@ public class UriHandlerActivity extends AppCompatActivity { } } - private void handleIntent(Intent data) { - if (handled) { - return; - } - if (data == null || data.getAction() == null) { - finish(); + private void handleIntent(final Intent data) { + final String action = data == null ? null : data.getAction(); + if (action == null) { return; } - - handled = true; - - switch (data.getAction()) { + switch (action) { + case Intent.ACTION_MAIN: + binding.progress.setVisibility(call != null && !call.isCanceled() ? View.VISIBLE : View.INVISIBLE); + break; case Intent.ACTION_VIEW: case Intent.ACTION_SENDTO: - handleUri(data.getData()); + if (handleUri(data.getData())) { + finish(); + } break; case ACTION_SCAN_QR_CODE: - Intent intent = new Intent(this, ScanActivity.class); - startActivityForResult(intent, REQUEST_SCAN_QR_CODE); - return; + Log.d(Config.LOGTAG, "scan. allow=" + allowProvisioning()); + setIntent(createMainIntent()); + startActivityForResult(new Intent(this, ScanActivity.class), REQUEST_SCAN_QR_CODE); + break; } + } - finish(); + private Intent createMainIntent() { + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.putExtra(EXTRA_ALLOW_PROVISIONING, allowProvisioning()); + return intent; } private boolean allowProvisioning() { @@ -226,6 +287,7 @@ public class UriHandlerActivity extends AppCompatActivity { public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { super.onActivityResult(requestCode, requestCode, intent); if (requestCode == REQUEST_SCAN_QR_CODE && resultCode == RESULT_OK) { + final boolean allowProvisioning = allowProvisioning(); final String result = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT); if (Strings.isNullOrEmpty(result)) { finish(); @@ -234,18 +296,34 @@ public class UriHandlerActivity extends AppCompatActivity { if (result.startsWith("BEGIN:VCARD\n")) { final Matcher matcher = V_CARD_XMPP_PATTERN.matcher(result); if (matcher.find()) { - handleUri(Uri.parse(matcher.group(2)), true); + if (handleUri(Uri.parse(matcher.group(2)), true)) { + finish(); + } + } else { + showError(R.string.no_xmpp_adddress_found); } - finish(); return; - } else if (QuickConversationsService.isConversations() && looksLikeJsonObject(result) && allowProvisioning()) { + } else if (QuickConversationsService.isConversations() && looksLikeJsonObject(result) && allowProvisioning) { ProvisioningUtils.provision(this, result); finish(); return; } - handleUri(Uri.parse(result), true); + final Uri uri = Uri.parse(result.trim()); + if (allowProvisioning && "https".equalsIgnoreCase(uri.getScheme()) && !XmppUri.INVITE_DOMAIN.equalsIgnoreCase(uri.getHost())) { + final HttpUrl httpUrl = HttpUrl.parse(uri.toString()); + if (httpUrl != null) { + checkForLinkHeader(httpUrl); + } else { + finish(); + } + } else if (handleUri(uri, true)) { + finish(); + } else { + setIntent(new Intent(Intent.ACTION_VIEW, uri)); + } + } else { + finish(); } - finish(); } private static boolean looksLikeJsonObject(final String input) { diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java index 084982e171cf6e550ca4de20572fd1352abea269..6c3075be9b7ec1194d979d961c6bc3096aae76cf 100644 --- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java +++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java @@ -35,6 +35,8 @@ public class XmppUri { private Map parameters = Collections.emptyMap(); private boolean safeSource = true; + public static final String INVITE_DOMAIN = "conversations.im"; + public XmppUri(final String uri) { try { parse(Uri.parse(uri)); @@ -136,10 +138,10 @@ public class XmppUri { return; } this.uri = uri; - String scheme = uri.getScheme(); - String host = uri.getHost(); + final String scheme = uri.getScheme(); + final String host = uri.getHost(); List segments = uri.getPathSegments(); - if ("https".equalsIgnoreCase(scheme) && "conversations.im".equalsIgnoreCase(host)) { + if ("https".equalsIgnoreCase(scheme) && INVITE_DOMAIN.equalsIgnoreCase(host)) { if (segments.size() >= 2 && segments.get(1).contains("@")) { // sample : https://conversations.im/i/foo@bar.com try { diff --git a/src/main/res/layout/activity_uri_handler.xml b/src/main/res/layout/activity_uri_handler.xml new file mode 100644 index 0000000000000000000000000000000000000000..9eda73c87c1ae03e827e1780d722a46a5524e5a2 --- /dev/null +++ b/src/main/res/layout/activity_uri_handler.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 6a3815073c600b36fff7a7cec79a618818532c6c..4e520e8566432ee960665afbfaca14c1f1988f26 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -968,5 +968,7 @@ The backup has been started. You’ll get a notification once it has been completed. Unable to enable video. Plain text document + Account registrations are not supported + No XMPP address found
From 370698164590b602e6b0fc7cc579c44a92c13473 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 14 Oct 2021 17:30:55 +0200 Subject: [PATCH 090/145] fix mime type detection in urls that have query params or an anchor --- .../siacs/conversations/utils/MimeUtils.java | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java index 76d4911b82d5045bd111a2682fb940cff82caed0..c0a6f4cfd17491a87f616f003e796836d7b37542 100644 --- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java @@ -21,6 +21,8 @@ import android.net.Uri; import android.provider.OpenableColumns; import android.util.Log; +import com.google.common.base.Strings; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -274,6 +276,8 @@ public final class MimeUtils { add("image/ico", "ico"); add("image/ief", "ief"); add("image/heic", "heic"); + add("image/heif", "heif"); + add("image/avif", "avif"); // add ".jpg" first so it will be the default for guessExtensionFromMimeType add("image/jpeg", "jpg"); add("image/jpeg", "jpeg"); @@ -587,22 +591,33 @@ public final class MimeUtils { } public static String extractRelevantExtension(final String path, final boolean ignoreCryptoExtension) { - if (path == null || path.isEmpty()) { + if (Strings.isNullOrEmpty(path)) { return null; } - String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase(); - int dotPosition = filename.lastIndexOf("."); + final String filenameQueryAnchor = path.substring(path.lastIndexOf('/') + 1); + final String filenameQuery = cutBefore(filenameQueryAnchor, '#'); + final String filename = cutBefore(filenameQuery, '?'); + final int dotPosition = filename.lastIndexOf('.'); - if (dotPosition != -1) { - String extension = filename.substring(dotPosition + 1); - // we want the real file extension, not the crypto one - if (ignoreCryptoExtension && Transferable.VALID_CRYPTO_EXTENSIONS.contains(extension)) { - return extractRelevantExtension(filename.substring(0, dotPosition)); - } else { - return extension; - } + if (dotPosition == -1) { + return null; + } + final String extension = filename.substring(dotPosition + 1); + // we want the real file extension, not the crypto one + if (ignoreCryptoExtension && Transferable.VALID_CRYPTO_EXTENSIONS.contains(extension)) { + return extractRelevantExtension(filename.substring(0, dotPosition)); + } else { + return extension; + } + } + + private static String cutBefore(final String input, final char c) { + final int position = input.indexOf(c); + if (position > 0) { + return input.substring(0, position); + } else { + return input; } - return null; } } From e0c4964cc2b12801da51311f3af4976c02283db6 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 14 Oct 2021 17:32:07 +0200 Subject: [PATCH 091/145] bump gradle plugin version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3740db5d5a698190a24877c2fcbf2068827beece..a51fb2f45e3c2bf5538952cfd5223423ce14ae90 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.2' + classpath 'com.android.tools.build:gradle:7.0.3' } } From 2ca00265db9b7e3ec3c1f5cb0ed76d64841c1fdd Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 20 Oct 2021 09:52:10 +0200 Subject: [PATCH 092/145] bump speed dial version to something that uses AndroidX --- build.gradle | 2 +- .../ui/StartConversationActivity.java | 23 +++++++++++++++++-- .../menu/start_conversation_fab_submenu.xml | 20 ++++++++-------- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index a51fb2f45e3c2bf5538952cfd5223423ce14ae90..76883787044984f4daf13a87c6f8155d42eb0420 100644 --- a/build.gradle +++ b/build.gradle @@ -69,7 +69,7 @@ dependencies { implementation 'org.hsluv:hsluv:0.2' implementation 'org.conscrypt:conscrypt-android:2.5.2' implementation 'me.drakeet.support:toastcompat:1.1.0' - implementation "com.leinardi.android:speed-dial:2.0.1" + implementation "com.leinardi.android:speed-dial:3.2.0" implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:converter-gson:2.9.0" diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index e6d3ebf6ee524f854171f93f95b150bddb7364d1..99479777963a8aca1551b4717f79d1f804bec0ef 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -37,11 +37,14 @@ import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.MenuRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.PopupMenu; +import androidx.core.content.ContextCompat; import androidx.databinding.DataBindingUtil; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; @@ -51,6 +54,8 @@ import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; import com.google.android.material.textfield.TextInputLayout; +import com.leinardi.android.speeddial.SpeedDialActionItem; +import com.leinardi.android.speeddial.SpeedDialView; import java.util.ArrayList; import java.util.Collections; @@ -266,8 +271,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne setSupportActionBar(binding.toolbar); configureActionBar(getSupportActionBar()); - binding.speedDial.inflate(R.menu.start_conversation_fab_submenu); - + inflateFab(binding.speedDial, R.menu.start_conversation_fab_submenu); binding.tabLayout.setupWithViewPager(binding.startConversationViewPager); binding.startConversationViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override @@ -338,6 +342,21 @@ public class StartConversationActivity extends XmppActivity implements XmppConne }); } + private void inflateFab(final SpeedDialView speedDialView, final @MenuRes int menuRes) { + speedDialView.clearActionItems(); + final PopupMenu popupMenu = new PopupMenu(this, new View(this)); + popupMenu.inflate(menuRes); + final Menu menu = popupMenu.getMenu(); + for (int i = 0; i < menu.size(); i++) { + final MenuItem menuItem = menu.getItem(i); + final SpeedDialActionItem actionItem = new SpeedDialActionItem.Builder(menuItem.getItemId(), menuItem.getIcon()) + .setLabel(menuItem.getTitle() != null ? menuItem.getTitle().toString() : null) + .setFabImageTintColor(ContextCompat.getColor(this, R.color.white)) + .create(); + speedDialView.addActionItem(actionItem); + } + } + public static boolean isValidJid(String input) { try { Jid jid = Jid.ofEscaped(input); diff --git a/src/main/res/menu/start_conversation_fab_submenu.xml b/src/main/res/menu/start_conversation_fab_submenu.xml index bfaca0727684c50016c5558f75a0d0e0282657fd..2cf545d681c821da29bfbba7b9aba202a5e0dcb2 100644 --- a/src/main/res/menu/start_conversation_fab_submenu.xml +++ b/src/main/res/menu/start_conversation_fab_submenu.xml @@ -2,22 +2,22 @@ + android:icon="@drawable/ic_search_white_24dp" + android:title="@string/discover_channels" /> + android:icon="@drawable/ic_input_white_24dp" + android:title="@string/join_public_channel" /> + android:icon="@drawable/ic_public_white_24dp" + android:title="@string/create_public_channel" /> + android:icon="@drawable/ic_group_white_24dp" + android:title="@string/create_private_group_chat" /> + android:icon="@drawable/ic_person_white_48dp" + android:title="@string/add_contact" /> \ No newline at end of file From 7ddd60d314c0f25487ba073c62c0f6bb772bc41c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 20 Oct 2021 10:19:46 +0200 Subject: [PATCH 093/145] bump jxmpp. fix crash in magic create when entering @ --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 76883787044984f4daf13a87c6f8155d42eb0420..596a5ee90ae67b8f3eab665346ef4ec25e35095d 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,7 @@ dependencies { implementation "com.wefika:flowlayout:0.4.1" implementation 'com.otaliastudios:transcoder:0.10.4' - implementation 'org.jxmpp:jxmpp-jid:1.0.1' + implementation 'org.jxmpp:jxmpp-jid:1.0.2' implementation 'org.osmdroid:osmdroid-android:6.1.10' implementation 'org.hsluv:hsluv:0.2' implementation 'org.conscrypt:conscrypt-android:2.5.2' From 869a135bab8e0c5a7fef434ced5096976082cd02 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 20 Oct 2021 10:19:59 +0200 Subject: [PATCH 094/145] bump okhttp --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 596a5ee90ae67b8f3eab665346ef4ec25e35095d..442b2a5006affe16f61e4f6fc158503e39f929d3 100644 --- a/build.gradle +++ b/build.gradle @@ -73,7 +73,7 @@ dependencies { implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:converter-gson:2.9.0" - implementation "com.squareup.okhttp3:okhttp:4.9.1" + implementation "com.squareup.okhttp3:okhttp:4.9.2" implementation 'com.google.guava:guava:30.1.1-android' quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.18' From 226eb739bdd5d1b27feaaf52cb2a206236f1e74e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 31 Oct 2021 08:35:39 +0100 Subject: [PATCH 095/145] make custom 'xmpp' protocol in address book case insensitve fixes #4215 --- .../java/eu/siacs/conversations/android/JabberIdContact.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/android/JabberIdContact.java b/src/main/java/eu/siacs/conversations/android/JabberIdContact.java index 0fd46148b7e9bbbbfb654ec25b9452026972323c..0b701d27ac34cded2c222279f664073cffaa1eb5 100644 --- a/src/main/java/eu/siacs/conversations/android/JabberIdContact.java +++ b/src/main/java/eu/siacs/conversations/android/JabberIdContact.java @@ -23,13 +23,13 @@ public class JabberIdContact extends AbstractPhoneContact { ContactsContract.Data.LOOKUP_KEY, ContactsContract.CommonDataKinds.Im.DATA }; - private static final String SELECTION = ContactsContract.Data.MIMETYPE + "=? AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? or (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? and " + ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL + "=?))"; + private static final String SELECTION = ContactsContract.Data.MIMETYPE + "=? AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? or (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? and lower(" + ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL + ")=?))"; private static final String[] SELECTION_ARGS = { ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE, String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER), String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM), - "XMPP" + "xmpp" }; private final Jid jid; From ba4a47204b1d9e76a5d48656ac2e4b0f9f8f6a42 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 31 Oct 2021 10:20:34 +0100 Subject: [PATCH 096/145] fixed IndexOutOfBounds when rendering quotes --- .../ui/adapter/MessageAdapter.java | 16 ++++----- .../conversations/ui/util/QuoteHelper.java | 34 +++++++++---------- 2 files changed, 24 insertions(+), 26 deletions(-) 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 0eb8a10fbeeb77f9dc13ac21a8eb87dd0e172785..ccb40418a941d6e81c323d5865e022a624687e72 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -370,9 +370,7 @@ public class MessageAdapter extends ArrayAdapter { char current = body.length() > i ? body.charAt(i) : '\n'; if (lineStart == -1) { if (previous == '\n') { - if ( - QuoteHelper.isPositionQuoteStart(body, i) - ) { + if (i < body.length() && QuoteHelper.isPositionQuoteStart(body, i)) { // Line start with quote lineStart = i; if (quoteStart == -1) quoteStart = i; @@ -806,12 +804,12 @@ public class MessageAdapter extends ArrayAdapter { } else if (message.treatAsDownloadable()) { try { final URI uri = new URI(message.getBody()); - displayDownloadableMessage(viewHolder, - message, - activity.getString(R.string.check_x_filesize_on_host, - UIHelper.getFileDescriptionString(activity, message), - uri.getHost()), - darkBackground); + displayDownloadableMessage(viewHolder, + message, + activity.getString(R.string.check_x_filesize_on_host, + UIHelper.getFileDescriptionString(activity, message), + uri.getHost()), + darkBackground); } catch (Exception e) { displayDownloadableMessage(viewHolder, message, diff --git a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java index ac2913037c85d579858572991bc44def0884ebd0..4beee8a2294dbe5bc62da102398d8c6686e6f936 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java @@ -11,47 +11,47 @@ public class QuoteHelper { public static final char QUOTE_ALT_CHAR = '»'; public static final char QUOTE_ALT_END_CHAR = '«'; - public static boolean isPositionQuoteCharacter(CharSequence body, int pos){ + public static boolean isPositionQuoteCharacter(CharSequence body, int pos) { // second part of logical check actually goes against the logic indicated in the method name, since it also checks for context // but it's very useful return body.charAt(pos) == QUOTE_CHAR || isPositionAltQuoteStart(body, pos); } - public static boolean isPositionQuoteEndCharacter(CharSequence body, int pos){ + public static boolean isPositionQuoteEndCharacter(CharSequence body, int pos) { return body.charAt(pos) == QUOTE_END_CHAR; } - public static boolean isPositionAltQuoteCharacter (CharSequence body, int pos){ + public static boolean isPositionAltQuoteCharacter(CharSequence body, int pos) { return body.charAt(pos) == QUOTE_ALT_CHAR; } - public static boolean isPositionAltQuoteEndCharacter(CharSequence body, int pos){ + public static boolean isPositionAltQuoteEndCharacter(CharSequence body, int pos) { return body.charAt(pos) == QUOTE_ALT_END_CHAR; } - public static boolean isPositionAltQuoteStart(CharSequence body, int pos){ + public static boolean isPositionAltQuoteStart(CharSequence body, int pos) { return isPositionAltQuoteCharacter(body, pos) && !isPositionFollowedByAltQuoteEnd(body, pos); } public static boolean isPositionFollowedByQuoteChar(CharSequence body, int pos) { - return body.length() > pos + 1 && isPositionQuoteCharacter(body, pos +1 ); + return body.length() > pos + 1 && isPositionQuoteCharacter(body, pos + 1); } // 'Prequote' means anything we require or can accept in front of a QuoteChar - public static boolean isPositionPrecededByPrequote(CharSequence body, int pos){ + public static boolean isPositionPrecededByPreQuote(CharSequence body, int pos) { return UIHelper.isPositionPrecededByLineStart(body, pos); } - public static boolean isPositionQuoteStart (CharSequence body, int pos){ + public static boolean isPositionQuoteStart(CharSequence body, int pos) { return (isPositionQuoteCharacter(body, pos) - && isPositionPrecededByPrequote(body, pos) + && isPositionPrecededByPreQuote(body, pos) && (UIHelper.isPositionFollowedByQuoteableCharacter(body, pos) - || isPositionFollowedByQuoteChar(body, pos))); + || isPositionFollowedByQuoteChar(body, pos))); } - public static boolean bodyContainsQuoteStart (CharSequence body){ - for (int i = 0; i < body.length(); i++){ - if (isPositionQuoteStart(body, i)){ + public static boolean bodyContainsQuoteStart(CharSequence body) { + for (int i = 0; i < body.length(); i++) { + if (isPositionQuoteStart(body, i)) { return true; } } @@ -76,7 +76,7 @@ public class QuoteHelper { return false; } - public static boolean isNestedTooDeeply (CharSequence line){ + public static boolean isNestedTooDeeply(CharSequence line) { if (isPositionQuoteStart(line, 0)) { int nestingDepth = 1; for (int i = 1; i < line.length(); i++) { @@ -91,9 +91,9 @@ public class QuoteHelper { return false; } - public static String replaceAltQuoteCharsInText(String text){ - for (int i = 0; i < text.length(); i++){ - if (isPositionAltQuoteStart(text, i)){ + public static String replaceAltQuoteCharsInText(String text) { + for (int i = 0; i < text.length(); i++) { + if (isPositionAltQuoteStart(text, i)) { text = text.substring(0, i) + QUOTE_CHAR + text.substring(i + 1); } } From bae9fc45c22ca959fae3c97b5ca515dbbc9b8591 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 31 Oct 2021 10:20:58 +0100 Subject: [PATCH 097/145] make rtcpMux optional --- .../java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 7b9caa66cf9ea532165be5e9c3c219e6581e25f1..751fa66f45a9ca2cb8adeb86da331398015a17d8 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -255,6 +255,7 @@ public class WebRTCWrapper { rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED; //XEP-0176 doesn't support tcp rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY; rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN; + rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.NEGOTIATE; final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, peerConnectionObserver); if (peerConnection == null) { throw new InitializationException("Unable to create PeerConnection"); From a8ff88398df1697751d50daa3c4f068508389528 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 3 Nov 2021 15:59:05 +0100 Subject: [PATCH 098/145] version bump to 2.10.2 + changelog --- CHANGELOG.md | 5 +++++ build.gradle | 8 +++++--- fastlane/metadata/android/en-US/changelogs/42023.txt | 2 ++ 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/42023.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index b9227af42550791448810f3bb57b18218ada4aab..c2bb0a9381fab2381c0ad4377e808c6b82419da4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Version 2.10.2 + +* Fix crash when rendering some quotes +* Fix crash in welcome screen + ### Version 2.10.1 * Fix issue with some videos not being compressed diff --git a/build.gradle b/build.gradle index 442b2a5006affe16f61e4f6fc158503e39f929d3..8edf100657ca0485b7727a39b9a4a85d6f8ad58c 100644 --- a/build.gradle +++ b/build.gradle @@ -92,8 +92,8 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 42022 - versionName "2.10.1" + versionCode 42023 + versionName "2.10.2" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId @@ -277,7 +277,9 @@ android { variant.outputs.each { output -> def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(com.android.build.OutputFile.ABI)) if (baseAbiVersionCode != null) { - output.versionCodeOverride = (100 * variant.versionCode) + baseAbiVersionCode + output.versionCodeOverride = (100 * project.android.defaultConfig.versionCode) + baseAbiVersionCode + } else { + output.versionCodeOverride = 100 * project.android.defaultConfig.versionCode } } diff --git a/fastlane/metadata/android/en-US/changelogs/42023.txt b/fastlane/metadata/android/en-US/changelogs/42023.txt new file mode 100644 index 0000000000000000000000000000000000000000..ed3c253807388b2b27f7716c86c88070232c3a28 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/42023.txt @@ -0,0 +1,2 @@ +* Fix crash when rendering some quotes +* Fix crash in welcome screen From 7d7e158fd75dd447e16ec8c5e9b96e5594a82fee Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 3 Nov 2021 16:00:26 +0100 Subject: [PATCH 099/145] code clean up for LocationProvider --- .../conversations/utils/LocationProvider.java | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/LocationProvider.java b/src/main/java/eu/siacs/conversations/utils/LocationProvider.java index afb39a0089030c238ce1566aaaccf182b7ef459e..3eb786e39a18957a44920b79070384c4500c4098 100644 --- a/src/main/java/eu/siacs/conversations/utils/LocationProvider.java +++ b/src/main/java/eu/siacs/conversations/utils/LocationProvider.java @@ -4,6 +4,8 @@ import android.content.Context; import android.telephony.TelephonyManager; import android.util.Log; +import androidx.core.content.ContextCompat; + import org.osmdroid.util.GeoPoint; import java.io.BufferedReader; @@ -16,11 +18,14 @@ import eu.siacs.conversations.R; public class LocationProvider { - public static final GeoPoint FALLBACK = new GeoPoint(0.0,0.0); + public static final GeoPoint FALLBACK = new GeoPoint(0.0, 0.0); - public static String getUserCountry(Context context) { + public static String getUserCountry(final Context context) { try { - final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + final TelephonyManager tm = ContextCompat.getSystemService(context, TelephonyManager.class); + if (tm == null) { + return getUserCountryFallback(); + } final String simCountry = tm.getSimCountryIso(); if (simCountry != null && simCountry.length() == 2) { // SIM country code is available return simCountry.toUpperCase(Locale.US); @@ -30,40 +35,41 @@ public class LocationProvider { return networkCountry.toUpperCase(Locale.US); } } - } catch (Exception e) { - // fallthrough + return getUserCountryFallback(); + } catch (final Exception e) { + return getUserCountryFallback(); } - Locale locale = Locale.getDefault(); + } + + private static String getUserCountryFallback() { + final Locale locale = Locale.getDefault(); return locale.getCountry(); } - public static GeoPoint getGeoPoint(Context context) { + public static GeoPoint getGeoPoint(final Context context) { return getGeoPoint(context, getUserCountry(context)); } - public static synchronized GeoPoint getGeoPoint(Context context, String country) { - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(context.getResources().openRawResource(R.raw.countries))); + public static synchronized GeoPoint getGeoPoint(final Context context, final String country) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(context.getResources().openRawResource(R.raw.countries)))) { String line; - while((line = reader.readLine()) != null) { - String[] parts = line.split("\\s+",4); + while ((line = reader.readLine()) != null) { + final String[] parts = line.split("\\s+", 4); if (parts.length == 4) { if (country.equalsIgnoreCase(parts[0])) { try { return new GeoPoint(Double.parseDouble(parts[1]), Double.parseDouble(parts[2])); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { return FALLBACK; } } - } else { - Log.d(Config.LOGTAG,"unable to parse line="+line); } } - } catch (IOException e) { - Log.d(Config.LOGTAG,e.getMessage()); + } catch (final IOException e) { + Log.d(Config.LOGTAG, "unable to parse country->geo map", e); } return FALLBACK; } -} +} \ No newline at end of file From d4cbf2e11e6a2883c4583a0ab598be0433f7fcad Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 7 Nov 2021 11:35:00 +0100 Subject: [PATCH 100/145] take intent type into account when sharing with conversations --- .../conversations/services/XmppConnectionService.java | 4 ++-- .../eu/siacs/conversations/ui/ConversationFragment.java | 9 +++++---- .../eu/siacs/conversations/ui/ConversationsActivity.java | 1 + .../eu/siacs/conversations/ui/ShareWithActivity.java | 8 +++++++- .../java/eu/siacs/conversations/ui/util/Attachment.java | 8 ++++---- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 4222b9faa36a9dfede035828e2c3d4cca26ccd1d..815182680979ce4f19d2f25095dbb2f275578ed1 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -572,8 +572,8 @@ public class XmppConnectionService extends Service { } } - public void attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) { - final String mimeType = MimeUtils.guessMimeTypeFromUri(this, uri); + public void attachImageToConversation(final Conversation conversation, final Uri uri, final String type, final UiCallback callback) { + final String mimeType = MimeUtils.guessMimeTypeFromUriAndMime(this, uri, type); final String compressPictures = getCompressPicturesPreference(); if ("never".equals(compressPictures) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index a019f282ab7d2fedfdd05cb3033a75a714d028e3..0077684a5d30d627620056aa7d8b3af2cf0cf7e5 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -688,14 +688,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke toggleInputMethod(); } - private void attachImageToConversation(Conversation conversation, Uri uri) { + private void attachImageToConversation(Conversation conversation, Uri uri, String type) { if (conversation == null) { return; } final Toast prepareFileToast = Toast.makeText(getActivity(), getText(R.string.preparing_image), Toast.LENGTH_LONG); prepareFileToast.show(); activity.delegateUriPermissionsToService(uri); - activity.xmppConnectionService.attachImageToConversation(conversation, uri, + activity.xmppConnectionService.attachImageToConversation(conversation, uri, type, new UiCallback() { @Override @@ -889,7 +889,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke attachLocationToConversation(conversation, attachment.getUri()); } else if (attachment.getType() == Attachment.Type.IMAGE) { Log.d(Config.LOGTAG, "ConversationsActivity.commitAttachments() - attaching image to conversations. CHOOSE_IMAGE"); - attachImageToConversation(conversation, attachment.getUri()); + attachImageToConversation(conversation, attachment.getUri(), attachment.getMime()); } else { Log.d(Config.LOGTAG, "ConversationsActivity.commitAttachments() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE/RECORD_VIDEO"); attachFileToConversation(conversation, attachment.getUri(), attachment.getMime()); @@ -2186,13 +2186,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke final boolean asQuote = extras.getBoolean(ConversationsActivity.EXTRA_AS_QUOTE); final boolean pm = extras.getBoolean(ConversationsActivity.EXTRA_IS_PRIVATE_MESSAGE, false); final boolean doNotAppend = extras.getBoolean(ConversationsActivity.EXTRA_DO_NOT_APPEND, false); + final String type = extras.getString(ConversationsActivity.EXTRA_TYPE); final List uris = extractUris(extras); if (uris != null && uris.size() > 0) { if (uris.size() == 1 && "geo".equals(uris.get(0).getScheme())) { mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), uris.get(0), Attachment.Type.LOCATION)); } else { final List cleanedUris = cleanUris(new ArrayList<>(uris)); - mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), cleanedUris)); + mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), cleanedUris, type)); } toggleInputMethod(); return; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index fbdba5724853353d666049555af517418fa6d007..cc46ed33f8f3813acb7f8e1497a852c45bbd6e2a 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -99,6 +99,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio public static final String EXTRA_DO_NOT_APPEND = "do_not_append"; public static final String EXTRA_POST_INIT_ACTION = "post_init_action"; public static final String POST_ACTION_RECORD_VOICE = "record_voice"; + public static final String EXTRA_TYPE = "type"; private static final List VIEW_AND_SHARE_ACTIONS = Arrays.asList( ACTION_VIEW_CONVERSATION, diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index cb698691e538881c4d74c6f07da085074f8fbaea..d03928c8cb422ce608ffc69b8f3fd71d76780acd 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -33,7 +33,8 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer refreshUi(); } - private class Share { + private static class Share { + public String type; ArrayList uris = new ArrayList<>(); public String account; public String contact; @@ -65,6 +66,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults.length > 0) if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (requestCode == REQUEST_STORAGE_PERMISSION) { @@ -139,6 +141,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer } else if (type != null && uri != null) { this.share.uris.clear(); this.share.uris.add(uri); + this.share.type = type; } else { this.share.text = text; this.share.asQuote = asQuote; @@ -193,6 +196,9 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer intent.setAction(Intent.ACTION_SEND_MULTIPLE); intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, share.uris); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + if (share.type != null) { + intent.putExtra(ConversationsActivity.EXTRA_TYPE, share.type); + } } else if (share.text != null) { intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION); intent.putExtra(Intent.EXTRA_TEXT, share.text); diff --git a/src/main/java/eu/siacs/conversations/ui/util/Attachment.java b/src/main/java/eu/siacs/conversations/ui/util/Attachment.java index 4083d5b0450a156f96a65cb7e43edd3de7a96a01..b539c70efca6aafc6de8b998f323abbf055b06a1 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/Attachment.java +++ b/src/main/java/eu/siacs/conversations/ui/util/Attachment.java @@ -136,10 +136,10 @@ public class Attachment implements Parcelable { return Collections.singletonList(new Attachment(uri, type, mime)); } - public static List of(final Context context, List uris) { - List attachments = new ArrayList<>(); - for (Uri uri : uris) { - final String mime = MimeUtils.guessMimeTypeFromUri(context, uri); + public static List of(final Context context, List uris, final String type) { + final List attachments = new ArrayList<>(); + for (final Uri uri : uris) { + final String mime = MimeUtils.guessMimeTypeFromUriAndMime(context, uri, type); attachments.add(new Attachment(uri, mime != null && isImage(mime) ? Type.IMAGE : Type.FILE, mime)); } return attachments; From b5786787f011607b2aacc869c2a9d1c83ffc871f Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 9 Nov 2021 14:27:26 +0100 Subject: [PATCH 101/145] bump libphone number library --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8edf100657ca0485b7727a39b9a4a85d6f8ad58c..a95b2ff8e3f3c23857e93c07d9712a3ea99950a9 100644 --- a/build.gradle +++ b/build.gradle @@ -76,7 +76,7 @@ dependencies { implementation "com.squareup.okhttp3:okhttp:4.9.2" implementation 'com.google.guava:guava:30.1.1-android' - quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.18' + quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.36' implementation fileTree(include: ['libwebrtc-m92.aar'], dir: 'libs') } From fda45a7c86004dfee6e05dd748819fe195fd147f Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 10 Nov 2021 16:40:16 +0100 Subject: [PATCH 102/145] use implicit descriptions for WebRTC using the parameter-free form of setLocalDescription() solves some potential race conditions that can occur - especially once we introduce restartIce() --- .../xmpp/jingle/JingleRtpConnection.java | 24 +++---- .../xmpp/jingle/WebRTCWrapper.java | 69 ++++++------------- 2 files changed, 28 insertions(+), 65 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 8c4d14843a96327f11b8892591c9d9a3dee0ab29..72d2a48379069ead3bb1702aaa1ff43d230dedc6 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -537,9 +537,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web try { this.webRTCWrapper.setRemoteDescription(sdp).get(); addIceCandidatesFromBlackLog(); - org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createAnswer().get(); + org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.setLocalDescription().get(); prepareSessionAccept(webRTCSessionDescription); } catch (final Exception e) { + //TODO sending the error text is worthwhile as well. Especially for FailureToSet exceptions failureToAcceptSession(e); } } @@ -569,7 +570,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web new FutureCallback() { @Override public void onSuccess(final RtpContentMap outgoingContentMap) { - sendSessionAccept(outgoingContentMap, webRTCSessionDescription); + sendSessionAccept(outgoingContentMap); } @Override @@ -581,7 +582,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web ); } - private void sendSessionAccept(final RtpContentMap rtpContentMap, final org.webrtc.SessionDescription webRTCSessionDescription) { + private void sendSessionAccept(final RtpContentMap rtpContentMap) { if (isTerminated()) { Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": preparing session accept was too slow. already terminated. nothing to do."); return; @@ -589,11 +590,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web transitionOrThrow(State.SESSION_ACCEPTED); final JinglePacket sessionAccept = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId); send(sessionAccept); - try { - webRTCWrapper.setLocalDescription(webRTCSessionDescription).get(); - } catch (Exception e) { - failureToAcceptSession(e); - } } private ListenableFuture prepareOutgoingContentMap(final RtpContentMap rtpContentMap) { @@ -841,9 +837,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web return; } try { - org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createOffer().get(); + org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.setLocalDescription().get(); prepareSessionInitiate(webRTCSessionDescription, targetState); } catch (final Exception e) { + //TODO sending the error text is worthwhile as well. Especially for FailureToSet exceptions failureToInitiateSession(e, targetState); } } @@ -877,7 +874,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web Futures.addCallback(outgoingContentMapFuture, new FutureCallback() { @Override public void onSuccess(final RtpContentMap outgoingContentMap) { - sendSessionInitiate(outgoingContentMap, webRTCSessionDescription, targetState); + sendSessionInitiate(outgoingContentMap, targetState); } @Override @@ -887,7 +884,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web }, MoreExecutors.directExecutor()); } - private void sendSessionInitiate(final RtpContentMap rtpContentMap, final org.webrtc.SessionDescription webRTCSessionDescription, final State targetState) { + private void sendSessionInitiate(final RtpContentMap rtpContentMap, final State targetState) { if (isTerminated()) { Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": preparing session was too slow. already terminated. nothing to do."); return; @@ -895,11 +892,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web this.transitionOrThrow(targetState); final JinglePacket sessionInitiate = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); send(sessionInitiate); - try { - this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get(); - } catch (Exception e) { - failureToInitiateSession(e, targetState); - } } private ListenableFuture encryptSessionInitiate(final RtpContentMap rtpContentMap) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 751fa66f45a9ca2cb8adeb86da331398015a17d8..368b1391369c9b256862d622b33e43748e1b9a9a 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -403,70 +403,36 @@ public class WebRTCWrapper { videoTrack.setEnabled(enabled); } - ListenableFuture createOffer() { + ListenableFuture setLocalDescription() { return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> { final SettableFuture future = SettableFuture.create(); - peerConnection.createOffer(new CreateSdpObserver() { - @Override - public void onCreateSuccess(SessionDescription sessionDescription) { - future.set(sessionDescription); - } - - @Override - public void onCreateFailure(String s) { - future.setException(new IllegalStateException("Unable to create offer: " + s)); - } - }, new MediaConstraints()); - return future; - }, MoreExecutors.directExecutor()); - } - - ListenableFuture createAnswer() { - return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> { - final SettableFuture future = SettableFuture.create(); - peerConnection.createAnswer(new CreateSdpObserver() { + peerConnection.setLocalDescription(new SetSdpObserver() { @Override - public void onCreateSuccess(SessionDescription sessionDescription) { - future.set(sessionDescription); + public void onSetSuccess() { + final SessionDescription description = peerConnection.getLocalDescription(); + Log.d(EXTENDED_LOGGING_TAG, "set local description:"); + logDescription(description); + future.set(description); } @Override - public void onCreateFailure(String s) { - future.setException(new IllegalStateException("Unable to create answer: " + s)); + public void onSetFailure(final String message) { + future.setException(new FailureToSetDescriptionException(message)); } - }, new MediaConstraints()); + }); return future; }, MoreExecutors.directExecutor()); } - ListenableFuture setLocalDescription(final SessionDescription sessionDescription) { - Log.d(EXTENDED_LOGGING_TAG, "setting local description:"); + private static void logDescription(final SessionDescription sessionDescription) { for (final String line : sessionDescription.description.split(eu.siacs.conversations.xmpp.jingle.SessionDescription.LINE_DIVIDER)) { Log.d(EXTENDED_LOGGING_TAG, line); } - return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> { - final SettableFuture future = SettableFuture.create(); - peerConnection.setLocalDescription(new SetSdpObserver() { - @Override - public void onSetSuccess() { - future.set(null); - } - - @Override - public void onSetFailure(final String s) { - future.setException(new IllegalArgumentException("unable to set local session description: " + s)); - - } - }, sessionDescription); - return future; - }, MoreExecutors.directExecutor()); } ListenableFuture setRemoteDescription(final SessionDescription sessionDescription) { Log.d(EXTENDED_LOGGING_TAG, "setting remote description:"); - for (final String line : sessionDescription.description.split(eu.siacs.conversations.xmpp.jingle.SessionDescription.LINE_DIVIDER)) { - Log.d(EXTENDED_LOGGING_TAG, line); - } + logDescription(sessionDescription); return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> { final SettableFuture future = SettableFuture.create(); peerConnection.setRemoteDescription(new SetSdpObserver() { @@ -476,9 +442,8 @@ public class WebRTCWrapper { } @Override - public void onSetFailure(String s) { - future.setException(new IllegalArgumentException("unable to set remote session description: " + s)); - + public void onSetFailure(final String message) { + future.setException(new FailureToSetDescriptionException(message)); } }, sessionDescription); return future; @@ -619,6 +584,12 @@ public class WebRTCWrapper { } + private static class FailureToSetDescriptionException extends IllegalArgumentException { + public FailureToSetDescriptionException(String message) { + super(message); + } + } + private static class CapturerChoice { private final CameraVideoCapturer cameraVideoCapturer; private final CameraEnumerationAndroid.CaptureFormat captureFormat; From 4ec0996dffa2381dc7399a7dc8bfa67e2cdf24ed Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 11 Nov 2021 11:19:37 +0100 Subject: [PATCH 103/145] webrtc: include oldState in onConnectionChange --- .../xmpp/jingle/JingleRtpConnection.java | 4 ++-- .../conversations/xmpp/jingle/WebRTCWrapper.java | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 72d2a48379069ead3bb1702aaa1ff43d230dedc6..af4e05ba0905b0e67470801dbab9a0bee2042b44 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1330,8 +1330,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } @Override - public void onConnectionChange(final PeerConnection.PeerConnectionState newState) { - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed to " + newState); + public void onConnectionChange(final PeerConnection.PeerConnectionState oldState, final PeerConnection.PeerConnectionState newState) { + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed: "+oldState+"->" + newState); if (newState == PeerConnection.PeerConnectionState.CONNECTED && this.rtpConnectionStarted == 0) { this.rtpConnectionStarted = SystemClock.elapsedRealtime(); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 368b1391369c9b256862d622b33e43748e1b9a9a..f5b2c0b8764fa35ff58d27bb4bca77c13ceac3cc 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -88,6 +88,7 @@ public class WebRTCWrapper { private final Handler mainHandler = new Handler(Looper.getMainLooper()); private VideoTrack localVideoTrack = null; private VideoTrack remoteVideoTrack = null; + private PeerConnection.PeerConnectionState currentState; private final PeerConnection.Observer peerConnectionObserver = new PeerConnection.Observer() { @Override public void onSignalingChange(PeerConnection.SignalingState signalingState) { @@ -98,8 +99,9 @@ public class WebRTCWrapper { } @Override - public void onConnectionChange(PeerConnection.PeerConnectionState newState) { - eventCallback.onConnectionChange(newState); + public void onConnectionChange(final PeerConnection.PeerConnectionState newState) { + eventCallback.onConnectionChange(currentState, newState); + currentState = newState; } @Override @@ -150,7 +152,7 @@ public class WebRTCWrapper { @Override public void onRenegotiationNeeded() { - + Log.d(EXTENDED_LOGGING_TAG,"onRenegotiationNeeded - current state: "+currentState); } @Override @@ -261,6 +263,8 @@ public class WebRTCWrapper { throw new InitializationException("Unable to create PeerConnection"); } + this.currentState = peerConnection.connectionState(); + final Optional optionalCapturerChoice = media.contains(Media.VIDEO) ? getVideoCapturer() : Optional.absent(); if (optionalCapturerChoice.isPresent()) { @@ -531,7 +535,7 @@ public class WebRTCWrapper { public interface EventCallback { void onIceCandidate(IceCandidate iceCandidate); - void onConnectionChange(PeerConnection.PeerConnectionState newState); + void onConnectionChange(PeerConnection.PeerConnectionState oldState, PeerConnection.PeerConnectionState newState); void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set availableAudioDevices); } From 61851e5f843307f2f90de892fd4fefbff5b19d46 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 11 Nov 2021 14:40:15 +0100 Subject: [PATCH 104/145] do not automacially hang up failed webrtc sessions --- .../conversations/ui/RtpSessionActivity.java | 16 +++-- .../xmpp/jingle/JingleRtpConnection.java | 66 +++++++++++-------- .../xmpp/jingle/RtpEndUserState.java | 1 + .../xmpp/jingle/WebRTCWrapper.java | 16 ++++- src/main/res/values/strings.xml | 1 + 5 files changed, 64 insertions(+), 36 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 96aa00db048d534ea749a7e3db94b0fdab8af3bb..b2cf583b51af92928f5afac556869ddaf4343f56 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -96,7 +96,12 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe ); private static final List STATES_SHOWING_SWITCH_TO_CHAT = Arrays.asList( RtpEndUserState.CONNECTING, - RtpEndUserState.CONNECTED + RtpEndUserState.CONNECTED, + RtpEndUserState.RECONNECTING + ); + private static final List STATES_CONSIDERED_CONNECTED = Arrays.asList( + RtpEndUserState.CONNECTED, + RtpEndUserState.RECONNECTING ); private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session"; private static final int REQUEST_ACCEPT_CALL = 0x1111; @@ -502,7 +507,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe private boolean isConnected() { final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null; - return connection != null && connection.getEndUserState() == RtpEndUserState.CONNECTED; + return connection != null && STATES_CONSIDERED_CONNECTED.contains(connection.getEndUserState()); } private boolean switchToPictureInPicture() { @@ -661,6 +666,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe case CONNECTED: setTitle(R.string.rtp_state_connected); break; + case RECONNECTING: + setTitle(R.string.rtp_state_reconnecting); + break; case ACCEPTING_CALL: setTitle(R.string.rtp_state_accepting_call); break; @@ -803,7 +811,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe @SuppressLint("RestrictedApi") private void updateInCallButtonConfiguration(final RtpEndUserState state, final Set media) { - if (state == RtpEndUserState.CONNECTED && !isPictureInPicture()) { + if (STATES_CONSIDERED_CONNECTED.contains(state) && !isPictureInPicture()) { Preconditions.checkArgument(media.size() > 0, "Media must not be empty"); if (media.contains(Media.VIDEO)) { final JingleRtpConnection rtpConnection = requireRtpConnection(); @@ -998,7 +1006,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe RendererCommon.ScalingType.SCALE_ASPECT_FILL, RendererCommon.ScalingType.SCALE_ASPECT_FIT ); - if (state == RtpEndUserState.CONNECTED) { + if (STATES_CONSIDERED_CONNECTED.contains(state)) { binding.appBarLayout.setVisibility(View.GONE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); binding.remoteVideoWrapper.setVisibility(View.VISIBLE); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index af4e05ba0905b0e67470801dbab9a0bee2042b44..2d322ead9ec08290d95f8b08a65472cf6cf49ce2 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1035,24 +1035,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web return RtpEndUserState.CONNECTING; } case SESSION_ACCEPTED: - //TODO refactor this out into separate method (that uses switch for better readability) - final PeerConnection.PeerConnectionState state; - try { - state = webRTCWrapper.getState(); - } catch (final WebRTCWrapper.PeerConnectionNotInitialized e) { - //We usually close the WebRTCWrapper *before* transitioning so we might still - //be in SESSION_ACCEPTED even though the peerConnection has been torn down - return RtpEndUserState.ENDING_CALL; - } - if (state == PeerConnection.PeerConnectionState.CONNECTED) { - return RtpEndUserState.CONNECTED; - } else if (state == PeerConnection.PeerConnectionState.NEW || state == PeerConnection.PeerConnectionState.CONNECTING) { - return RtpEndUserState.CONNECTING; - } else if (state == PeerConnection.PeerConnectionState.CLOSED) { - return RtpEndUserState.ENDING_CALL; - } else { - return rtpConnectionStarted == 0 ? RtpEndUserState.CONNECTIVITY_ERROR : RtpEndUserState.CONNECTIVITY_LOST_ERROR; - } + return getPeerConnectionStateAsEndUserState(); case REJECTED: case REJECTED_RACED: case TERMINATED_DECLINED_OR_BUSY: @@ -1082,6 +1065,29 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web throw new IllegalStateException(String.format("%s has no equivalent EndUserState", this.state)); } + + private RtpEndUserState getPeerConnectionStateAsEndUserState() { + final PeerConnection.PeerConnectionState state; + try { + state = webRTCWrapper.getState(); + } catch (final WebRTCWrapper.PeerConnectionNotInitialized e) { + //We usually close the WebRTCWrapper *before* transitioning so we might still + //be in SESSION_ACCEPTED even though the peerConnection has been torn down + return RtpEndUserState.ENDING_CALL; + } + switch (state) { + case CONNECTED: + return RtpEndUserState.CONNECTED; + case NEW: + case CONNECTING: + return RtpEndUserState.CONNECTING; + case CLOSED: + return RtpEndUserState.ENDING_CALL; + default: + return rtpConnectionStarted == 0 ? RtpEndUserState.CONNECTIVITY_ERROR : RtpEndUserState.RECONNECTING; + } + } + public Set getMedia() { final State current = getState(); if (current == State.NULL) { @@ -1331,29 +1337,31 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web @Override public void onConnectionChange(final PeerConnection.PeerConnectionState oldState, final PeerConnection.PeerConnectionState newState) { - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed: "+oldState+"->" + newState); + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed: " + oldState + "->" + newState); + final boolean neverConnected = this.rtpConnectionStarted == 0; if (newState == PeerConnection.PeerConnectionState.CONNECTED && this.rtpConnectionStarted == 0) { this.rtpConnectionStarted = SystemClock.elapsedRealtime(); } if (newState == PeerConnection.PeerConnectionState.CLOSED && this.rtpConnectionEnded == 0) { this.rtpConnectionEnded = SystemClock.elapsedRealtime(); } - //TODO 'failed' means we need to restart ICE - // - //TODO 'disconnected' can probably be ignored as "This is a less stringent test than failed - // and may trigger intermittently and resolve just as spontaneously on less reliable networks, - // or during temporary disconnections. When the problem resolves, the connection may return - // to the connected state." - // Obviously the UI needs to reflect this new state with a 'reconnecting' display or something - if (Arrays.asList(PeerConnection.PeerConnectionState.FAILED, PeerConnection.PeerConnectionState.DISCONNECTED).contains(newState)) { + + if (neverConnected && Arrays.asList(PeerConnection.PeerConnectionState.FAILED, PeerConnection.PeerConnectionState.DISCONNECTED).contains(newState)) { if (isTerminated()) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not sending session-terminate after connectivity error because session is already in state " + this.state); return; } new Thread(this::closeWebRTCSessionAfterFailedConnection).start(); - } else { - updateEndUserState(); + } else if (newState == PeerConnection.PeerConnectionState.FAILED) { + Log.d(Config.LOGTAG, "attempting to restart ICE"); + webRTCWrapper.restartIce(); } + updateEndUserState(); + } + + @Override + public void onRenegotiationNeeded() { + Log.d(Config.LOGTAG, "onRenegotiationNeeded()"); } private void closeWebRTCSessionAfterFailedConnection() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java index 61536bb7c19b849cb2cf140c8560447a27117f45..9a431bc011c7bb6758d3916ffcd240b6c0acb958 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java @@ -4,6 +4,7 @@ public enum RtpEndUserState { INCOMING_CALL, //received a 'propose' message CONNECTING, //session-initiate or session-accepted but no webrtc peer connection yet CONNECTED, //session-accepted and webrtc peer connection is connected + RECONNECTING, //session-accepted and webrtc peer connection was connected once but is currently disconnected or failed FINDING_DEVICE, //'propose' has been sent out; no 184 ack yet RINGING, //'propose' has been sent out and it has been 184 acked ACCEPTING_CALL, //'proceed' message has been sent; but no session-initiate has been received diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index f5b2c0b8764fa35ff58d27bb4bca77c13ceac3cc..6e4a94539e3005fcf6eb4220a17e89844fe5d57b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -100,13 +100,14 @@ public class WebRTCWrapper { @Override public void onConnectionChange(final PeerConnection.PeerConnectionState newState) { - eventCallback.onConnectionChange(currentState, newState); + final PeerConnection.PeerConnectionState oldState = currentState; currentState = newState; + eventCallback.onConnectionChange(oldState, newState); } @Override public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) { - + Log.d(EXTENDED_LOGGING_TAG, "onIceConnectionChange(" + iceConnectionState + ")"); } @Override @@ -152,7 +153,10 @@ public class WebRTCWrapper { @Override public void onRenegotiationNeeded() { - Log.d(EXTENDED_LOGGING_TAG,"onRenegotiationNeeded - current state: "+currentState); + Log.d(EXTENDED_LOGGING_TAG, "onRenegotiationNeeded()"); + if (currentState != null && currentState != PeerConnection.PeerConnectionState.NEW) { + eventCallback.onRenegotiationNeeded(); + } } @Override @@ -293,6 +297,10 @@ public class WebRTCWrapper { this.peerConnection = peerConnection; } + void restartIce() { + requirePeerConnection().restartIce(); + } + synchronized void close() { final PeerConnection peerConnection = this.peerConnection; final CapturerChoice capturerChoice = this.capturerChoice; @@ -538,6 +546,8 @@ public class WebRTCWrapper { void onConnectionChange(PeerConnection.PeerConnectionState oldState, PeerConnection.PeerConnectionState newState); void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set availableAudioDevices); + + void onRenegotiationNeeded(); } private static abstract class SetSdpObserver implements SdpObserver { diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 4e520e8566432ee960665afbfaca14c1f1988f26..dae88c606c81b5ab4e3b6177822ff5a6c257bd45 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -904,6 +904,7 @@ Incoming video call Connecting Connected + Reconnecting Accepting call Ending call Answer From 9843b72f6fc3993313c404c2ab0e5aa70b4c6a77 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 11 Nov 2021 15:23:45 +0100 Subject: [PATCH 105/145] always use Camera2Enumerator --- .../siacs/conversations/xmpp/jingle/WebRTCWrapper.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 6e4a94539e3005fcf6eb4220a17e89844fe5d57b..c1201206ad604d803c328ae2f335ff2788b3796c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -476,16 +476,8 @@ public class WebRTCWrapper { requirePeerConnection().addIceCandidate(iceCandidate); } - private CameraEnumerator getCameraEnumerator() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - return new Camera2Enumerator(requireContext()); - } else { - return new Camera1Enumerator(); - } - } - private Optional getVideoCapturer() { - final CameraEnumerator enumerator = getCameraEnumerator(); + final CameraEnumerator enumerator = new Camera2Enumerator(requireContext()); final Set deviceNames = ImmutableSet.copyOf(enumerator.getDeviceNames()); for (final String deviceName : deviceNames) { if (isFrontFacing(enumerator, deviceName)) { From 9c3f55bef220351e5eff5790b38ccec93aa6c573 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 11 Nov 2021 16:52:18 +0100 Subject: [PATCH 106/145] use stopwatch to keep track of jingle rtp session duration --- .../conversations/ui/RtpSessionActivity.java | 11 ++--- .../conversations/utils/TimeFrameUtils.java | 12 ++++-- .../xmpp/jingle/JingleRtpConnection.java | 40 ++++++++++--------- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index b2cf583b51af92928f5afac556869ddaf4343f56..d0bdbb78881b9964d99ae1f03b941acb19b48ba0 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -939,14 +939,11 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe this.binding.duration.setVisibility(View.GONE); return; } - final long rtpConnectionStarted = connection.getRtpConnectionStarted(); - final long rtpConnectionEnded = connection.getRtpConnectionEnded(); - if (rtpConnectionStarted != 0) { - final long ended = rtpConnectionEnded == 0 ? SystemClock.elapsedRealtime() : rtpConnectionEnded; - this.binding.duration.setText(TimeFrameUtils.formatTimePassed(rtpConnectionStarted, ended, false)); - this.binding.duration.setVisibility(View.VISIBLE); - } else { + if (connection.zeroDuration()) { this.binding.duration.setVisibility(View.GONE); + } else { + this.binding.duration.setText(TimeFrameUtils.formatElapsedTime(connection.getCallDuration(), false)); + this.binding.duration.setVisibility(View.VISIBLE); } } diff --git a/src/main/java/eu/siacs/conversations/utils/TimeFrameUtils.java b/src/main/java/eu/siacs/conversations/utils/TimeFrameUtils.java index 1cb78db0cfe7b01ebf5174dfbf1bb5b595b70e69..9e7946d570b459cf465a5265f8f2b297c143b4eb 100644 --- a/src/main/java/eu/siacs/conversations/utils/TimeFrameUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/TimeFrameUtils.java @@ -71,10 +71,14 @@ public class TimeFrameUtils { public static String formatTimePassed(final long since, final long to, final boolean withMilliseconds) { final long passed = (since < 0) ? 0 : (to - since); - final int hours = (int) (passed / 3600000); - final int minutes = (int) (passed / 60000) % 60; - final int seconds = (int) (passed / 1000) % 60; - final int milliseconds = (int) (passed / 100) % 10; + return formatElapsedTime(passed, withMilliseconds); + } + + public static String formatElapsedTime(final long elapsed, final boolean withMilliseconds) { + final int hours = (int) (elapsed / 3600000); + final int minutes = (int) (elapsed / 60000) % 60; + final int seconds = (int) (elapsed / 1000) % 60; + final int milliseconds = (int) (elapsed / 100) % 10; if (hours > 0) { return String.format(Locale.ENGLISH, "%d:%02d:%02d", hours, minutes, seconds); } else if (withMilliseconds) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 2d322ead9ec08290d95f8b08a65472cf6cf49ce2..ed1f35dd2a918c3203ddc1ba808435e64b4b0615 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.xmpp.jingle; -import android.os.SystemClock; import android.util.Log; import androidx.annotation.NonNull; @@ -8,6 +7,7 @@ import androidx.annotation.Nullable; import com.google.common.base.Optional; import com.google.common.base.Preconditions; +import com.google.common.base.Stopwatch; import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.collect.Collections2; @@ -29,8 +29,10 @@ import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -147,8 +149,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private Set proposedMedia; private RtpContentMap initiatorRtpContentMap; private RtpContentMap responderRtpContentMap; - private long rtpConnectionStarted = 0; //time of 'connected' - private long rtpConnectionEnded = 0; + private final Stopwatch sessionDuration = Stopwatch.createUnstarted(); + private final Queue stateHistory = new LinkedList<>(); private ScheduledFuture ringingTimeoutFuture; JingleRtpConnection(JingleConnectionManager jingleConnectionManager, Id id, Jid initiator) { @@ -1056,7 +1058,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web return RtpEndUserState.RETRACTED; } case TERMINATED_CONNECTIVITY_ERROR: - return rtpConnectionStarted == 0 ? RtpEndUserState.CONNECTIVITY_ERROR : RtpEndUserState.CONNECTIVITY_LOST_ERROR; + return zeroDuration() ? RtpEndUserState.CONNECTIVITY_ERROR : RtpEndUserState.CONNECTIVITY_LOST_ERROR; case TERMINATED_APPLICATION_FAILURE: return RtpEndUserState.APPLICATION_ERROR; case TERMINATED_SECURITY_ERROR: @@ -1084,7 +1086,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web case CLOSED: return RtpEndUserState.ENDING_CALL; default: - return rtpConnectionStarted == 0 ? RtpEndUserState.CONNECTIVITY_ERROR : RtpEndUserState.RECONNECTING; + return zeroDuration() ? RtpEndUserState.CONNECTIVITY_ERROR : RtpEndUserState.RECONNECTING; } } @@ -1338,15 +1340,18 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web @Override public void onConnectionChange(final PeerConnection.PeerConnectionState oldState, final PeerConnection.PeerConnectionState newState) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed: " + oldState + "->" + newState); - final boolean neverConnected = this.rtpConnectionStarted == 0; - if (newState == PeerConnection.PeerConnectionState.CONNECTED && this.rtpConnectionStarted == 0) { - this.rtpConnectionStarted = SystemClock.elapsedRealtime(); - } - if (newState == PeerConnection.PeerConnectionState.CLOSED && this.rtpConnectionEnded == 0) { - this.rtpConnectionEnded = SystemClock.elapsedRealtime(); + this.stateHistory.add(newState); + if (newState == PeerConnection.PeerConnectionState.CONNECTED) { + this.sessionDuration.start(); + } else if (this.sessionDuration.isRunning()) { + this.sessionDuration.stop(); } - if (neverConnected && Arrays.asList(PeerConnection.PeerConnectionState.FAILED, PeerConnection.PeerConnectionState.DISCONNECTED).contains(newState)) { + final boolean neverConnected = !this.stateHistory.contains(PeerConnection.PeerConnectionState.CONNECTED); + final boolean failedOrDisconnected = Arrays.asList(PeerConnection.PeerConnectionState.FAILED, PeerConnection.PeerConnectionState.DISCONNECTED).contains(newState); + + + if (neverConnected && failedOrDisconnected) { if (isTerminated()) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not sending session-terminate after connectivity error because session is already in state " + this.state); return; @@ -1375,12 +1380,12 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } } - public long getRtpConnectionStarted() { - return this.rtpConnectionStarted; + public boolean zeroDuration() { + return this.sessionDuration.elapsed(TimeUnit.NANOSECONDS) <= 0; } - public long getRtpConnectionEnded() { - return this.rtpConnectionEnded; + public long getCallDuration() { + return this.sessionDuration.elapsed(TimeUnit.MILLISECONDS); } public AppRTCAudioManager getAudioManager() { @@ -1507,8 +1512,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private void writeLogMessage(final State state) { - final long started = this.rtpConnectionStarted; - long duration = started <= 0 ? 0 : SystemClock.elapsedRealtime() - started; + final long duration = getCallDuration(); if (state == State.TERMINATED_SUCCESS || (state == State.TERMINATED_CONNECTIVITY_ERROR && duration > 0)) { writeLogMessageSuccess(duration); } else { From b6dee6da6a16a383ca1cac097719e5aebafd77b5 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 11 Nov 2021 17:05:32 +0100 Subject: [PATCH 107/145] reverse: webrtc: include oldState in onConnectionChange MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit turns out we don’t need it and a better way is for RtpConnection to keep track of *all* states in the current generation --- .../conversations/xmpp/jingle/JingleRtpConnection.java | 4 ++-- .../siacs/conversations/xmpp/jingle/WebRTCWrapper.java | 10 +++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index ed1f35dd2a918c3203ddc1ba808435e64b4b0615..37c51bfdc4ef80155753085dab21c6284f959d2a 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1338,8 +1338,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } @Override - public void onConnectionChange(final PeerConnection.PeerConnectionState oldState, final PeerConnection.PeerConnectionState newState) { - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed: " + oldState + "->" + newState); + public void onConnectionChange(final PeerConnection.PeerConnectionState newState) { + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed to" + newState); this.stateHistory.add(newState); if (newState == PeerConnection.PeerConnectionState.CONNECTED) { this.sessionDuration.start(); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index c1201206ad604d803c328ae2f335ff2788b3796c..5ee6c5d5e94812acd2f852589df7d8bad6bc18d1 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -88,7 +88,6 @@ public class WebRTCWrapper { private final Handler mainHandler = new Handler(Looper.getMainLooper()); private VideoTrack localVideoTrack = null; private VideoTrack remoteVideoTrack = null; - private PeerConnection.PeerConnectionState currentState; private final PeerConnection.Observer peerConnectionObserver = new PeerConnection.Observer() { @Override public void onSignalingChange(PeerConnection.SignalingState signalingState) { @@ -100,9 +99,7 @@ public class WebRTCWrapper { @Override public void onConnectionChange(final PeerConnection.PeerConnectionState newState) { - final PeerConnection.PeerConnectionState oldState = currentState; - currentState = newState; - eventCallback.onConnectionChange(oldState, newState); + eventCallback.onConnectionChange(newState); } @Override @@ -154,6 +151,7 @@ public class WebRTCWrapper { @Override public void onRenegotiationNeeded() { Log.d(EXTENDED_LOGGING_TAG, "onRenegotiationNeeded()"); + final PeerConnection.PeerConnectionState currentState = peerConnection == null ? null : peerConnection.connectionState(); if (currentState != null && currentState != PeerConnection.PeerConnectionState.NEW) { eventCallback.onRenegotiationNeeded(); } @@ -267,8 +265,6 @@ public class WebRTCWrapper { throw new InitializationException("Unable to create PeerConnection"); } - this.currentState = peerConnection.connectionState(); - final Optional optionalCapturerChoice = media.contains(Media.VIDEO) ? getVideoCapturer() : Optional.absent(); if (optionalCapturerChoice.isPresent()) { @@ -535,7 +531,7 @@ public class WebRTCWrapper { public interface EventCallback { void onIceCandidate(IceCandidate iceCandidate); - void onConnectionChange(PeerConnection.PeerConnectionState oldState, PeerConnection.PeerConnectionState newState); + void onConnectionChange(PeerConnection.PeerConnectionState newState); void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set availableAudioDevices); From 717c83753f24ffbccabe17c23100268f9146040a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 11 Nov 2021 21:02:15 +0100 Subject: [PATCH 108/145] delay discovered ice candidates until JingleRtpConnection is ready to receive otherwise setLocalDescription and the arrival of first candidates might race the rtpContentDescription being set --- .../xmpp/jingle/JingleRtpConnection.java | 39 +++++++++++++++++-- .../xmpp/jingle/WebRTCWrapper.java | 28 ++++++++++++- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 37c51bfdc4ef80155753085dab21c6284f959d2a..4796d0c585d835dd3848a1bfd24ca53a3afb989b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -34,6 +34,7 @@ import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -567,6 +568,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription); this.responderRtpContentMap = respondingRtpContentMap; + webRTCWrapper.setIsReadyToReceiveIceCandidates(true); final ListenableFuture outgoingContentMapFuture = prepareOutgoingContentMap(respondingRtpContentMap); Futures.addCallback(outgoingContentMapFuture, new FutureCallback() { @@ -872,6 +874,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription); this.initiatorRtpContentMap = rtpContentMap; + this.webRTCWrapper.setIsReadyToReceiveIceCandidates(true); final ListenableFuture outgoingContentMapFuture = encryptSessionInitiate(rtpContentMap); Futures.addCallback(outgoingContentMapFuture, new FutureCallback() { @Override @@ -1339,7 +1342,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web @Override public void onConnectionChange(final PeerConnection.PeerConnectionState newState) { - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed to" + newState); + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed to " + newState); this.stateHistory.add(newState); if (newState == PeerConnection.PeerConnectionState.CONNECTED) { this.sessionDuration.start(); @@ -1348,7 +1351,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } final boolean neverConnected = !this.stateHistory.contains(PeerConnection.PeerConnectionState.CONNECTED); - final boolean failedOrDisconnected = Arrays.asList(PeerConnection.PeerConnectionState.FAILED, PeerConnection.PeerConnectionState.DISCONNECTED).contains(newState); + final boolean failedOrDisconnected = Arrays.asList( + PeerConnection.PeerConnectionState.FAILED, + PeerConnection.PeerConnectionState.DISCONNECTED + ).contains(newState); if (neverConnected && failedOrDisconnected) { @@ -1356,7 +1362,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not sending session-terminate after connectivity error because session is already in state " + this.state); return; } - new Thread(this::closeWebRTCSessionAfterFailedConnection).start(); + webRTCWrapper.execute(this::closeWebRTCSessionAfterFailedConnection); } else if (newState == PeerConnection.PeerConnectionState.FAILED) { Log.d(Config.LOGTAG, "attempting to restart ICE"); webRTCWrapper.restartIce(); @@ -1367,6 +1373,33 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web @Override public void onRenegotiationNeeded() { Log.d(Config.LOGTAG, "onRenegotiationNeeded()"); + this.webRTCWrapper.execute(this::renegotiate); + } + + private void renegotiate() { + this.webRTCWrapper.setIsReadyToReceiveIceCandidates(false); + try { + final SessionDescription sessionDescription = setLocalSessionDescription(); + final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription); + setRenegotiatedContentMap(rtpContentMap); + this.webRTCWrapper.setIsReadyToReceiveIceCandidates(true); + } catch (final Exception e) { + Log.d(Config.LOGTAG, "failed to renegotiate", e); + //TODO send some sort of failure (comparable to when initiating) + } + } + + private void setRenegotiatedContentMap(final RtpContentMap rtpContentMap) { + if (isInitiator()) { + this.initiatorRtpContentMap = rtpContentMap; + } else { + this.responderRtpContentMap = rtpContentMap; + } + } + + private SessionDescription setLocalSessionDescription() throws ExecutionException, InterruptedException { + final org.webrtc.SessionDescription sessionDescription = this.webRTCWrapper.setLocalDescription().get(); + return SessionDescription.parse(sessionDescription.description); } private void closeWebRTCSessionAfterFailedConnection() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 5ee6c5d5e94812acd2f852589df7d8bad6bc18d1..9ea4cd3897fb446c89446246e1b3720070fb74e7 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -45,8 +45,13 @@ import org.webrtc.voiceengine.WebRtcAudioEffects; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedList; import java.util.List; +import java.util.Queue; import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -59,6 +64,8 @@ public class WebRTCWrapper { private static final String EXTENDED_LOGGING_TAG = WebRTCWrapper.class.getSimpleName(); + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + //we should probably keep this in sync with: https://github.com/signalapp/Signal-Android/blob/master/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java#L296 private static final Set HARDWARE_AEC_BLACKLIST = new ImmutableSet.Builder() .add("Pixel") @@ -79,6 +86,8 @@ public class WebRTCWrapper { private static final int CAPTURING_MAX_FRAME_RATE = 30; private final EventCallback eventCallback; + private final AtomicBoolean readyToReceivedIceCandidates = new AtomicBoolean(false); + private final Queue iceCandidates = new LinkedList<>(); private final AppRTCAudioManager.AudioManagerEvents audioManagerEvents = new AppRTCAudioManager.AudioManagerEvents() { @Override public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set availableAudioDevices) { @@ -125,7 +134,11 @@ public class WebRTCWrapper { @Override public void onIceCandidate(IceCandidate iceCandidate) { - eventCallback.onIceCandidate(iceCandidate); + if (readyToReceivedIceCandidates.get()) { + eventCallback.onIceCandidate(iceCandidate); + } else { + iceCandidates.add(iceCandidate); + } } @Override @@ -294,7 +307,14 @@ public class WebRTCWrapper { } void restartIce() { - requirePeerConnection().restartIce(); + executorService.execute(()-> requirePeerConnection().restartIce()); + } + + public void setIsReadyToReceiveIceCandidates(final boolean ready) { + readyToReceivedIceCandidates.set(ready); + while(ready && iceCandidates.peek() != null) { + eventCallback.onIceCandidate(iceCandidates.poll()); + } } synchronized void close() { @@ -528,6 +548,10 @@ public class WebRTCWrapper { return appRTCAudioManager; } + void execute(final Runnable command) { + executorService.execute(command); + } + public interface EventCallback { void onIceCandidate(IceCandidate iceCandidate); From 5b80c62a637fabea89ceafb39dc9353bc50773d6 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Nov 2021 18:22:18 +0100 Subject: [PATCH 109/145] treat transport-info w/o candidates and changed credentials as offer --- .../eu/siacs/conversations/xml/Element.java | 5 +- .../xmpp/jingle/JingleRtpConnection.java | 193 +++++++++++++----- .../xmpp/jingle/RtpContentMap.java | 34 ++- .../xmpp/jingle/WebRTCWrapper.java | 34 ++- .../jingle/stanzas/IceUdpTransportInfo.java | 45 +++- 5 files changed, 254 insertions(+), 57 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index c0ece7f4cecd3634c59812ee7e3d9c343e49196e..4d53a17b723f3b179bf8446d82829b7ebc0b3348 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.xml; +import org.jetbrains.annotations.NotNull; + import java.util.ArrayList; import java.util.Hashtable; import java.util.List; @@ -165,8 +167,9 @@ public class Element { return this.attributes; } + @NotNull public String toString() { - StringBuilder elementOutput = new StringBuilder(); + final StringBuilder elementOutput = new StringBuilder(); if ((content == null) && (children.size() == 0)) { Tag emptyTag = Tag.empty(name); emptyTag.setAtttributes(this.attributes); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 4796d0c585d835dd3848a1bfd24ca53a3afb989b..71cdb02c4aecd66365bf7b85035a932e7a004ba1 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -25,7 +25,6 @@ import org.webrtc.IceCandidate; import org.webrtc.PeerConnection; import org.webrtc.VideoTrack; -import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -142,7 +141,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private final WebRTCWrapper webRTCWrapper = new WebRTCWrapper(this); - private final ArrayDeque>> pendingIceCandidates = new ArrayDeque<>(); + //TODO convert to Queue>? + private final Queue> pendingIceCandidates = new LinkedList<>(); private final OmemoVerification omemoVerification = new OmemoVerification(); private final Message message; private State state = State.NULL; @@ -193,7 +193,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web @Override synchronized void deliverPacket(final JinglePacket jinglePacket) { - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": packet delivered to JingleRtpConnection"); switch (jinglePacket.getAction()) { case SESSION_INITIATE: receiveSessionInitiate(jinglePacket); @@ -254,23 +253,29 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private void receiveTransportInfo(final JinglePacket jinglePacket) { //Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to INITIALIZED only after transport-info has been received if (isInState(State.NULL, State.PROCEED, State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) { - respondOk(jinglePacket); final RtpContentMap contentMap; try { contentMap = RtpContentMap.of(jinglePacket); - } catch (IllegalArgumentException | NullPointerException e) { + } catch (final IllegalArgumentException | NullPointerException e) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents; ignoring", e); + respondOk(jinglePacket); return; } final Set> candidates = contentMap.contents.entrySet(); if (this.state == State.SESSION_ACCEPTED) { + //zero candidates + modified credentials are an ICE restart offer + if (checkForIceRestart(contentMap, jinglePacket)) { + return; + } + respondOk(jinglePacket); try { processCandidates(candidates); } catch (final WebRTCWrapper.PeerConnectionNotInitialized e) { Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnection was not initialized when processing transport info. this usually indicates a race condition that can be ignored"); } } else { - pendingIceCandidates.push(candidates); + respondOk(jinglePacket); + pendingIceCandidates.addAll(candidates); } } else { if (isTerminated()) { @@ -283,37 +288,106 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } } - private void processCandidates(final Set> contents) { - final RtpContentMap rtpContentMap = isInitiator() ? this.responderRtpContentMap : this.initiatorRtpContentMap; - final Group originalGroup = rtpContentMap.group; - final List identificationTags = originalGroup == null ? rtpContentMap.getNames() : originalGroup.getIdentificationTags(); - if (identificationTags.size() == 0) { - Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": no identification tags found in initial offer. we won't be able to calculate mLineIndices"); + private boolean checkForIceRestart(final RtpContentMap rtpContentMap, final JinglePacket jinglePacket) { + final RtpContentMap existing = getRemoteContentMap(); + final Map existingCredentials = existing.getCredentials(); + final Map newCredentials = rtpContentMap.getCredentials(); + if (!existingCredentials.keySet().equals(newCredentials.keySet())) { + return false; + } + if (existingCredentials.equals(newCredentials)) { + return false; + } + final boolean isOffer = rtpContentMap.emptyCandidates(); + Log.d(Config.LOGTAG, "detected ICE restart. offer=" + isOffer + " " + jinglePacket); + //TODO reset to 'actpass'? + final RtpContentMap restartContentMap = existing.modifiedCredentials(newCredentials); + try { + if (applyIceRestart(isOffer, restartContentMap)) { + return false; + } else { + Log.d(Config.LOGTAG, "responding with tie break"); + //TODO respond with conflict + return true; + } + } catch (Exception e) { + Log.d(Config.LOGTAG, "failure to apply ICE restart. sending error", e); + //TODO send some kind of error + return true; } - processCandidates(identificationTags, contents); } - private void processCandidates(final List indices, final Set> contents) { + private boolean applyIceRestart(final boolean isOffer, final RtpContentMap restartContentMap) throws ExecutionException, InterruptedException { + final SessionDescription sessionDescription = SessionDescription.of(restartContentMap); + final org.webrtc.SessionDescription.Type type = isOffer ? org.webrtc.SessionDescription.Type.OFFER : org.webrtc.SessionDescription.Type.ANSWER; + org.webrtc.SessionDescription sdp = new org.webrtc.SessionDescription(type, sessionDescription.toString()); + if (isOffer && webRTCWrapper.getSignalingState() != PeerConnection.SignalingState.STABLE) { + if (isInitiator()) { + //We ignore the offer and respond with tie-break. This will clause the responder not to apply the content map + return false; + } + //rollback our own local description. should happen automatically but doesn't + webRTCWrapper.rollbackLocalDescription().get(); + } + webRTCWrapper.setRemoteDescription(sdp).get(); + if (isInitiator()) { + this.responderRtpContentMap = restartContentMap; + } else { + this.initiatorRtpContentMap = restartContentMap; + } + if (isOffer) { + webRTCWrapper.setIsReadyToReceiveIceCandidates(false); + final SessionDescription localSessionDescription = setLocalSessionDescription(); + setLocalContentMap(RtpContentMap.of(localSessionDescription)); + webRTCWrapper.setIsReadyToReceiveIceCandidates(true); + } + return true; + } + + private void processCandidates(final Set> contents) { for (final Map.Entry content : contents) { - final String ufrag = content.getValue().transport.getAttribute("ufrag"); - for (final IceUdpTransportInfo.Candidate candidate : content.getValue().transport.getCandidates()) { - final String sdp; - try { - sdp = candidate.toSdpAttribute(ufrag); - } catch (IllegalArgumentException e) { - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring invalid ICE candidate " + e.getMessage()); - continue; - } - final String sdpMid = content.getKey(); - final int mLineIndex = indices.indexOf(sdpMid); - if (mLineIndex < 0) { - Log.w(Config.LOGTAG, "mLineIndex not found for " + sdpMid + ". available indices " + indices); - } - final IceCandidate iceCandidate = new IceCandidate(sdpMid, mLineIndex, sdp); - Log.d(Config.LOGTAG, "received candidate: " + iceCandidate); - this.webRTCWrapper.addIceCandidate(iceCandidate); + processCandidate(content); + } + } + + private void processCandidate(final Map.Entry content) { + final RtpContentMap rtpContentMap = getRemoteContentMap(); + final List indices = toIdentificationTags(rtpContentMap); + final String sdpMid = content.getKey(); //aka content name + final IceUdpTransportInfo transport = content.getValue().transport; + final IceUdpTransportInfo.Credentials credentials = transport.getCredentials(); + + //TODO check that credentials remained the same + + for (final IceUdpTransportInfo.Candidate candidate : transport.getCandidates()) { + final String sdp; + try { + sdp = candidate.toSdpAttribute(credentials.ufrag); + } catch (final IllegalArgumentException e) { + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring invalid ICE candidate " + e.getMessage()); + continue; } + final int mLineIndex = indices.indexOf(sdpMid); + if (mLineIndex < 0) { + Log.w(Config.LOGTAG, "mLineIndex not found for " + sdpMid + ". available indices " + indices); + } + final IceCandidate iceCandidate = new IceCandidate(sdpMid, mLineIndex, sdp); + Log.d(Config.LOGTAG, "received candidate: " + iceCandidate); + this.webRTCWrapper.addIceCandidate(iceCandidate); + } + } + + private RtpContentMap getRemoteContentMap() { + return isInitiator() ? this.responderRtpContentMap : this.initiatorRtpContentMap; + } + + private List toIdentificationTags(final RtpContentMap rtpContentMap) { + final Group originalGroup = rtpContentMap.group; + final List identificationTags = originalGroup == null ? rtpContentMap.getNames() : originalGroup.getIdentificationTags(); + if (identificationTags.size() == 0) { + Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": no identification tags found in initial offer. we won't be able to calculate mLineIndices"); } + return identificationTags; } private ListenableFuture receiveRtpContentMap(final JinglePacket jinglePacket, final boolean expectVerification) { @@ -401,11 +475,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } if (transition(target, () -> this.initiatorRtpContentMap = contentMap)) { respondOk(jinglePacket); - - final Set> candidates = contentMap.contents.entrySet(); - if (candidates.size() > 0) { - pendingIceCandidates.push(candidates); - } + pendingIceCandidates.addAll(contentMap.contents.entrySet()); if (target == State.SESSION_INITIALIZED_PRE_APPROVED) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": automatically accepting session-initiate"); sendSessionAccept(); @@ -495,8 +565,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web sendSessionTerminate(Reason.FAILED_APPLICATION); return; } - final List identificationTags = contentMap.group == null ? contentMap.getNames() : contentMap.group.getIdentificationTags(); - processCandidates(identificationTags, contentMap.contents.entrySet()); + processCandidates(contentMap.contents.entrySet()); } private void sendSessionAccept() { @@ -558,9 +627,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private void addIceCandidatesFromBlackLog() { - while (!this.pendingIceCandidates.isEmpty()) { - processCandidates(this.pendingIceCandidates.poll()); - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": added candidates from back log"); + Map.Entry foo; + while ((foo = this.pendingIceCandidates.poll()) != null) { + processCandidate(foo); + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": added candidate from back log"); } } @@ -1335,7 +1405,13 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web @Override public void onIceCandidate(final IceCandidate iceCandidate) { - final IceUdpTransportInfo.Candidate candidate = IceUdpTransportInfo.Candidate.fromSdpAttribute(iceCandidate.sdp); + final RtpContentMap rtpContentMap = isInitiator() ? this.initiatorRtpContentMap : this.responderRtpContentMap; + final Collection currentUfrags = Collections2.transform(rtpContentMap.getCredentials().values(), c -> c.ufrag); + final IceUdpTransportInfo.Candidate candidate = IceUdpTransportInfo.Candidate.fromSdpAttribute(iceCandidate.sdp, currentUfrags); + if (candidate == null) { + Log.d(Config.LOGTAG,"ignoring (not sending) candidate: "+iceCandidate.toString()); + return; + } Log.d(Config.LOGTAG, "sending candidate: " + iceCandidate.toString()); sendTransportInfo(iceCandidate.sdpMid, candidate); } @@ -1373,23 +1449,42 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web @Override public void onRenegotiationNeeded() { Log.d(Config.LOGTAG, "onRenegotiationNeeded()"); - this.webRTCWrapper.execute(this::renegotiate); + this.webRTCWrapper.execute(this::initiateIceRestart); } - private void renegotiate() { + private void initiateIceRestart() { + PeerConnection.SignalingState signalingState = webRTCWrapper.getSignalingState(); + Log.d(Config.LOGTAG, "initiateIceRestart() - " + signalingState); + if (signalingState != PeerConnection.SignalingState.STABLE) { + return; + } this.webRTCWrapper.setIsReadyToReceiveIceCandidates(false); + final SessionDescription sessionDescription; try { - final SessionDescription sessionDescription = setLocalSessionDescription(); - final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription); - setRenegotiatedContentMap(rtpContentMap); - this.webRTCWrapper.setIsReadyToReceiveIceCandidates(true); + sessionDescription = setLocalSessionDescription(); } catch (final Exception e) { Log.d(Config.LOGTAG, "failed to renegotiate", e); //TODO send some sort of failure (comparable to when initiating) + return; } + final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription); + final RtpContentMap transportInfo = rtpContentMap.transportInfo(); + final JinglePacket jinglePacket = transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); + Log.d(Config.LOGTAG, "initiating ice restart: " + jinglePacket); + jinglePacket.setTo(id.with); + xmppConnectionService.sendIqPacket(id.account, jinglePacket, (account, response) -> { + if (response.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, "received success to our ice restart"); + setLocalContentMap(rtpContentMap); + webRTCWrapper.setIsReadyToReceiveIceCandidates(true); + } else { + Log.d(Config.LOGTAG, "received failure to our ice restart"); + //TODO handle tie-break. Rollback? + } + }); } - private void setRenegotiatedContentMap(final RtpContentMap rtpContentMap) { + private void setLocalContentMap(final RtpContentMap rtpContentMap) { if (isInitiator()) { this.initiatorRtpContentMap = rtpContentMap; } else { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java index 9baffcf81b2ac01fe8883eb91e54a951c1fc4ff6..99db8bd344ea1e5ad4d3a445ac85c8fec24b556c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java @@ -1,7 +1,5 @@ package eu.siacs.conversations.xmpp.jingle; -import android.util.Log; - import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Strings; @@ -17,9 +15,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableDecl; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; -import eu.siacs.conversations.Config; import eu.siacs.conversations.xmpp.jingle.stanzas.Content; import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo; @@ -137,7 +135,37 @@ public class RtpContentMap { final IceUdpTransportInfo newTransportInfo = transportInfo.cloneWrapper(); newTransportInfo.addChild(candidate); return new RtpContentMap(null, ImmutableMap.of(contentName, new DescriptionTransport(null, newTransportInfo))); + } + + RtpContentMap transportInfo() { + return new RtpContentMap( + null, + Maps.transformValues(contents, dt -> new DescriptionTransport(null, dt.transport.cloneWrapper())) + ); + } + + public Map getCredentials() { + return Maps.transformValues(contents, dt -> dt.transport.getCredentials()); + } + public boolean emptyCandidates() { + int count = 0; + for (DescriptionTransport descriptionTransport : contents.values()) { + count += descriptionTransport.transport.getCandidates().size(); + } + return count == 0; + } + + public RtpContentMap modifiedCredentials(Map credentialsMap) { + final ImmutableMap.Builder contentMapBuilder = new ImmutableMap.Builder<>(); + for (final Map.Entry content : contents.entrySet()) { + final RtpDescription rtpDescription = content.getValue().description; + IceUdpTransportInfo transportInfo = content.getValue().transport; + final IceUdpTransportInfo.Credentials credentials = Objects.requireNonNull(credentialsMap.get(content.getKey())); + final IceUdpTransportInfo modifiedTransportInfo = transportInfo.modifyCredentials(credentials); + contentMapBuilder.put(content.getKey(), new DescriptionTransport(rtpDescription, modifiedTransportInfo)); + } + return new RtpContentMap(this.group, contentMapBuilder.build()); } public static class DescriptionTransport { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 9ea4cd3897fb446c89446246e1b3720070fb74e7..401121b86ee33cdf63251ffa3cbe1db771c05073 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -17,7 +17,6 @@ import com.google.common.util.concurrent.SettableFuture; import org.webrtc.AudioSource; import org.webrtc.AudioTrack; -import org.webrtc.Camera1Enumerator; import org.webrtc.Camera2Enumerator; import org.webrtc.CameraEnumerationAndroid; import org.webrtc.CameraEnumerator; @@ -87,6 +86,7 @@ public class WebRTCWrapper { private final EventCallback eventCallback; private final AtomicBoolean readyToReceivedIceCandidates = new AtomicBoolean(false); + private final AtomicBoolean ignoreOnRenegotiationNeeded = new AtomicBoolean(false); private final Queue iceCandidates = new LinkedList<>(); private final AppRTCAudioManager.AudioManagerEvents audioManagerEvents = new AppRTCAudioManager.AudioManagerEvents() { @Override @@ -163,6 +163,10 @@ public class WebRTCWrapper { @Override public void onRenegotiationNeeded() { + if (ignoreOnRenegotiationNeeded.get()) { + Log.d(EXTENDED_LOGGING_TAG, "ignoring onRenegotiationNeeded()"); + return; + } Log.d(EXTENDED_LOGGING_TAG, "onRenegotiationNeeded()"); final PeerConnection.PeerConnectionState currentState = peerConnection == null ? null : peerConnection.connectionState(); if (currentState != null && currentState != PeerConnection.PeerConnectionState.NEW) { @@ -307,12 +311,12 @@ public class WebRTCWrapper { } void restartIce() { - executorService.execute(()-> requirePeerConnection().restartIce()); + executorService.execute(() -> requirePeerConnection().restartIce()); } public void setIsReadyToReceiveIceCandidates(final boolean ready) { readyToReceivedIceCandidates.set(ready); - while(ready && iceCandidates.peek() != null) { + while (ready && iceCandidates.peek() != null) { eventCallback.onIceCandidate(iceCandidates.poll()); } } @@ -452,6 +456,26 @@ public class WebRTCWrapper { }, MoreExecutors.directExecutor()); } + public ListenableFuture rollbackLocalDescription() { + final SettableFuture future = SettableFuture.create(); + final SessionDescription rollback = new SessionDescription(SessionDescription.Type.ROLLBACK, ""); + ignoreOnRenegotiationNeeded.set(true); + requirePeerConnection().setLocalDescription(new SetSdpObserver() { + @Override + public void onSetSuccess() { + future.set(null); + ignoreOnRenegotiationNeeded.set(false); + } + + @Override + public void onSetFailure(final String message) { + future.setException(new FailureToSetDescriptionException(message)); + } + }, rollback); + return future; + } + + private static void logDescription(final SessionDescription sessionDescription) { for (final String line : sessionDescription.description.split(eu.siacs.conversations.xmpp.jingle.SessionDescription.LINE_DIVIDER)) { Log.d(EXTENDED_LOGGING_TAG, line); @@ -552,6 +576,10 @@ public class WebRTCWrapper { executorService.execute(command); } + public PeerConnection.SignalingState getSignalingState() { + return requirePeerConnection().signalingState(); + } + public interface EventCallback { void onIceCandidate(IceCandidate iceCandidate); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java index 022c4d2dd309c24e7c3216265a0956fc402dd535..2b8770578dba46c6bd92b80f8a2a6768ffea4203 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java @@ -1,6 +1,7 @@ package eu.siacs.conversations.xmpp.jingle.stanzas; import com.google.common.base.Joiner; +import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ArrayListMultimap; @@ -8,6 +9,7 @@ import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; import java.util.LinkedHashMap; @@ -58,6 +60,12 @@ public class IceUdpTransportInfo extends GenericTransportInfo { return fingerprint == null ? null : Fingerprint.upgrade(fingerprint); } + public Credentials getCredentials() { + final String ufrag = this.getAttribute("ufrag"); + final String password = this.getAttribute("pwd"); + return new Credentials(ufrag, password); + } + public List getCandidates() { final ImmutableList.Builder builder = new ImmutableList.Builder<>(); for (final Element child : getChildren()) { @@ -74,6 +82,37 @@ public class IceUdpTransportInfo extends GenericTransportInfo { return transportInfo; } + public IceUdpTransportInfo modifyCredentials(Credentials credentials) { + final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo(); + transportInfo.setAttribute("ufrag", credentials.ufrag); + transportInfo.setAttribute("pwd", credentials.password); + transportInfo.setChildren(getChildren()); + return transportInfo; + } + + public static class Credentials { + public final String ufrag; + public final String password; + + public Credentials(String ufrag, String password) { + this.ufrag = ufrag; + this.password = password; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Credentials that = (Credentials) o; + return Objects.equal(ufrag, that.ufrag) && Objects.equal(password, that.password); + } + + @Override + public int hashCode() { + return Objects.hashCode(ufrag, password); + } + } + public static class Candidate extends Element { private Candidate() { @@ -89,7 +128,7 @@ public class IceUdpTransportInfo extends GenericTransportInfo { } // https://tools.ietf.org/html/draft-ietf-mmusic-ice-sip-sdp-39#section-5.1 - public static Candidate fromSdpAttribute(final String attribute) { + public static Candidate fromSdpAttribute(final String attribute, Collection currentUfrags) { final String[] pair = attribute.split(":", 2); if (pair.length == 2 && "candidate".equals(pair[0])) { final String[] segments = pair[1].split(" "); @@ -105,6 +144,10 @@ public class IceUdpTransportInfo extends GenericTransportInfo { for (int i = 6; i < segments.length - 1; i = i + 2) { additional.put(segments[i], segments[i + 1]); } + final String ufrag = additional.get("ufrag"); + if (ufrag != null && !currentUfrags.contains(ufrag)) { + return null; + } final Candidate candidate = new Candidate(); candidate.setAttribute("component", component); candidate.setAttribute("foundation", foundation); From 3f402b132b122f0018a9b27afe846d558ae7b40c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 15 Nov 2021 13:03:04 +0100 Subject: [PATCH 110/145] respond with tie-break to prevent ICE restart race --- .../xmpp/jingle/JingleRtpConnection.java | 80 +++++++++++-------- .../jingle/stanzas/IceUdpTransportInfo.java | 9 +++ 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 71cdb02c4aecd66365bf7b85035a932e7a004ba1..e6a34cd8ec7a8eb64b018d3399bf4ca723d63cfa 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -261,22 +261,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web respondOk(jinglePacket); return; } - final Set> candidates = contentMap.contents.entrySet(); - if (this.state == State.SESSION_ACCEPTED) { - //zero candidates + modified credentials are an ICE restart offer - if (checkForIceRestart(contentMap, jinglePacket)) { - return; - } - respondOk(jinglePacket); - try { - processCandidates(candidates); - } catch (final WebRTCWrapper.PeerConnectionNotInitialized e) { - Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnection was not initialized when processing transport info. this usually indicates a race condition that can be ignored"); - } - } else { - respondOk(jinglePacket); - pendingIceCandidates.addAll(candidates); - } + receiveTransportInfo(jinglePacket, contentMap); } else { if (isTerminated()) { respondOk(jinglePacket); @@ -288,7 +273,26 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } } - private boolean checkForIceRestart(final RtpContentMap rtpContentMap, final JinglePacket jinglePacket) { + private void receiveTransportInfo(final JinglePacket jinglePacket, final RtpContentMap contentMap) { + final Set> candidates = contentMap.contents.entrySet(); + if (this.state == State.SESSION_ACCEPTED) { + //zero candidates + modified credentials are an ICE restart offer + if (checkForIceRestart(jinglePacket, contentMap)) { + return; + } + respondOk(jinglePacket); + try { + processCandidates(candidates); + } catch (final WebRTCWrapper.PeerConnectionNotInitialized e) { + Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnection was not initialized when processing transport info. this usually indicates a race condition that can be ignored"); + } + } else { + respondOk(jinglePacket); + pendingIceCandidates.addAll(candidates); + } + } + + private boolean checkForIceRestart(final JinglePacket jinglePacket, final RtpContentMap rtpContentMap) { final RtpContentMap existing = getRemoteContentMap(); final Map existingCredentials = existing.getCredentials(); final Map newCredentials = rtpContentMap.getCredentials(); @@ -299,25 +303,30 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web return false; } final boolean isOffer = rtpContentMap.emptyCandidates(); - Log.d(Config.LOGTAG, "detected ICE restart. offer=" + isOffer + " " + jinglePacket); - //TODO reset to 'actpass'? + if (isOffer) { + Log.d(Config.LOGTAG, "received offer to restart ICE " + newCredentials); + } else { + Log.d(Config.LOGTAG, "received confirmation of ICE restart" + newCredentials); + } + //TODO rewrite setup attribute + //https://groups.google.com/g/discuss-webrtc/c/DfpIMwvUfeM + //https://datatracker.ietf.org/doc/html/draft-ietf-mmusic-dtls-sdp-15#section-5.5 final RtpContentMap restartContentMap = existing.modifiedCredentials(newCredentials); try { - if (applyIceRestart(isOffer, restartContentMap)) { - return false; + if (applyIceRestart(jinglePacket, restartContentMap, isOffer)) { + return isOffer; } else { - Log.d(Config.LOGTAG, "responding with tie break"); - //TODO respond with conflict + respondWithTieBreak(jinglePacket); return true; } } catch (Exception e) { Log.d(Config.LOGTAG, "failure to apply ICE restart. sending error", e); - //TODO send some kind of error + //TODO respond OK and then terminate session return true; } } - private boolean applyIceRestart(final boolean isOffer, final RtpContentMap restartContentMap) throws ExecutionException, InterruptedException { + private boolean applyIceRestart(final JinglePacket jinglePacket, final RtpContentMap restartContentMap, final boolean isOffer) throws ExecutionException, InterruptedException { final SessionDescription sessionDescription = SessionDescription.of(restartContentMap); final org.webrtc.SessionDescription.Type type = isOffer ? org.webrtc.SessionDescription.Type.OFFER : org.webrtc.SessionDescription.Type.ANSWER; org.webrtc.SessionDescription sdp = new org.webrtc.SessionDescription(type, sessionDescription.toString()); @@ -339,6 +348,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web webRTCWrapper.setIsReadyToReceiveIceCandidates(false); final SessionDescription localSessionDescription = setLocalSessionDescription(); setLocalContentMap(RtpContentMap.of(localSessionDescription)); + //We need to respond OK before sending any candidates + respondOk(jinglePacket); webRTCWrapper.setIsReadyToReceiveIceCandidates(true); } return true; @@ -447,6 +458,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private void receiveSessionInitiate(final JinglePacket jinglePacket, final RtpContentMap contentMap) { try { contentMap.requireContentDescriptions(); + //TODO require actpass contentMap.requireDTLSFingerprint(); } catch (final RuntimeException e) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents", Throwables.getRootCause(e)); @@ -1072,8 +1084,16 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web this.finish(); } + private void respondWithTieBreak(final JinglePacket jinglePacket) { + respondWithJingleError(jinglePacket, "tie-break", "conflict", "cancel"); + } + private void respondWithOutOfOrder(final JinglePacket jinglePacket) { - jingleConnectionManager.respondWithJingleError(id.account, jinglePacket, "out-of-order", "unexpected-request", "wait"); + respondWithJingleError(jinglePacket, "out-of-order", "unexpected-request", "wait"); + } + + void respondWithJingleError(final IqPacket original, String jingleCondition, String condition, String conditionType) { + jingleConnectionManager.respondWithJingleError(id.account, original, jingleCondition, condition, conditionType); } private void respondOk(final JinglePacket jinglePacket) { @@ -1409,7 +1429,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web final Collection currentUfrags = Collections2.transform(rtpContentMap.getCredentials().values(), c -> c.ufrag); final IceUdpTransportInfo.Candidate candidate = IceUdpTransportInfo.Candidate.fromSdpAttribute(iceCandidate.sdp, currentUfrags); if (candidate == null) { - Log.d(Config.LOGTAG,"ignoring (not sending) candidate: "+iceCandidate.toString()); + Log.d(Config.LOGTAG, "ignoring (not sending) candidate: " + iceCandidate.toString()); return; } Log.d(Config.LOGTAG, "sending candidate: " + iceCandidate.toString()); @@ -1448,16 +1468,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web @Override public void onRenegotiationNeeded() { - Log.d(Config.LOGTAG, "onRenegotiationNeeded()"); this.webRTCWrapper.execute(this::initiateIceRestart); } private void initiateIceRestart() { - PeerConnection.SignalingState signalingState = webRTCWrapper.getSignalingState(); - Log.d(Config.LOGTAG, "initiateIceRestart() - " + signalingState); - if (signalingState != PeerConnection.SignalingState.STABLE) { - return; - } this.webRTCWrapper.setIsReadyToReceiveIceCandidates(false); final SessionDescription sessionDescription; try { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java index 2b8770578dba46c6bd92b80f8a2a6768ffea4203..1586557b7b06c95009f7a6395d509f08a5f9ea3b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java @@ -1,6 +1,7 @@ package eu.siacs.conversations.xmpp.jingle.stanzas; import com.google.common.base.Joiner; +import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.base.Strings; @@ -111,6 +112,14 @@ public class IceUdpTransportInfo extends GenericTransportInfo { public int hashCode() { return Objects.hashCode(ufrag, password); } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("ufrag", ufrag) + .add("password", password) + .toString(); + } } public static class Candidate extends Element { From 0a3947b8e308fbeb04492542dba3e726d81a3104 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 15 Nov 2021 17:18:43 +0100 Subject: [PATCH 111/145] terminate with application failure when failing to apply ICE restart This is fairly unlikely to happen in practice --- .../xmpp/jingle/JingleRtpConnection.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index e6a34cd8ec7a8eb64b018d3399bf4ca723d63cfa..04c43ec1c96fdf7c9271ed40a06d2a5da8f2ce1e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -319,14 +319,17 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web respondWithTieBreak(jinglePacket); return true; } - } catch (Exception e) { - Log.d(Config.LOGTAG, "failure to apply ICE restart. sending error", e); - //TODO respond OK and then terminate session + } catch (final Exception exception) { + respondOk(jinglePacket); + final Throwable rootCause = Throwables.getRootCause(exception); + Log.d(Config.LOGTAG, "failure to apply ICE restart", rootCause); + webRTCWrapper.close(); + sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage()); return true; } } - private boolean applyIceRestart(final JinglePacket jinglePacket, final RtpContentMap restartContentMap, final boolean isOffer) throws ExecutionException, InterruptedException { + private boolean applyIceRestart(final JinglePacket jinglePacket, final RtpContentMap restartContentMap, final boolean isOffer) throws ExecutionException, InterruptedException { final SessionDescription sessionDescription = SessionDescription.of(restartContentMap); final org.webrtc.SessionDescription.Type type = isOffer ? org.webrtc.SessionDescription.Type.OFFER : org.webrtc.SessionDescription.Type.ANSWER; org.webrtc.SessionDescription sdp = new org.webrtc.SessionDescription(type, sessionDescription.toString()); @@ -574,7 +577,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } catch (final Exception e) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to set remote description after receiving session-accept", Throwables.getRootCause(e)); webRTCWrapper.close(); - sendSessionTerminate(Reason.FAILED_APPLICATION); + sendSessionTerminate(Reason.FAILED_APPLICATION, Throwables.getRootCause(e).getMessage()); return; } processCandidates(contentMap.contents.entrySet()); @@ -624,7 +627,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.setLocalDescription().get(); prepareSessionAccept(webRTCSessionDescription); } catch (final Exception e) { - //TODO sending the error text is worthwhile as well. Especially for FailureToSet exceptions failureToAcceptSession(e); } } @@ -633,9 +635,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web if (isTerminated()) { return; } - Log.d(Config.LOGTAG, "unable to send session accept", Throwables.getRootCause(throwable)); + final Throwable rootCause = Throwables.getRootCause(throwable); + Log.d(Config.LOGTAG, "unable to send session accept", rootCause); webRTCWrapper.close(); - sendSessionTerminate(Reason.ofThrowable(throwable)); + sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage()); } private void addIceCandidatesFromBlackLog() { From 70b5d8d81aa3059623570972a58b7c595c1e1f26 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 15 Nov 2021 21:49:31 +0100 Subject: [PATCH 112/145] set proper peer dtls setup on ice restart received --- .../xmpp/jingle/JingleRtpConnection.java | 31 +++++++++----- .../xmpp/jingle/RtpContentMap.java | 16 ++++++-- .../xmpp/jingle/SessionDescription.java | 5 ++- .../jingle/stanzas/IceUdpTransportInfo.java | 40 +++++++++++++++++-- 4 files changed, 74 insertions(+), 18 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 04c43ec1c96fdf7c9271ed40a06d2a5da8f2ce1e..c0ddfd96a3863f845176d6b38ffa8ee633bc83dd 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -303,16 +303,18 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web return false; } final boolean isOffer = rtpContentMap.emptyCandidates(); - if (isOffer) { - Log.d(Config.LOGTAG, "received offer to restart ICE " + newCredentials); - } else { - Log.d(Config.LOGTAG, "received confirmation of ICE restart" + newCredentials); - } - //TODO rewrite setup attribute - //https://groups.google.com/g/discuss-webrtc/c/DfpIMwvUfeM - //https://datatracker.ietf.org/doc/html/draft-ietf-mmusic-dtls-sdp-15#section-5.5 - final RtpContentMap restartContentMap = existing.modifiedCredentials(newCredentials); + final RtpContentMap restartContentMap; try { + if (isOffer) { + Log.d(Config.LOGTAG, "received offer to restart ICE " + newCredentials.values()); + restartContentMap = existing.modifiedCredentials(newCredentials, IceUdpTransportInfo.Setup.ACTPASS); + } else { + final IceUdpTransportInfo.Setup setup = getPeerDtlsSetup(); + Log.d(Config.LOGTAG, "received confirmation of ICE restart" + newCredentials.values()+" peer_setup="+setup); + // DTLS setup attribute needs to be rewritten to reflect current peer state + // https://groups.google.com/g/discuss-webrtc/c/DfpIMwvUfeM + restartContentMap = existing.modifiedCredentials(newCredentials, setup); + } if (applyIceRestart(jinglePacket, restartContentMap, isOffer)) { return isOffer; } else { @@ -329,6 +331,14 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } } + private IceUdpTransportInfo.Setup getPeerDtlsSetup() { + final IceUdpTransportInfo.Setup responderSetup = this.responderRtpContentMap.getDtlsSetup(); + if (responderSetup == null || responderSetup == IceUdpTransportInfo.Setup.ACTPASS) { + throw new IllegalStateException("Invalid DTLS setup value in responder content map"); + } + return isInitiator() ? responderSetup : responderSetup.flip(); + } + private boolean applyIceRestart(final JinglePacket jinglePacket, final RtpContentMap restartContentMap, final boolean isOffer) throws ExecutionException, InterruptedException { final SessionDescription sessionDescription = SessionDescription.of(restartContentMap); final org.webrtc.SessionDescription.Type type = isOffer ? org.webrtc.SessionDescription.Type.OFFER : org.webrtc.SessionDescription.Type.ANSWER; @@ -1496,7 +1506,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web webRTCWrapper.setIsReadyToReceiveIceCandidates(true); } else { Log.d(Config.LOGTAG, "received failure to our ice restart"); - //TODO handle tie-break. Rollback? + //TODO ignore tie break (maybe rollback?) + //TODO handle other errors } }); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java index 99db8bd344ea1e5ad4d3a445ac85c8fec24b556c..5366460ecd963d20a6c46625c81a073c27c233cf 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java @@ -6,6 +6,7 @@ import com.google.common.base.Strings; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -104,7 +105,8 @@ public class RtpContentMap { if (fingerprint == null || Strings.isNullOrEmpty(fingerprint.getContent()) || Strings.isNullOrEmpty(fingerprint.getHash())) { throw new SecurityException(String.format("Use of DTLS-SRTP (XEP-0320) is required for content %s", entry.getKey())); } - if (Strings.isNullOrEmpty(fingerprint.getSetup())) { + final IceUdpTransportInfo.Setup setup = fingerprint.getSetup(); + if (setup == null) { throw new SecurityException(String.format("Use of DTLS-SRTP (XEP-0320) is required for content %s but missing setup attribute", entry.getKey())); } } @@ -148,6 +150,14 @@ public class RtpContentMap { return Maps.transformValues(contents, dt -> dt.transport.getCredentials()); } + public IceUdpTransportInfo.Setup getDtlsSetup() { + final Set setups = ImmutableSet.copyOf(Collections2.transform( + contents.values(), + dt->dt.transport.getFingerprint().getSetup() + )); + return setups.size() == 1 ? Iterables.getFirst(setups, null) : null; + } + public boolean emptyCandidates() { int count = 0; for (DescriptionTransport descriptionTransport : contents.values()) { @@ -156,13 +166,13 @@ public class RtpContentMap { return count == 0; } - public RtpContentMap modifiedCredentials(Map credentialsMap) { + public RtpContentMap modifiedCredentials(Map credentialsMap, final IceUdpTransportInfo.Setup setup) { final ImmutableMap.Builder contentMapBuilder = new ImmutableMap.Builder<>(); for (final Map.Entry content : contents.entrySet()) { final RtpDescription rtpDescription = content.getValue().description; IceUdpTransportInfo transportInfo = content.getValue().transport; final IceUdpTransportInfo.Credentials credentials = Objects.requireNonNull(credentialsMap.get(content.getKey())); - final IceUdpTransportInfo modifiedTransportInfo = transportInfo.modifyCredentials(credentials); + final IceUdpTransportInfo modifiedTransportInfo = transportInfo.modifyCredentials(credentials, setup); contentMapBuilder.put(content.getKey(), new DescriptionTransport(rtpDescription, modifiedTransportInfo)); } return new RtpContentMap(this.group, contentMapBuilder.build()); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java index 39031c4a9c2b5bc183cc3b8e7320785488cc2a03..e113146b135ed5beabbd61cd49b1a91babca520b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java @@ -156,7 +156,10 @@ public class SessionDescription { final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint(); if (fingerprint != null) { mediaAttributes.put("fingerprint", fingerprint.getHash() + " " + fingerprint.getContent()); - mediaAttributes.put("setup", fingerprint.getSetup()); + final IceUdpTransportInfo.Setup setup = fingerprint.getSetup(); + if (setup != null) { + mediaAttributes.put("setup", setup.toString().toLowerCase(Locale.ROOT)); + } } final ImmutableList.Builder formatBuilder = new ImmutableList.Builder<>(); for (RtpDescription.PayloadType payloadType : description.getPayloadTypes()) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java index 1586557b7b06c95009f7a6395d509f08a5f9ea3b..9af1186fc3924ccc79526eb8305332f9c326d144 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java @@ -10,6 +10,7 @@ import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; @@ -83,11 +84,19 @@ public class IceUdpTransportInfo extends GenericTransportInfo { return transportInfo; } - public IceUdpTransportInfo modifyCredentials(Credentials credentials) { + public IceUdpTransportInfo modifyCredentials(final Credentials credentials, final Setup setup) { final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo(); transportInfo.setAttribute("ufrag", credentials.ufrag); transportInfo.setAttribute("pwd", credentials.password); - transportInfo.setChildren(getChildren()); + for (final Element child : getChildren()) { + if (child.getName().equals("fingerprint") && Namespace.JINGLE_APPS_DTLS.equals(child.getNamespace())) { + final Fingerprint fingerprint = new Fingerprint(); + fingerprint.setAttributes(new Hashtable<>(child.getAttributes())); + fingerprint.setContent(child.getContent()); + fingerprint.setAttribute("setup", setup.toString().toLowerCase(Locale.ROOT)); + transportInfo.addChild(fingerprint); + } + } return transportInfo; } @@ -337,8 +346,31 @@ public class IceUdpTransportInfo extends GenericTransportInfo { return this.getAttribute("hash"); } - public String getSetup() { - return this.getAttribute("setup"); + public Setup getSetup() { + final String setup = this.getAttribute("setup"); + return setup == null ? null : Setup.of(setup); + } + } + + public enum Setup { + ACTPASS, PASSIVE, ACTIVE; + + public static Setup of(String setup) { + try { + return valueOf(setup.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + return null; + } + } + + public Setup flip() { + if (this == PASSIVE) { + return ACTIVE; + } + if (this == ACTIVE) { + return PASSIVE; + } + throw new IllegalStateException(this.name()+" can not be flipped"); } } } From 0698fa0d8c55507d0f52051d47603dd8ca3bef9e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 16 Nov 2021 11:21:11 +0100 Subject: [PATCH 113/145] store peer dtls setup for later use in ice restart --- .../xmpp/jingle/JingleRtpConnection.java | 34 ++++++++++++++----- .../xmpp/jingle/RtpContentMap.java | 6 +++- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index c0ddfd96a3863f845176d6b38ffa8ee633bc83dd..ce726a2f0e4eaff3269a91b792d5b2e6e011bee6 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -150,6 +150,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private Set proposedMedia; private RtpContentMap initiatorRtpContentMap; private RtpContentMap responderRtpContentMap; + private IceUdpTransportInfo.Setup peerDtlsSetup; private final Stopwatch sessionDuration = Stopwatch.createUnstarted(); private final Queue stateHistory = new LinkedList<>(); private ScheduledFuture ringingTimeoutFuture; @@ -332,11 +333,18 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private IceUdpTransportInfo.Setup getPeerDtlsSetup() { - final IceUdpTransportInfo.Setup responderSetup = this.responderRtpContentMap.getDtlsSetup(); - if (responderSetup == null || responderSetup == IceUdpTransportInfo.Setup.ACTPASS) { - throw new IllegalStateException("Invalid DTLS setup value in responder content map"); + final IceUdpTransportInfo.Setup peerSetup = this.peerDtlsSetup; + if (peerSetup == null || peerSetup == IceUdpTransportInfo.Setup.ACTPASS) { + throw new IllegalStateException("Invalid peer setup"); } - return isInitiator() ? responderSetup : responderSetup.flip(); + return peerSetup; + } + + private void storePeerDtlsSetup(final IceUdpTransportInfo.Setup setup) { + if (setup == null || setup == IceUdpTransportInfo.Setup.ACTPASS) { + throw new IllegalArgumentException("Trying to store invalid peer dtls setup"); + } + this.peerDtlsSetup = setup; } private boolean applyIceRestart(final JinglePacket jinglePacket, final RtpContentMap restartContentMap, final boolean isOffer) throws ExecutionException, InterruptedException { @@ -352,11 +360,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web webRTCWrapper.rollbackLocalDescription().get(); } webRTCWrapper.setRemoteDescription(sdp).get(); - if (isInitiator()) { - this.responderRtpContentMap = restartContentMap; - } else { - this.initiatorRtpContentMap = restartContentMap; - } + setRemoteContentMap(restartContentMap); if (isOffer) { webRTCWrapper.setIsReadyToReceiveIceCandidates(false); final SessionDescription localSessionDescription = setLocalSessionDescription(); @@ -364,6 +368,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web //We need to respond OK before sending any candidates respondOk(jinglePacket); webRTCWrapper.setIsReadyToReceiveIceCandidates(true); + } else { + storePeerDtlsSetup(restartContentMap.getDtlsSetup()); } return true; } @@ -569,6 +575,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private void receiveSessionAccept(final RtpContentMap contentMap) { this.responderRtpContentMap = contentMap; + this.storePeerDtlsSetup(contentMap.getDtlsSetup()); final SessionDescription sessionDescription; try { sessionDescription = SessionDescription.of(contentMap); @@ -663,6 +670,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription); this.responderRtpContentMap = respondingRtpContentMap; + storePeerDtlsSetup(respondingRtpContentMap.getDtlsSetup().flip()); webRTCWrapper.setIsReadyToReceiveIceCandidates(true); final ListenableFuture outgoingContentMapFuture = prepareOutgoingContentMap(respondingRtpContentMap); Futures.addCallback(outgoingContentMapFuture, @@ -1520,6 +1528,14 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } } + private void setRemoteContentMap(final RtpContentMap rtpContentMap) { + if (isInitiator()) { + this.responderRtpContentMap = rtpContentMap; + } else { + this.initiatorRtpContentMap = rtpContentMap; + } + } + private SessionDescription setLocalSessionDescription() throws ExecutionException, InterruptedException { final org.webrtc.SessionDescription sessionDescription = this.webRTCWrapper.setLocalDescription().get(); return SessionDescription.parse(sessionDescription.description); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java index 5366460ecd963d20a6c46625c81a073c27c233cf..091bf7c10ec0f45d0e188b1ff51d8e8d06990ce3 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java @@ -155,7 +155,11 @@ public class RtpContentMap { contents.values(), dt->dt.transport.getFingerprint().getSetup() )); - return setups.size() == 1 ? Iterables.getFirst(setups, null) : null; + final IceUdpTransportInfo.Setup setup = Iterables.getFirst(setups, null); + if (setups.size() == 1 && setup != null) { + return setup; + } + throw new IllegalStateException("Content map doesn't have distinct DTLS setup"); } public boolean emptyCandidates() { From 297a843b9c6b245f4e3bb07a23e487a4f6d5d9f8 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 16 Nov 2021 13:17:10 +0100 Subject: [PATCH 114/145] use implicit rollback (needed to be enabled on libwebrtc) --- .../xmpp/jingle/JingleRtpConnection.java | 2 -- .../xmpp/jingle/WebRTCWrapper.java | 26 +------------------ 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index ce726a2f0e4eaff3269a91b792d5b2e6e011bee6..5c474a754d20c9a4a3fed9e5514b5b47281a4257 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -356,8 +356,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web //We ignore the offer and respond with tie-break. This will clause the responder not to apply the content map return false; } - //rollback our own local description. should happen automatically but doesn't - webRTCWrapper.rollbackLocalDescription().get(); } webRTCWrapper.setRemoteDescription(sdp).get(); setRemoteContentMap(restartContentMap); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 401121b86ee33cdf63251ffa3cbe1db771c05073..13b695b0e516c664ccca8fdf3f5b7880150de8f6 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -86,7 +86,6 @@ public class WebRTCWrapper { private final EventCallback eventCallback; private final AtomicBoolean readyToReceivedIceCandidates = new AtomicBoolean(false); - private final AtomicBoolean ignoreOnRenegotiationNeeded = new AtomicBoolean(false); private final Queue iceCandidates = new LinkedList<>(); private final AppRTCAudioManager.AudioManagerEvents audioManagerEvents = new AppRTCAudioManager.AudioManagerEvents() { @Override @@ -163,10 +162,6 @@ public class WebRTCWrapper { @Override public void onRenegotiationNeeded() { - if (ignoreOnRenegotiationNeeded.get()) { - Log.d(EXTENDED_LOGGING_TAG, "ignoring onRenegotiationNeeded()"); - return; - } Log.d(EXTENDED_LOGGING_TAG, "onRenegotiationNeeded()"); final PeerConnection.PeerConnectionState currentState = peerConnection == null ? null : peerConnection.connectionState(); if (currentState != null && currentState != PeerConnection.PeerConnectionState.NEW) { @@ -277,6 +272,7 @@ public class WebRTCWrapper { rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY; rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN; rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.NEGOTIATE; + rtcConfig.enableImplicitRollback = true; final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, peerConnectionObserver); if (peerConnection == null) { throw new InitializationException("Unable to create PeerConnection"); @@ -456,26 +452,6 @@ public class WebRTCWrapper { }, MoreExecutors.directExecutor()); } - public ListenableFuture rollbackLocalDescription() { - final SettableFuture future = SettableFuture.create(); - final SessionDescription rollback = new SessionDescription(SessionDescription.Type.ROLLBACK, ""); - ignoreOnRenegotiationNeeded.set(true); - requirePeerConnection().setLocalDescription(new SetSdpObserver() { - @Override - public void onSetSuccess() { - future.set(null); - ignoreOnRenegotiationNeeded.set(false); - } - - @Override - public void onSetFailure(final String message) { - future.setException(new FailureToSetDescriptionException(message)); - } - }, rollback); - return future; - } - - private static void logDescription(final SessionDescription sessionDescription) { for (final String line : sessionDescription.description.split(eu.siacs.conversations.xmpp.jingle.SessionDescription.LINE_DIVIDER)) { Log.d(EXTENDED_LOGGING_TAG, line); From abb671616cf0b085d9dbe2fc3b1d3eb446decab2 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 16 Nov 2021 15:17:12 +0100 Subject: [PATCH 115/145] synchronize setDescription calls --- .../conversations/xmpp/XmppConnection.java | 1 - .../xmpp/jingle/JingleRtpConnection.java | 30 +++++++++-------- .../xmpp/jingle/WebRTCWrapper.java | 33 ++++++++++--------- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index abe5d161f55c89a477470d1e9f230f51870d1b35..c3a3b153261acbaac29354b7493d76d7d8943df1 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -54,7 +54,6 @@ import javax.net.ssl.X509TrustManager; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.DomainHostnameVerifier; import eu.siacs.conversations.crypto.XmppDomainVerifier; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.sasl.Anonymous; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 5c474a754d20c9a4a3fed9e5514b5b47281a4257..66cf5c23b29484630612c763d513c5f18594fbd1 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -311,7 +311,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web restartContentMap = existing.modifiedCredentials(newCredentials, IceUdpTransportInfo.Setup.ACTPASS); } else { final IceUdpTransportInfo.Setup setup = getPeerDtlsSetup(); - Log.d(Config.LOGTAG, "received confirmation of ICE restart" + newCredentials.values()+" peer_setup="+setup); + Log.d(Config.LOGTAG, "received confirmation of ICE restart" + newCredentials.values() + " peer_setup=" + setup); // DTLS setup attribute needs to be rewritten to reflect current peer state // https://groups.google.com/g/discuss-webrtc/c/DfpIMwvUfeM restartContentMap = existing.modifiedCredentials(newCredentials, setup); @@ -319,12 +319,18 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web if (applyIceRestart(jinglePacket, restartContentMap, isOffer)) { return isOffer; } else { + Log.d(Config.LOGTAG,"ignored ice restart. offer="+isOffer); respondWithTieBreak(jinglePacket); return true; } } catch (final Exception exception) { respondOk(jinglePacket); final Throwable rootCause = Throwables.getRootCause(exception); + if (rootCause instanceof WebRTCWrapper.PeerConnectionNotInitialized) { + Log.d(Config.LOGTAG,"ignoring PeerConnectionNotInitialized"); + //TODO don’t respond OK but respond with out-of-order + return true; + } Log.d(Config.LOGTAG, "failure to apply ICE restart", rootCause); webRTCWrapper.close(); sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage()); @@ -1466,21 +1472,18 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } final boolean neverConnected = !this.stateHistory.contains(PeerConnection.PeerConnectionState.CONNECTED); - final boolean failedOrDisconnected = Arrays.asList( - PeerConnection.PeerConnectionState.FAILED, - PeerConnection.PeerConnectionState.DISCONNECTED - ).contains(newState); - - if (neverConnected && failedOrDisconnected) { - if (isTerminated()) { - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not sending session-terminate after connectivity error because session is already in state " + this.state); + if (newState == PeerConnection.PeerConnectionState.FAILED) { + if (neverConnected) { + if (isTerminated()) { + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not sending session-terminate after connectivity error because session is already in state " + this.state); + return; + } + webRTCWrapper.execute(this::closeWebRTCSessionAfterFailedConnection); return; + } else { + webRTCWrapper.restartIce(); } - webRTCWrapper.execute(this::closeWebRTCSessionAfterFailedConnection); - } else if (newState == PeerConnection.PeerConnectionState.FAILED) { - Log.d(Config.LOGTAG, "attempting to restart ICE"); - webRTCWrapper.restartIce(); } updateEndUserState(); } @@ -1491,6 +1494,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private void initiateIceRestart() { + this.stateHistory.clear(); this.webRTCWrapper.setIsReadyToReceiveIceCandidates(false); final SessionDescription sessionDescription; try { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 13b695b0e516c664ccca8fdf3f5b7880150de8f6..0712fa900d7c5d50138ff5a63ec459c92dfdbec4 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -431,7 +431,7 @@ public class WebRTCWrapper { videoTrack.setEnabled(enabled); } - ListenableFuture setLocalDescription() { + synchronized ListenableFuture setLocalDescription() { return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> { final SettableFuture future = SettableFuture.create(); peerConnection.setLocalDescription(new SetSdpObserver() { @@ -458,7 +458,7 @@ public class WebRTCWrapper { } } - ListenableFuture setRemoteDescription(final SessionDescription sessionDescription) { + synchronized ListenableFuture setRemoteDescription(final SessionDescription sessionDescription) { Log.d(EXTENDED_LOGGING_TAG, "setting remote description:"); logDescription(sessionDescription); return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> { @@ -482,12 +482,20 @@ public class WebRTCWrapper { private ListenableFuture getPeerConnectionFuture() { final PeerConnection peerConnection = this.peerConnection; if (peerConnection == null) { - return Futures.immediateFailedFuture(new IllegalStateException("initialize PeerConnection first")); + return Futures.immediateFailedFuture(new PeerConnectionNotInitialized()); } else { return Futures.immediateFuture(peerConnection); } } + private PeerConnection requirePeerConnection() { + final PeerConnection peerConnection = this.peerConnection; + if (peerConnection == null) { + throw new PeerConnectionNotInitialized(); + } + return peerConnection; + } + void addIceCandidate(IceCandidate iceCandidate) { requirePeerConnection().addIceCandidate(iceCandidate); } @@ -512,10 +520,15 @@ public class WebRTCWrapper { } } - public PeerConnection.PeerConnectionState getState() { + PeerConnection.PeerConnectionState getState() { return requirePeerConnection().connectionState(); } + public PeerConnection.SignalingState getSignalingState() { + return requirePeerConnection().signalingState(); + } + + EglBase.Context getEglBaseContext() { return this.eglBase.getEglBaseContext(); } @@ -528,14 +541,6 @@ public class WebRTCWrapper { return Optional.fromNullable(this.remoteVideoTrack); } - private PeerConnection requirePeerConnection() { - final PeerConnection peerConnection = this.peerConnection; - if (peerConnection == null) { - throw new PeerConnectionNotInitialized(); - } - return peerConnection; - } - private Context requireContext() { final Context context = this.context; if (context == null) { @@ -552,10 +557,6 @@ public class WebRTCWrapper { executorService.execute(command); } - public PeerConnection.SignalingState getSignalingState() { - return requirePeerConnection().signalingState(); - } - public interface EventCallback { void onIceCandidate(IceCandidate iceCandidate); From 0a18c8613f83d2a6861a4ae4fef4e534f3f122f9 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 16 Nov 2021 17:08:34 +0100 Subject: [PATCH 116/145] assume credentials are the same for all contents when restarting ICE --- .../conversations/ui/RtpSessionActivity.java | 1 + .../xmpp/jingle/JingleRtpConnection.java | 23 +++++++++++-------- .../xmpp/jingle/RtpContentMap.java | 17 ++++++++++---- .../jingle/stanzas/IceUdpTransportInfo.java | 4 ++-- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index d0bdbb78881b9964d99ae1f03b941acb19b48ba0..39ba7429f161a2d7f01c16fcbf47110892c055a8 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -1003,6 +1003,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe RendererCommon.ScalingType.SCALE_ASPECT_FILL, RendererCommon.ScalingType.SCALE_ASPECT_FIT ); + //TODO this should probably only be 'connected' if (STATES_CONSIDERED_CONNECTED.contains(state)) { binding.appBarLayout.setVisibility(View.GONE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 66cf5c23b29484630612c763d513c5f18594fbd1..26c068c080135a5fecc26e75bf55ba9ca7973797 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -141,7 +141,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private final WebRTCWrapper webRTCWrapper = new WebRTCWrapper(this); - //TODO convert to Queue>? private final Queue> pendingIceCandidates = new LinkedList<>(); private final OmemoVerification omemoVerification = new OmemoVerification(); private final Message message; @@ -295,9 +294,13 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private boolean checkForIceRestart(final JinglePacket jinglePacket, final RtpContentMap rtpContentMap) { final RtpContentMap existing = getRemoteContentMap(); - final Map existingCredentials = existing.getCredentials(); - final Map newCredentials = rtpContentMap.getCredentials(); - if (!existingCredentials.keySet().equals(newCredentials.keySet())) { + final IceUdpTransportInfo.Credentials existingCredentials; + final IceUdpTransportInfo.Credentials newCredentials; + try { + existingCredentials = existing.getCredentials(); + newCredentials = rtpContentMap.getCredentials(); + } catch (final IllegalStateException e) { + Log.d(Config.LOGTAG, "unable to gather credentials for comparison", e); return false; } if (existingCredentials.equals(newCredentials)) { @@ -307,11 +310,11 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web final RtpContentMap restartContentMap; try { if (isOffer) { - Log.d(Config.LOGTAG, "received offer to restart ICE " + newCredentials.values()); + Log.d(Config.LOGTAG, "received offer to restart ICE " + newCredentials); restartContentMap = existing.modifiedCredentials(newCredentials, IceUdpTransportInfo.Setup.ACTPASS); } else { final IceUdpTransportInfo.Setup setup = getPeerDtlsSetup(); - Log.d(Config.LOGTAG, "received confirmation of ICE restart" + newCredentials.values() + " peer_setup=" + setup); + Log.d(Config.LOGTAG, "received confirmation of ICE restart" + newCredentials + " peer_setup=" + setup); // DTLS setup attribute needs to be rewritten to reflect current peer state // https://groups.google.com/g/discuss-webrtc/c/DfpIMwvUfeM restartContentMap = existing.modifiedCredentials(newCredentials, setup); @@ -319,7 +322,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web if (applyIceRestart(jinglePacket, restartContentMap, isOffer)) { return isOffer; } else { - Log.d(Config.LOGTAG,"ignored ice restart. offer="+isOffer); + Log.d(Config.LOGTAG, "ignoring ICE restart. sending tie-break"); respondWithTieBreak(jinglePacket); return true; } @@ -327,7 +330,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web respondOk(jinglePacket); final Throwable rootCause = Throwables.getRootCause(exception); if (rootCause instanceof WebRTCWrapper.PeerConnectionNotInitialized) { - Log.d(Config.LOGTAG,"ignoring PeerConnectionNotInitialized"); + Log.d(Config.LOGTAG, "ignoring PeerConnectionNotInitialized"); //TODO don’t respond OK but respond with out-of-order return true; } @@ -1451,8 +1454,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web @Override public void onIceCandidate(final IceCandidate iceCandidate) { final RtpContentMap rtpContentMap = isInitiator() ? this.initiatorRtpContentMap : this.responderRtpContentMap; - final Collection currentUfrags = Collections2.transform(rtpContentMap.getCredentials().values(), c -> c.ufrag); - final IceUdpTransportInfo.Candidate candidate = IceUdpTransportInfo.Candidate.fromSdpAttribute(iceCandidate.sdp, currentUfrags); + final String ufrag = rtpContentMap.getCredentials().ufrag; + final IceUdpTransportInfo.Candidate candidate = IceUdpTransportInfo.Candidate.fromSdpAttribute(iceCandidate.sdp, ufrag); if (candidate == null) { Log.d(Config.LOGTAG, "ignoring (not sending) candidate: " + iceCandidate.toString()); return; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java index 091bf7c10ec0f45d0e188b1ff51d8e8d06990ce3..ea351cb1b2881dc36dc09b20c70ec6674d2e6934 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java @@ -146,14 +146,22 @@ public class RtpContentMap { ); } - public Map getCredentials() { - return Maps.transformValues(contents, dt -> dt.transport.getCredentials()); + public IceUdpTransportInfo.Credentials getCredentials() { + final Set allCredentials = ImmutableSet.copyOf(Collections2.transform( + contents.values(), + dt -> dt.transport.getCredentials() + )); + final IceUdpTransportInfo.Credentials credentials = Iterables.getFirst(allCredentials, null); + if (allCredentials.size() == 1 && credentials != null) { + return credentials; + } + throw new IllegalStateException("Content map does not have distinct credentials"); } public IceUdpTransportInfo.Setup getDtlsSetup() { final Set setups = ImmutableSet.copyOf(Collections2.transform( contents.values(), - dt->dt.transport.getFingerprint().getSetup() + dt -> dt.transport.getFingerprint().getSetup() )); final IceUdpTransportInfo.Setup setup = Iterables.getFirst(setups, null); if (setups.size() == 1 && setup != null) { @@ -170,12 +178,11 @@ public class RtpContentMap { return count == 0; } - public RtpContentMap modifiedCredentials(Map credentialsMap, final IceUdpTransportInfo.Setup setup) { + public RtpContentMap modifiedCredentials(IceUdpTransportInfo.Credentials credentials, final IceUdpTransportInfo.Setup setup) { final ImmutableMap.Builder contentMapBuilder = new ImmutableMap.Builder<>(); for (final Map.Entry content : contents.entrySet()) { final RtpDescription rtpDescription = content.getValue().description; IceUdpTransportInfo transportInfo = content.getValue().transport; - final IceUdpTransportInfo.Credentials credentials = Objects.requireNonNull(credentialsMap.get(content.getKey())); final IceUdpTransportInfo modifiedTransportInfo = transportInfo.modifyCredentials(credentials, setup); contentMapBuilder.put(content.getKey(), new DescriptionTransport(rtpDescription, modifiedTransportInfo)); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java index 9af1186fc3924ccc79526eb8305332f9c326d144..45260cafb44a4f9ee89ada72933499ee585b25d4 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java @@ -146,7 +146,7 @@ public class IceUdpTransportInfo extends GenericTransportInfo { } // https://tools.ietf.org/html/draft-ietf-mmusic-ice-sip-sdp-39#section-5.1 - public static Candidate fromSdpAttribute(final String attribute, Collection currentUfrags) { + public static Candidate fromSdpAttribute(final String attribute, String currentUfrag) { final String[] pair = attribute.split(":", 2); if (pair.length == 2 && "candidate".equals(pair[0])) { final String[] segments = pair[1].split(" "); @@ -163,7 +163,7 @@ public class IceUdpTransportInfo extends GenericTransportInfo { additional.put(segments[i], segments[i + 1]); } final String ufrag = additional.get("ufrag"); - if (ufrag != null && !currentUfrags.contains(ufrag)) { + if (ufrag != null && !ufrag.equals(currentUfrag)) { return null; } final Candidate candidate = new Candidate(); From 1bf2d5dd8f31c4c10199e9133012cc5d1645921a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 16 Nov 2021 22:01:48 +0100 Subject: [PATCH 117/145] video calls: leave full screen mode during reconnect --- .../conversations/ui/RtpSessionActivity.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 39ba7429f161a2d7f01c16fcbf47110892c055a8..65beae35dbfc0c658d835db41e0f507b3deefa14 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -103,6 +103,11 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe RtpEndUserState.CONNECTED, RtpEndUserState.RECONNECTING ); + private static final List STATES_SHOWING_PIP_PLACEHOLDER = Arrays.asList( + RtpEndUserState.ACCEPTING_CALL, + RtpEndUserState.CONNECTING, + RtpEndUserState.RECONNECTING + ); private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session"; private static final int REQUEST_ACCEPT_CALL = 0x1111; private WeakReference rtpConnectionReference; @@ -640,8 +645,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe surfaceViewRenderer.setVisibility(View.VISIBLE); try { surfaceViewRenderer.init(requireRtpConnection().getEglBaseContext(), null); - } catch (IllegalStateException e) { - Log.d(Config.LOGTAG, "SurfaceViewRenderer was already initialized"); + } catch (final IllegalStateException e) { + //Log.d(Config.LOGTAG, "SurfaceViewRenderer was already initialized"); } surfaceViewRenderer.setEnableHardwareScaler(true); } @@ -975,7 +980,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); return; } - if (isPictureInPicture() && (state == RtpEndUserState.CONNECTING || state == RtpEndUserState.ACCEPTING_CALL)) { + if (isPictureInPicture() && STATES_SHOWING_PIP_PLACEHOLDER.contains(state)) { binding.localVideo.setVisibility(View.GONE); binding.remoteVideoWrapper.setVisibility(View.GONE); binding.appBarLayout.setVisibility(View.GONE); @@ -1003,12 +1008,12 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe RendererCommon.ScalingType.SCALE_ASPECT_FILL, RendererCommon.ScalingType.SCALE_ASPECT_FIT ); - //TODO this should probably only be 'connected' - if (STATES_CONSIDERED_CONNECTED.contains(state)) { + if (state == RtpEndUserState.CONNECTED) { binding.appBarLayout.setVisibility(View.GONE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); binding.remoteVideoWrapper.setVisibility(View.VISIBLE); } else { + binding.appBarLayout.setVisibility(View.VISIBLE); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); binding.remoteVideoWrapper.setVisibility(View.GONE); } From 61fb38cd8442086596b347b45ef2e4b69f647af2 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 17 Nov 2021 10:49:16 +0100 Subject: [PATCH 118/145] clean up some error handling error ICE restarts --- .../eu/siacs/conversations/xml/Namespace.java | 1 + .../xmpp/jingle/JingleConnectionManager.java | 2 +- .../xmpp/jingle/JingleRtpConnection.java | 101 +++++++++++------- .../xmpp/jingle/RtpContentMap.java | 7 ++ 4 files changed, 71 insertions(+), 40 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index b0c4fe85c4e54dbe5d5522bf9c1ec2c08cf41020..09bbda4cdcb4dbbe7a08352f452f931bf7ea4866 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -28,6 +28,7 @@ public final class Namespace { public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0"; public static final String AVATAR_CONVERSION = "urn:xmpp:pep-vcard-conversion:0"; public static final String JINGLE = "urn:xmpp:jingle:1"; + public static final String JINGLE_ERRORS = "urn:xmpp:jingle:errors:1"; public static final String JINGLE_MESSAGE = "urn:xmpp:jingle-message:0"; 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"; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 6b94f1f4dad24a540764315ef342cc9a7ae940c1..cbf4b85fd253a2519e94acef650b4b6c0f1dcadc 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -206,7 +206,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { final Element error = response.addChild("error"); error.setAttribute("type", conditionType); error.addChild(condition, "urn:ietf:params:xml:ns:xmpp-stanzas"); - error.addChild(jingleCondition, "urn:xmpp:jingle:errors:1"); + error.addChild(jingleCondition, Namespace.JINGLE_ERRORS); account.getXmppConnection().sendIqPacket(response, null); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 26c068c080135a5fecc26e75bf55ba9ca7973797..1295bf908568ebc04f9fe1201e035aeac9d74790 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -306,6 +306,9 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web if (existingCredentials.equals(newCredentials)) { return false; } + //TODO an alternative approach is to check if we already got an iq result to our ICE-restart + // and if that's the case we are seeing an answer. + // This might be more spec compliant but also more error prone potentially final boolean isOffer = rtpContentMap.emptyCandidates(); final RtpContentMap restartContentMap; try { @@ -330,8 +333,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web respondOk(jinglePacket); final Throwable rootCause = Throwables.getRootCause(exception); if (rootCause instanceof WebRTCWrapper.PeerConnectionNotInitialized) { - Log.d(Config.LOGTAG, "ignoring PeerConnectionNotInitialized"); - //TODO don’t respond OK but respond with out-of-order + //If this happens a termination is already in progress + Log.d(Config.LOGTAG, "ignoring PeerConnectionNotInitialized on ICE restart"); return true; } Log.d(Config.LOGTAG, "failure to apply ICE restart", rootCause); @@ -484,8 +487,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private void receiveSessionInitiate(final JinglePacket jinglePacket, final RtpContentMap contentMap) { try { contentMap.requireContentDescriptions(); - //TODO require actpass - contentMap.requireDTLSFingerprint(); + contentMap.requireDTLSFingerprint(true); } catch (final RuntimeException e) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents", Throwables.getRootCause(e)); respondOk(jinglePacket); @@ -1072,36 +1074,48 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private synchronized void handleIqResponse(final Account account, final IqPacket response) { if (response.getType() == IqPacket.TYPE.ERROR) { - final String errorCondition = response.getErrorCondition(); - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received IQ-error from " + response.getFrom() + " in RTP session. " + errorCondition); - if (isTerminated()) { - Log.i(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring error because session was already terminated"); - return; - } - this.webRTCWrapper.close(); - final State target; - if (Arrays.asList( - "service-unavailable", - "recipient-unavailable", - "remote-server-not-found", - "remote-server-timeout" - ).contains(errorCondition)) { - target = State.TERMINATED_CONNECTIVITY_ERROR; - } else { - target = State.TERMINATED_APPLICATION_FAILURE; - } - transitionOrThrow(target); - this.finish(); - } else if (response.getType() == IqPacket.TYPE.TIMEOUT) { - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received IQ timeout in RTP session with " + id.with + ". terminating with connectivity error"); - if (isTerminated()) { - Log.i(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring error because session was already terminated"); - return; - } - this.webRTCWrapper.close(); - transitionOrThrow(State.TERMINATED_CONNECTIVITY_ERROR); - this.finish(); + handleIqErrorResponse(response); + return; + } + if (response.getType() == IqPacket.TYPE.TIMEOUT) { + handleIqTimeoutResponse(response); + } + } + + private void handleIqErrorResponse(final IqPacket response) { + Preconditions.checkArgument(response.getType() == IqPacket.TYPE.ERROR); + final String errorCondition = response.getErrorCondition(); + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received IQ-error from " + response.getFrom() + " in RTP session. " + errorCondition); + if (isTerminated()) { + Log.i(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring error because session was already terminated"); + return; + } + this.webRTCWrapper.close(); + final State target; + if (Arrays.asList( + "service-unavailable", + "recipient-unavailable", + "remote-server-not-found", + "remote-server-timeout" + ).contains(errorCondition)) { + target = State.TERMINATED_CONNECTIVITY_ERROR; + } else { + target = State.TERMINATED_APPLICATION_FAILURE; + } + transitionOrThrow(target); + this.finish(); + } + + private void handleIqTimeoutResponse(final IqPacket response) { + Preconditions.checkArgument(response.getType() == IqPacket.TYPE.ERROR); + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received IQ timeout in RTP session with " + id.with + ". terminating with connectivity error"); + if (isTerminated()) { + Log.i(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring error because session was already terminated"); + return; } + this.webRTCWrapper.close(); + transitionOrThrow(State.TERMINATED_CONNECTIVITY_ERROR); + this.finish(); } private void terminateWithOutOfOrder(final JinglePacket jinglePacket) { @@ -1503,8 +1517,9 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web try { sessionDescription = setLocalSessionDescription(); } catch (final Exception e) { - Log.d(Config.LOGTAG, "failed to renegotiate", e); - //TODO send some sort of failure (comparable to when initiating) + final Throwable cause = Throwables.getRootCause(e); + Log.d(Config.LOGTAG, "failed to renegotiate", cause); + sendSessionTerminate(Reason.FAILED_APPLICATION, cause.getMessage()); return; } final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription); @@ -1517,10 +1532,18 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web Log.d(Config.LOGTAG, "received success to our ice restart"); setLocalContentMap(rtpContentMap); webRTCWrapper.setIsReadyToReceiveIceCandidates(true); - } else { - Log.d(Config.LOGTAG, "received failure to our ice restart"); - //TODO ignore tie break (maybe rollback?) - //TODO handle other errors + return; + } + if (response.getType() == IqPacket.TYPE.ERROR) { + final Element error = response.findChild("error"); + if (error != null && error.hasChild("tie-break", Namespace.JINGLE_ERRORS)) { + Log.d(Config.LOGTAG, "received tie-break as result of ice restart"); + return; + } + handleIqErrorResponse(response); + } + if (response.getType() == IqPacket.TYPE.TIMEOUT) { + handleIqTimeoutResponse(response); } }); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java index ea351cb1b2881dc36dc09b20c70ec6674d2e6934..21684a1657b242ce9db39553d18c27aa4c1f82f0 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java @@ -96,6 +96,10 @@ public class RtpContentMap { } void requireDTLSFingerprint() { + requireDTLSFingerprint(false); + } + + void requireDTLSFingerprint(final boolean requireActPass) { if (this.contents.size() == 0) { throw new IllegalStateException("No contents available"); } @@ -109,6 +113,9 @@ public class RtpContentMap { if (setup == null) { throw new SecurityException(String.format("Use of DTLS-SRTP (XEP-0320) is required for content %s but missing setup attribute", entry.getKey())); } + if (requireActPass && setup != IceUdpTransportInfo.Setup.ACTPASS) { + throw new SecurityException("Initiator needs to offer ACTPASS as setup for DTLS-SRTP (XEP-0320)"); + } } } From a508a81553604b7a6af6f650146f2ccd736b4066 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 17 Nov 2021 11:33:15 +0100 Subject: [PATCH 119/145] externalize rtc config generation into seperate method --- .../xmpp/jingle/JingleRtpConnection.java | 1 + .../xmpp/jingle/WebRTCWrapper.java | 21 +++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 1295bf908568ebc04f9fe1201e035aeac9d74790..e4661a3aa6bf1a528e6d92753502caa3eee228a6 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1511,6 +1511,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private void initiateIceRestart() { + //TODO discover new TURN/STUN credentials this.stateHistory.clear(); this.webRTCWrapper.setIsReadyToReceiveIceCandidates(false); final SessionDescription sessionDescription; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 0712fa900d7c5d50138ff5a63ec459c92dfdbec4..6722f9f2ca93bf443a3b0b335ca6f16373ab84fc 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -267,12 +267,7 @@ public class WebRTCWrapper { .createPeerConnectionFactory(); - final PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers); - rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED; //XEP-0176 doesn't support tcp - rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY; - rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN; - rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.NEGOTIATE; - rtcConfig.enableImplicitRollback = true; + final PeerConnection.RTCConfiguration rtcConfig = buildConfiguration(iceServers); final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, peerConnectionObserver); if (peerConnection == null) { throw new InitializationException("Unable to create PeerConnection"); @@ -306,6 +301,20 @@ public class WebRTCWrapper { this.peerConnection = peerConnection; } + private static PeerConnection.RTCConfiguration buildConfiguration(final List iceServers) { + final PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers); + rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED; //XEP-0176 doesn't support tcp + rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY; + rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN; + rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.NEGOTIATE; + rtcConfig.enableImplicitRollback = true; + return rtcConfig; + } + + void reconfigurePeerConnection(final List iceServers) { + requirePeerConnection().setConfiguration(buildConfiguration(iceServers)); + } + void restartIce() { executorService.execute(() -> requirePeerConnection().restartIce()); } From 5d526a77e3899714750d5f56536364a5ed9b5149 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 18 Nov 2021 11:24:10 +0100 Subject: [PATCH 120/145] include uncertainty into shared geo uri --- .../ui/ConversationFragment.java | 12 +- .../ui/ShareLocationActivity.java | 421 +++++++++--------- 2 files changed, 221 insertions(+), 212 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 0077684a5d30d627620056aa7d8b3af2cf0cf7e5..771c99e9cc28d7e32df863612283925f339881f9 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -856,9 +856,15 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke toggleInputMethod(); break; case ATTACHMENT_CHOICE_LOCATION: - double latitude = data.getDoubleExtra("latitude", 0); - double longitude = data.getDoubleExtra("longitude", 0); - Uri geo = Uri.parse("geo:" + latitude + "," + longitude); + final double latitude = data.getDoubleExtra("latitude", 0); + final double longitude = data.getDoubleExtra("longitude", 0); + final int accuracy = data.getIntExtra("accuracy", 0); + final Uri geo; + if (accuracy > 0) { + geo = Uri.parse(String.format("geo:%s,%s;u=%s", latitude, longitude, accuracy)); + } else { + geo = Uri.parse(String.format("geo:%s,%s", latitude, longitude)); + } mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), geo, Attachment.Type.LOCATION)); toggleInputMethod(); break; diff --git a/src/main/java/eu/siacs/conversations/ui/ShareLocationActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareLocationActivity.java index 641a01e5c1e952bb48cd5413c034e65fe5b20026..7e53fe89792b107f039bfa54a0c3532a61f1c4e9 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareLocationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareLocationActivity.java @@ -13,10 +13,13 @@ import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; import com.google.android.material.snackbar.Snackbar; +import com.google.common.math.DoubleMath; import org.osmdroid.api.IGeoPoint; import org.osmdroid.util.GeoPoint; +import java.math.RoundingMode; + import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityShareLocationBinding; @@ -28,213 +31,213 @@ import eu.siacs.conversations.utils.ThemeHelper; public class ShareLocationActivity extends LocationActivity implements LocationListener { - private Snackbar snackBar; - private ActivityShareLocationBinding binding; - private boolean marker_fixed_to_loc = false; - private static final String KEY_FIXED_TO_LOC = "fixed_to_loc"; - private Boolean noAskAgain = false; - - @Override - protected void onSaveInstanceState(@NonNull final Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putBoolean(KEY_FIXED_TO_LOC, marker_fixed_to_loc); - } - - @Override - protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - - if (savedInstanceState.containsKey(KEY_FIXED_TO_LOC)) { - this.marker_fixed_to_loc = savedInstanceState.getBoolean(KEY_FIXED_TO_LOC); - } - } - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - this.binding = DataBindingUtil.setContentView(this,R.layout.activity_share_location); - setSupportActionBar(binding.toolbar); - configureActionBar(getSupportActionBar()); - setupMapView(binding.map, LocationProvider.getGeoPoint(this)); - - this.binding.cancelButton.setOnClickListener(view -> { - setResult(RESULT_CANCELED); - finish(); - }); - - this.snackBar = Snackbar.make(this.binding.snackbarCoordinator, R.string.location_disabled, Snackbar.LENGTH_INDEFINITE); - this.snackBar.setAction(R.string.enable, view -> { - if (isLocationEnabledAndAllowed()) { - updateUi(); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !hasLocationPermissions()) { - requestPermissions(REQUEST_CODE_SNACKBAR_PRESSED); - } else if (!isLocationEnabled()) { - startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); - } - }); - ThemeHelper.fix(this.snackBar); - - this.binding.shareButton.setOnClickListener(view -> { - final Intent result = new Intent(); - - if (marker_fixed_to_loc && myLoc != null) { - result.putExtra("latitude", myLoc.getLatitude()); - result.putExtra("longitude", myLoc.getLongitude()); - result.putExtra("altitude", myLoc.getAltitude()); - result.putExtra("accuracy", (int) myLoc.getAccuracy()); - } else { - final IGeoPoint markerPoint = this.binding.map.getMapCenter(); - result.putExtra("latitude", markerPoint.getLatitude()); - result.putExtra("longitude", markerPoint.getLongitude()); - } - - setResult(RESULT_OK, result); - finish(); - }); - - this.marker_fixed_to_loc = isLocationEnabledAndAllowed(); - - this.binding.fab.setOnClickListener(view -> { - if (!marker_fixed_to_loc) { - if (!isLocationEnabled()) { - startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - requestPermissions(REQUEST_CODE_FAB_PRESSED); - } - } - toggleFixedLocation(); - }); - } - - @Override - public void onRequestPermissionsResult(final int requestCode, - @NonNull final String[] permissions, - @NonNull final int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - - if (grantResults.length > 0 && - grantResults[0] != PackageManager.PERMISSION_GRANTED && - Build.VERSION.SDK_INT >= 23 && - permissions.length > 0 && - ( - Manifest.permission.LOCATION_HARDWARE.equals(permissions[0]) || - Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[0]) || - Manifest.permission.ACCESS_COARSE_LOCATION.equals(permissions[0]) - ) && - !shouldShowRequestPermissionRationale(permissions[0])) { - noAskAgain = true; - } - - if (!noAskAgain && requestCode == REQUEST_CODE_SNACKBAR_PRESSED && !isLocationEnabled() && hasLocationPermissions()) { - startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); - } - updateUi(); - } - - @Override - protected void gotoLoc(final boolean setZoomLevel) { - if (this.myLoc != null && mapController != null) { - if (setZoomLevel) { - mapController.setZoom(Config.Map.FINAL_ZOOM_LEVEL); - } - mapController.animateTo(new GeoPoint(this.myLoc)); - } - } - - @Override - protected void setMyLoc(final Location location) { - this.myLoc = location; - } - - @Override - protected void onPause() { - super.onPause(); - } - - @Override - protected void updateLocationMarkers() { - super.updateLocationMarkers(); - if (this.myLoc != null) { - this.binding.map.getOverlays().add(new MyLocation(this, null, this.myLoc)); - if (this.marker_fixed_to_loc) { - this.binding.map.getOverlays().add(new Marker(marker_icon, new GeoPoint(this.myLoc))); - } else { - this.binding.map.getOverlays().add(new Marker(marker_icon)); - } - } else { - this.binding.map.getOverlays().add(new Marker(marker_icon)); - } - } - - @Override - public void onLocationChanged(final Location location) { - if (this.myLoc == null) { - this.marker_fixed_to_loc = true; - } - updateUi(); - if (LocationHelper.isBetterLocation(location, this.myLoc)) { - final Location oldLoc = this.myLoc; - this.myLoc = location; - - // Don't jump back to the users location if they're not moving (more or less). - if (oldLoc == null || (this.marker_fixed_to_loc && this.myLoc.distanceTo(oldLoc) > 1)) { - gotoLoc(); - } - - updateLocationMarkers(); - } - } - - @Override - public void onStatusChanged(final String provider, final int status, final Bundle extras) { - - } - - @Override - public void onProviderEnabled(final String provider) { - - } - - @Override - public void onProviderDisabled(final String provider) { - - } - - private boolean isLocationEnabledAndAllowed() { - return this.hasLocationFeature && (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || this.hasLocationPermissions()) && this.isLocationEnabled(); - } - - private void toggleFixedLocation() { - this.marker_fixed_to_loc = isLocationEnabledAndAllowed() && !this.marker_fixed_to_loc; - if (this.marker_fixed_to_loc) { - gotoLoc(false); - } - updateLocationMarkers(); - updateUi(); - } - - @Override - protected void updateUi() { - if (!hasLocationFeature || noAskAgain || isLocationEnabledAndAllowed()) { - this.snackBar.dismiss(); - } else { - this.snackBar.show(); - } - - if (isLocationEnabledAndAllowed()) { - this.binding.fab.setVisibility(View.VISIBLE); - runOnUiThread(() -> { - this.binding.fab.setImageResource(marker_fixed_to_loc ? R.drawable.ic_gps_fixed_white_24dp : - R.drawable.ic_gps_not_fixed_white_24dp); - this.binding.fab.setContentDescription(getResources().getString( - marker_fixed_to_loc ? R.string.action_unfix_from_location : R.string.action_fix_to_location - )); - this.binding.fab.invalidate(); - }); - } else { - this.binding.fab.setVisibility(View.GONE); - } - } + private Snackbar snackBar; + private ActivityShareLocationBinding binding; + private boolean marker_fixed_to_loc = false; + private static final String KEY_FIXED_TO_LOC = "fixed_to_loc"; + private Boolean noAskAgain = false; + + @Override + protected void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putBoolean(KEY_FIXED_TO_LOC, marker_fixed_to_loc); + } + + @Override + protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + + if (savedInstanceState.containsKey(KEY_FIXED_TO_LOC)) { + this.marker_fixed_to_loc = savedInstanceState.getBoolean(KEY_FIXED_TO_LOC); + } + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_share_location); + setSupportActionBar(binding.toolbar); + configureActionBar(getSupportActionBar()); + setupMapView(binding.map, LocationProvider.getGeoPoint(this)); + + this.binding.cancelButton.setOnClickListener(view -> { + setResult(RESULT_CANCELED); + finish(); + }); + + this.snackBar = Snackbar.make(this.binding.snackbarCoordinator, R.string.location_disabled, Snackbar.LENGTH_INDEFINITE); + this.snackBar.setAction(R.string.enable, view -> { + if (isLocationEnabledAndAllowed()) { + updateUi(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !hasLocationPermissions()) { + requestPermissions(REQUEST_CODE_SNACKBAR_PRESSED); + } else if (!isLocationEnabled()) { + startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + } + }); + ThemeHelper.fix(this.snackBar); + + this.binding.shareButton.setOnClickListener(this::shareLocation); + + this.marker_fixed_to_loc = isLocationEnabledAndAllowed(); + + this.binding.fab.setOnClickListener(view -> { + if (!marker_fixed_to_loc) { + if (!isLocationEnabled()) { + startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + requestPermissions(REQUEST_CODE_FAB_PRESSED); + } + } + toggleFixedLocation(); + }); + } + + private void shareLocation(final View view) { + final Intent result = new Intent(); + if (marker_fixed_to_loc && myLoc != null) { + result.putExtra("latitude", myLoc.getLatitude()); + result.putExtra("longitude", myLoc.getLongitude()); + result.putExtra("altitude", myLoc.getAltitude()); + result.putExtra("accuracy", DoubleMath.roundToInt(myLoc.getAccuracy(), RoundingMode.HALF_UP)); + } else { + final IGeoPoint markerPoint = this.binding.map.getMapCenter(); + result.putExtra("latitude", markerPoint.getLatitude()); + result.putExtra("longitude", markerPoint.getLongitude()); + } + setResult(RESULT_OK, result); + finish(); + } + + @Override + public void onRequestPermissionsResult(final int requestCode, + @NonNull final String[] permissions, + @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + if (grantResults.length > 0 && + grantResults[0] != PackageManager.PERMISSION_GRANTED && + Build.VERSION.SDK_INT >= 23 && + permissions.length > 0 && + ( + Manifest.permission.LOCATION_HARDWARE.equals(permissions[0]) || + Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[0]) || + Manifest.permission.ACCESS_COARSE_LOCATION.equals(permissions[0]) + ) && + !shouldShowRequestPermissionRationale(permissions[0])) { + noAskAgain = true; + } + + if (!noAskAgain && requestCode == REQUEST_CODE_SNACKBAR_PRESSED && !isLocationEnabled() && hasLocationPermissions()) { + startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + } + updateUi(); + } + + @Override + protected void gotoLoc(final boolean setZoomLevel) { + if (this.myLoc != null && mapController != null) { + if (setZoomLevel) { + mapController.setZoom(Config.Map.FINAL_ZOOM_LEVEL); + } + mapController.animateTo(new GeoPoint(this.myLoc)); + } + } + + @Override + protected void setMyLoc(final Location location) { + this.myLoc = location; + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + protected void updateLocationMarkers() { + super.updateLocationMarkers(); + if (this.myLoc != null) { + this.binding.map.getOverlays().add(new MyLocation(this, null, this.myLoc)); + if (this.marker_fixed_to_loc) { + this.binding.map.getOverlays().add(new Marker(marker_icon, new GeoPoint(this.myLoc))); + } else { + this.binding.map.getOverlays().add(new Marker(marker_icon)); + } + } else { + this.binding.map.getOverlays().add(new Marker(marker_icon)); + } + } + + @Override + public void onLocationChanged(final Location location) { + if (this.myLoc == null) { + this.marker_fixed_to_loc = true; + } + updateUi(); + if (LocationHelper.isBetterLocation(location, this.myLoc)) { + final Location oldLoc = this.myLoc; + this.myLoc = location; + + // Don't jump back to the users location if they're not moving (more or less). + if (oldLoc == null || (this.marker_fixed_to_loc && this.myLoc.distanceTo(oldLoc) > 1)) { + gotoLoc(); + } + + updateLocationMarkers(); + } + } + + @Override + public void onStatusChanged(final String provider, final int status, final Bundle extras) { + + } + + @Override + public void onProviderEnabled(final String provider) { + + } + + @Override + public void onProviderDisabled(final String provider) { + + } + + private boolean isLocationEnabledAndAllowed() { + return this.hasLocationFeature && (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || this.hasLocationPermissions()) && this.isLocationEnabled(); + } + + private void toggleFixedLocation() { + this.marker_fixed_to_loc = isLocationEnabledAndAllowed() && !this.marker_fixed_to_loc; + if (this.marker_fixed_to_loc) { + gotoLoc(false); + } + updateLocationMarkers(); + updateUi(); + } + + @Override + protected void updateUi() { + if (!hasLocationFeature || noAskAgain || isLocationEnabledAndAllowed()) { + this.snackBar.dismiss(); + } else { + this.snackBar.show(); + } + + if (isLocationEnabledAndAllowed()) { + this.binding.fab.setVisibility(View.VISIBLE); + runOnUiThread(() -> { + this.binding.fab.setImageResource(marker_fixed_to_loc ? R.drawable.ic_gps_fixed_white_24dp : + R.drawable.ic_gps_not_fixed_white_24dp); + this.binding.fab.setContentDescription(getResources().getString( + marker_fixed_to_loc ? R.string.action_unfix_from_location : R.string.action_fix_to_location + )); + this.binding.fab.invalidate(); + }); + } else { + this.binding.fab.setVisibility(View.GONE); + } + } } \ No newline at end of file From f8a94161dbc3398e6d355175e45d7b5e407bf32c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 19 Nov 2021 12:25:27 +0100 Subject: [PATCH 121/145] don't play tone going from connect->reconnect->connect --- .../java/eu/siacs/conversations/xmpp/jingle/ToneManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java index 4fb9dee1697a12c416bd5caf77f130e44e46a89c..e368d3b09ef0e7a6d4f7ca8761121c755db69402 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java @@ -51,7 +51,7 @@ class ToneManager { return ToneState.ENDING_CALL; } } - if (state == RtpEndUserState.CONNECTED) { + if (state == RtpEndUserState.CONNECTED || state == RtpEndUserState.RECONNECTING) { if (media.contains(Media.VIDEO)) { return ToneState.NULL; } else { From db834a1f07ee32005d8ba87e11ff03265f986a24 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 19 Nov 2021 12:26:11 +0100 Subject: [PATCH 122/145] indicate call reconnect in notification --- .../services/NotificationService.java | 17 +++++++++++++---- .../services/XmppConnectionService.java | 18 ++++++++++-------- .../xmpp/jingle/JingleRtpConnection.java | 14 +++++++++++--- src/main/res/values/strings.xml | 2 ++ 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 2f6f36c59f7bfa8bd764ea21bd8074c8371c0385..ca4499300ac6df8d7772211b74d8ef6649ea297d 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -488,14 +488,23 @@ public class NotificationService { notify(INCOMING_CALL_NOTIFICATION_ID, notification); } - public Notification getOngoingCallNotification(final AbstractJingleConnection.Id id, final Set media) { + public Notification getOngoingCallNotification(final XmppConnectionService.OngoingCall ongoingCall) { + final AbstractJingleConnection.Id id = ongoingCall.id; final NotificationCompat.Builder builder = new NotificationCompat.Builder(mXmppConnectionService, "ongoing_calls"); - if (media.contains(Media.VIDEO)) { + if (ongoingCall.media.contains(Media.VIDEO)) { builder.setSmallIcon(R.drawable.ic_videocam_white_24dp); - builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_video_call)); + if (ongoingCall.reconnecting) { + builder.setContentTitle(mXmppConnectionService.getString(R.string.reconnecting_video_call)); + } else { + builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_video_call)); + } } else { builder.setSmallIcon(R.drawable.ic_call_white_24dp); - builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_call)); + if (ongoingCall.reconnecting) { + builder.setContentTitle(mXmppConnectionService.getString(R.string.reconnecting_call)); + } else { + builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_call)); + } } builder.setContentText(id.account.getRoster().getContact(id.with).getDisplayName()); builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 815182680979ce4f19d2f25095dbb2f275578ed1..42b699e46e806cf95b0048f0fd320930a94cb967 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -1298,8 +1298,8 @@ public class XmppConnectionService extends Service { toggleForegroundService(false); } - public void setOngoingCall(AbstractJingleConnection.Id id, Set media) { - ongoingCall.set(new OngoingCall(id, media)); + public void setOngoingCall(AbstractJingleConnection.Id id, Set media, final boolean reconnecting) { + ongoingCall.set(new OngoingCall(id, media, reconnecting)); toggleForegroundService(false); } @@ -1315,7 +1315,7 @@ public class XmppConnectionService extends Service { final Notification notification; final int id; if (ongoing != null) { - notification = this.mNotificationService.getOngoingCallNotification(ongoing.id, ongoing.media); + notification = this.mNotificationService.getOngoingCallNotification(ongoing); id = NotificationService.ONGOING_CALL_NOTIFICATION_ID; startForeground(id, notification); mNotificationService.cancel(NotificationService.FOREGROUND_NOTIFICATION_ID); @@ -4869,12 +4869,14 @@ public class XmppConnectionService extends Service { } public static class OngoingCall { - private final AbstractJingleConnection.Id id; - private final Set media; + public final AbstractJingleConnection.Id id; + public final Set media; + public final boolean reconnecting; - public OngoingCall(AbstractJingleConnection.Id id, Set media) { + public OngoingCall(AbstractJingleConnection.Id id, Set media, final boolean reconnecting) { this.id = id; this.media = media; + this.reconnecting = reconnecting; } @Override @@ -4882,12 +4884,12 @@ public class XmppConnectionService extends Service { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; OngoingCall that = (OngoingCall) o; - return Objects.equal(id, that.id); + return reconnecting == that.reconnecting && Objects.equal(id, that.id) && Objects.equal(media, that.media); } @Override public int hashCode() { - return Objects.hashCode(id); + return Objects.hashCode(id, media, reconnecting); } } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index e4661a3aa6bf1a528e6d92753502caa3eee228a6..12ba3573310b96018828779ca5c219926181d281 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1484,8 +1484,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web this.stateHistory.add(newState); if (newState == PeerConnection.PeerConnectionState.CONNECTED) { this.sessionDuration.start(); + updateOngoingCallNotification(); } else if (this.sessionDuration.isRunning()) { this.sessionDuration.stop(); + updateOngoingCallNotification(); } final boolean neverConnected = !this.stateHistory.contains(PeerConnection.PeerConnectionState.CONNECTED); @@ -1633,8 +1635,15 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private void updateOngoingCallNotification() { - if (STATES_SHOWING_ONGOING_CALL.contains(this.state)) { - xmppConnectionService.setOngoingCall(id, getMedia()); + final State state = this.state; + if (STATES_SHOWING_ONGOING_CALL.contains(state)) { + final boolean reconnecting; + if (state == State.SESSION_ACCEPTED) { + reconnecting = getPeerConnectionStateAsEndUserState() == RtpEndUserState.RECONNECTING; + } else { + reconnecting = false; + } + xmppConnectionService.setOngoingCall(id, getMedia(), reconnecting); } else { xmppConnectionService.removeOngoingCall(); } @@ -1758,7 +1767,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web return webRTCWrapper.getRemoteVideoTrack(); } - public EglBase.Context getEglBaseContext() { return webRTCWrapper.getEglBaseContext(); } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index dae88c606c81b5ab4e3b6177822ff5a6c257bd45..ff18945336465421f4e79cf0ee9e3e54b42484fd 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -920,6 +920,8 @@ Hang up Ongoing call Ongoing video call + Reconnecting call + Reconnecting video call Disable Tor to make calls Incoming call Incoming call · %s From 51db83d62975d12e060320f3f35207cb1c0ec300 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 8 Jan 2022 11:14:29 +0100 Subject: [PATCH 123/145] pulled translations from transifex --- src/main/res/values-bg/strings.xml | 35 ++++++++++++++++++++------ src/main/res/values-da-rDK/strings.xml | 8 +++++- src/main/res/values-gl/strings.xml | 8 +++--- src/main/res/values-ja/strings.xml | 2 +- 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/main/res/values-bg/strings.xml b/src/main/res/values-bg/strings.xml index 41b22aed687736e55b8e43a93620d0a78b1c9fce..1530e1630e4dc655db119b1fa4df48f8cda08f05 100644 --- a/src/main/res/values-bg/strings.xml +++ b/src/main/res/values-bg/strings.xml @@ -101,7 +101,7 @@ Изпращане нешифровано Неуспешно дешифроване. Възможно е да нямате правилния частен ключ. OpenKeychain - OpenKeychain, за да шифрова и дешифрова съобщенията и да управлява публичните Ви ключове.OpenKeychain е с лиценз GPLv3+ и може се свали от F-Droid и Google Play.

(Моля, рестартирайте %1$s след това.)]]>
+ OpenKeychain, за да шифрова и дешифрова съобщенията и да управлява публичните Ви ключове.

OpenKeychain е под лиценза GPLv3+ и може се свали от F-Droid и Google Play.

(Моля, рестартирайте %1$s след това.)]]>
Рестартиране Инсталиране Моля, инсталирайте OpenKeychain @@ -188,7 +188,7 @@ XMPP адрес username@example.com Парола - Това не е валиден XMPP адрес + Това не е правилен XMPP адрес Няма достатъчно памет. Изображението е твърде голямо. Искате ли да добавите %s към адресния си указател? Инф. за сървъра @@ -351,11 +351,11 @@ Отхвърлен Член Разширен режим - Дай членски привилегии - Премахни членски привилегии - Даване на администраторски права + Даване на правомощия на член + Премахване на правомощията на член + Даване на правомощия на администратор Отмяна на администраторските права - Дай права на собственик + Даване на правомощия на собственик Премахване от груповия разговор Неуспешна промяна на принадлежността на %s Забраняване на достъпа до груповия разговор @@ -448,6 +448,8 @@ %d съобщения
Зареждане на още съобщения + Дайте на %1$s разрешение за достъп до външната памет + Дайте на %1$s разрешение за достъп до камерата Синхронизиране с контактите
Ние няма да пазим копия на тези телефонни номера.\n\nЗа повече информация, прочетете декларацията ни за поверителност.

Сега ще Ви помолим да дадете достъп до контактите си.]]>
Известяване за всички съобщения @@ -490,6 +492,9 @@ Поверителност Тема Изберете цветовата схема + Автоматично + Светла + Тъмна Зелен фон Получените съобщения ще бъдат на зелен фон Това устройство вече не се използва @@ -505,6 +510,7 @@ Няма позволение за достъп до %s Отдалеченият сървър не е намерен Времето за изчакване на отдалечения сървър изтече + Докладване този XMPP адрес за спам. Изтриване на идентификаторите OMEMO Изтриване на избраните ключове. Трябва да бъдете свързан(а), за да публикувате аватара си. @@ -563,6 +569,7 @@ Съответстващите разговори са затворени. Контактът е блокиран. Известия от непознати + Известяване за съобщения и обаждания от непознати. Получено е съобщение от непознат Блокиране на непознатия Блокиране на целия домейн @@ -572,11 +579,14 @@ Механизмът на SASL е понижен Сървърът изисква регистриране чрез уеб сайт Отваряне на уеб сайта + Няма намерено приложение за отваряне на уеб сайта Изскачащи известия + Показване на изскачащи известия Днес Вчера Проверка на името на сървъра чрез DNSSEC Сървърните сертификати, които съдържат проверено име на сървъра, се смятат за потвърдени + Сертификатът не съдържа XMPP адрес частично Запис на видео Копиране в буфера @@ -598,6 +608,9 @@ Редактиране на съобщението за състоянието Редактиране на съобщението за състоянието Изключване на шифроването + %1$s не може да изпраща шифровани съобщения до %2$s. Възможно е Вашият контакт да използва остарял сървър или клиент, който не може да работи с OMEMO. + Неуспешно получаване на списъка с устройства + Неуспешно получаване на ключове за шифроване Съвет: В някои случаи това може да се оправи, ако се добавите един друг в списъците си с контакти. Наистина ли искате да изключите шифроването чрез OMEMO за този разговор?\nТова ще позволи на администратора Ви да чете съобщенията Ви, но пък най-вероятно е единственият начин за общуване с хората, използващи стари клиенти. Изключване сега @@ -626,13 +639,16 @@ Споделяне на местоположението Показване на местоположението Споделяне + Записът не може да започне Моля, изчакайте… + Дайте на %1$s разрешение за достъп до микрофона Търсене в съобщенията GIF Преглед на разговора Разширение за споделяне на местоположението Използване на разширението за споделяне на местоположението вместо вградената карта Копиране на уеб адрес + Копиране на XMPP адрес Споделяне на файлове през HTTP за S3 Директно търсене На екрана за „Започване на разговор“ да се отваря клавиатурата и да се поставя курсорът в полето за търсене @@ -645,12 +661,17 @@ Въвеждането на име не е задължитално Име на груповия разговор Този групов разговор е унищожен + Записът не може да бъде запазен Услуга на преден план + Тази категория известия се използва за показване на постоянно известие, което показва, че %1$s работи. Информация за състоянието Проблеми с връзката Тази категория известия се използва за показване на известие, в случай че има проблем със свързването с профил. Съобщения + Обаждания Съобщения + Входящи обаждания + Изходящи обаждания Тихи съобщения Тази категория известия се използва за показване на известия, които не бива да изпълняват звук. Това може да се използва, например, докато използвате друго устройство (по време на Период на пренебрегване). Важност, звук, вибрация @@ -723,6 +744,6 @@ Създаване на групов разговор XMPP адрес Присъединихте се към съществуващ канал - Добави съществуващ профил + Добавяне на съществуващ профил Зает diff --git a/src/main/res/values-da-rDK/strings.xml b/src/main/res/values-da-rDK/strings.xml index bbcd082f012f82265ef57b282c04e76c3fe28f48..8c06e3f505ebb330eacb50de53220675cb66ed99 100644 --- a/src/main/res/values-da-rDK/strings.xml +++ b/src/main/res/values-da-rDK/strings.xml @@ -132,6 +132,8 @@ Ved at indsende \"stack traces\" hjælper du udviklingen Bekræft beskeder Lad dine kontakter vide når du har modtaget og læst deres beskeder + Forbyd skærmbillede + Skjul app indhold i app-skifteren og bloker skærmbilleder UI OpenKeychain producerede en fejl Dårlig nøgle til kryptering @@ -414,6 +416,7 @@ lyd video billede + vektorgrafik PDF dokument Android App Kontakt @@ -912,6 +915,7 @@ Forbindelsen tabt Tilbagetrukket opkald App fejl + Bekræftelsesproblem Læg på Udgående opkald Igangværende videoopkald @@ -963,4 +967,6 @@ Ingen aktive konti understøtter denne funktion Sikkerhedskopieringen er startet. Du får en notifikation, når den er afsluttet. Kunne ikke aktivere video. - + Ren tekstdokument + + diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index b272a1b4a02c8b1ba12190bf42b9ee84f61c07f9..fb320cfaa6f7dff739cf419c553ff114cc982236 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -89,7 +89,7 @@ Eliminar historial da conversa ¿Queres eliminar as mensaxes desta conversa?\n\nAviso: Esto non lle afecta as mensaxes gardadas noutros dispositivos ou servidores. Eliminar ficheiro - Está segura de querer eliminar este ficheiro?\n\nAviso: Esto non eliminará as copias de este ficheiro que están gardadas en outros dispositivos ou servidores. + Tes a certeza de querer eliminar este ficheiro?\n\nAviso: Esto non eliminará as copias de este ficheiro que están gardadas noutros dispositivos ou servidores. Pechar a conversa tras baleirar Escoller dispositivo Enviar mensaxe non cifrada @@ -244,7 +244,7 @@ Eliminar marcador Destruír a conversa en grupo Eliminar canle - Está segura de querer destruír esta conversa en grupo?\n\nAviso: A conversa en grupo será totalmente eliminada do servidor. + Tes a certeza de querer destruír esta conversa en grupo?\n\nAviso: A conversa en grupo será totalmente eliminada do servidor. Tes a certeza de querer eliminar a canle?\n\nAviso: A canle será eliminada completamente do servidor. Non se desfixo a conversa en grupo Non se puido eliminar a canle @@ -688,7 +688,7 @@ ¿Aceptar certificado descoñecido? O certificado do servidor non está asinado por unha autoridade de certificación coñecida. ¿Aceptar un nome de servidor que non coincida? - O servidor non pode autenticarse como \"%s\". O certificado só é válido para: + O servidor non pode autenticarse como \"%s\". O certificado só é válido para: Queres conectarte de todos os xeitos? Detalles do certificado: Unha vez @@ -828,7 +828,7 @@ Rexeitar solicitude Instalar Orbot Iniciar Orbot - Non ten loxa de aplicacións instalada. + Non hai tenda de apps instalada. Esta canle fará público o teu enderezo XMPP e-book Orixinal (non comprimido) diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 9037dd4711375ad7a81a655333ff5d3331815c1e..9963796ae7e1605246277c72e917b01459a17e0c 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -130,7 +130,7 @@ メッセージを確認 あなたがメッセージを受信して読んだときに、連絡先に知らせる スクリーンショットを防ぐ - アプリスイッチャーでアプリの内容を隠し、スクリーンショットを防ぐ + アプリスイッチャー内でアプリの内容を隠し、スクリーンショットを防ぐ UI OpenKeychain でエラーが発生しました。 暗号化の鍵が不正です。 From 666ca485dbd0e00ac26b32fddcec295c15a7dc15 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 13 Jan 2022 20:58:47 +0100 Subject: [PATCH 124/145] pulled translations from transifex --- src/conversations/res/values-sv/strings.xml | 11 +++-- src/main/res/values-bg/strings.xml | 48 ++++++++++++++++++++- src/main/res/values-de/strings.xml | 2 +- src/main/res/values-sv/strings.xml | 3 ++ 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/conversations/res/values-sv/strings.xml b/src/conversations/res/values-sv/strings.xml index e898b56434324ef4b5a1d41cbddbcd3c7cd05fb3..a6185650eade287dfa1828f7d5d0436db8fc0bdb 100644 --- a/src/conversations/res/values-sv/strings.xml +++ b/src/conversations/res/values-sv/strings.xml @@ -1,8 +1,13 @@ - Välj din XMPP leverantör + Välj din XMPP-leverantör Använd conversations.im - Skapa nytt konto - Din server inbjudan + Skapa ett nytt konto + Har du redan ett XMPP-konto? Detta kan vara fallet om du redan använder en annan XMPP-klient eller om du har använt Conversations tidigare. Om inte, kan du skapa ett nytt XMPP-konto på en gång.\nTips: Vissa e-postleverantörer tillhandahåller även XMPP-konton. + Din serverinbjudan + Felaktigt formaterad provisioneringskod + Tryck på dela-knappen för att skicka en inbjudan till din kontakt till %1$s. + Om din kontakt är i närheten, kan de också skanna koden nedan för att acceptera din inbjudan. + Gå med %1$s och chatta med mig: %2$s Dela inbjudan med... \ No newline at end of file diff --git a/src/main/res/values-bg/strings.xml b/src/main/res/values-bg/strings.xml index 1530e1630e4dc655db119b1fa4df48f8cda08f05..c1465e59ce3e64972b141eb19da3f9a4104be2b9 100644 --- a/src/main/res/values-bg/strings.xml +++ b/src/main/res/values-bg/strings.xml @@ -59,6 +59,7 @@ Споделяне с… Започване на разговор Поканете контакт + Поканете Контакти Контакт Отказ @@ -164,6 +165,7 @@ Потребителското име е заето Регистрацията е завършена Регистрацията не се поддържа от сървъра + Неправилен регистрационен идентификатор Договарянето чрез TLS беше неуспешно Домейнът не може да се провери Нарушение на политиката @@ -182,10 +184,11 @@ Наистина ли искате да премахнете своя публичен OpenPGP ключ от известяването си за присъствие?\nКонтактите Ви вече няма да могат да Ви изпращат съобщение, шифровани чрез OpenPGP. Публичният OpenPGP ключ е публикуван. Активиране на профила - Сигурни ли сте? + Наистина ли искате това? Изтриването на профила Ви ще изтрие и цялата история на разговорите Ви Запис на глас XMPP адрес + Блокиране на XMPP адрес username@example.com Парола Това не е правилен XMPP адрес @@ -224,6 +227,7 @@ Изтегляне на ключове… Готово Дешифроване + Отметки Търсене Въведете контакт Изтриване на контакта @@ -274,8 +278,10 @@ Включване Груповият разговор изисква парола Въведете парола + Моля, първо помолете контакта за актуализации на присъствието му.\n\nТова ще бъде използвано, за да се провери какво приложение използва контактът. Поискване сега Пренебрегване + Внимание: Изпращането на това без съвместни актуализации на присъствието може да доведе до неочаквани проблеми.\n\nПогледнете подробностите за контакта, за да проверите дали сте абониран за актуализации на присъствието. Сигурност Позволяване на поправянето на съобщения Позволяване на контактите да редактират съобщенията си след като са ги изпратили. @@ -289,6 +295,8 @@ Известията ще бъдат заглушени по време на тихите часове Други Синхронизиране с отметките + Автоматично присъединяване към групови разговори, ако такава е настройката на отметката + Отпечатъкът OMEMO е копиран Достъпът Ви до този групов разговор е забранен Този групов разговор е само за членове Ограничение на ресурса @@ -307,6 +315,7 @@ Повторно изпращане Адрес на файла Копиране на адреса + XMPP-адресът е копиран Съобщението за грешка е копирано уеб адрес Сканиране на 2-измерен баркод @@ -317,6 +326,14 @@ Повторен опит Услуга на преден план Предотвратява прекъсването на връзката Ви от операционната система + Създаване на резервно копие + Резервните копия ще се пазят в %s + Създаване на резервни копия + Резервното копие е създадено + Файловете на резервното копие бяха запазени в %s + Възстановяване от резервно копие + Възстановяването от резервно копие е завършено + Не забравяйте да включите профила. Изберете файл Получаване на %1$s (%2$d%% завършено) Сваляне на %s @@ -324,16 +341,27 @@ файл Отваряне на %s изпращане (%1$d%% завършено) + Файлът се подготвя за споделяне %s е предложен за сваляне Отказ на прехвърлянето + файлът не може да бъде споделен + изпращането на файла е отменено + Файлът е изтрит + Няма намерено приложение за отваряне на файла + Няма намерено приложение за отваряне на връзката + Няма намерено приложение за преглед на контакта Динамични етикети Показване на етикети, предназначени само за четене под контактите Включване на известията Не е открит сървър за груповия разговор + Груповият разговор не може да бъде създаден Аватар на профила Копиране на отпечатъка OMEMO Повторно създаване на ключа OMEMO Премахване на устройствата + Наистина ли искате да премахнете всички останали устройства от обявлението OMEMO? Следващия път, когато устройствата Ви се свържат, те ще обявят себе си отново, но може да не получат съобщенията, изпратени междувременно. + Няма ключове, които могат да бъдат използвани за този контакт.\nОт сървъра не могат да бъдат изтеглени нови ключове. Възможно е да има проблем със сървъра на контакта Ви. + Няма ключове, които могат да бъдат използвани за този контакт.\nУверете се, че и двамата имате абонамент за присъствието. Нещо се обърка Получаване на историята от сървъра Няма повече история на сървъра @@ -343,6 +371,7 @@ Промяна на паролата Текуща парола Нова парола + Паролата не може да е празна Активиране на всички профили Деактивиране на всички профили Изпълнение на действието с @@ -356,9 +385,13 @@ Даване на правомощия на администратор Отмяна на администраторските права Даване на правомощия на собственик + Премахване на правомощията на собственик Премахване от груповия разговор + Премахване от канала Неуспешна промяна на принадлежността на %s Забраняване на достъпа до груповия разговор + Забраняване на достъпа до канала + Опитвате се да премахнете%s от публичен канал. Единственият начин да направите това е да блокирате завинаги потребителя. Забраняване на достъпа сега Неуспешна промяна на ролята на %s Частно, само за членове @@ -411,6 +444,7 @@ Използвани наскоро Изберете бързо действие Търсене в контактите + Търсене в отметките Изпращане на лично съобщение Потребителско име Потребителско име @@ -732,6 +766,7 @@ Инсталиране на Orbot Пускане на Orbot Няма инсталирано приложение за инсталиране на приложения. + Този канал ще направи Вашия XMPP-адрес публичен е-книга Оригинално (некомпресирано) Отваряне с… @@ -742,8 +777,19 @@ Въведете паролата си за профила %s, за да направите възстановяване от резервно копие. Не използвайте възможността за възстановяване от резервно копие, за да клонирате (да изпълнявате едновременно) инсталацията. Възстановяването от резервно копие е предназначено за мигриране или в случай, че сте загубили устройството си. Създаване на групов разговор + Присъединяване към публичен канал + Създаване на публичен канал XMPP адрес + Създаване на публичен канал… Присъединихте се към съществуващ канал + Собствениците могат да канят други хора. + Всеки може да кани други хора. + В този публичен канал няма никакви участници. Поканете контактите си или използвайте бутона за споделяне, за да разпространите XMPP-адреса на канала. Добавяне на съществуващ профил + Присъединяване към публичен канал… + Повечето потребители трябва да изберат „jabber.network“ за по-добри предложения от цялата публична екосистема на XMPP. Зает + Поканата не може да бъде анализирана + Сървърът не поддържа създаването на покани + Видеото не може да бъде включено. diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index c5a7e680b494bf2d28f5094c77492487e086cd1d..4318db444e92f9c918da5549539cbd90615df9c7 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -270,7 +270,7 @@ an %s Private Nachricht an %s senden Verbinden - Das Konto existiert bereits + Dieses Konto existiert bereits Weiter Sitzung wiederhergestellt Überspringen diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index 96b77ff575162f534fa1c03de767e6fd5d689a0a..35666eb4a6cf25867f576d8b67059bd24454d8d2 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -71,11 +71,13 @@ Avblockera Spara Ok + %1$s har kraschat Skicka nu Fråga aldrig igen Kunde inte ansluta till konto Kunde inte ansluta till flera konton Bifoga fil + Vill du lägga till den här saknade kontakten i din kontaktlista? Lägg till kontakt sändning misslyckades Förbereder att skicka bild @@ -135,6 +137,7 @@ Ta ny bild Tillåt abonnemangsbegäran i förväg Filen du valt är inte en bild + Det gick inte att konvertera bildfilen Filen hittas ej Generellt I/O-fel. Du kanske fick slut på plats? Okänd From 68fd17778ccd16a66f164ae1bb19a83e6d550a4b Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 18 Jan 2022 09:48:10 +0100 Subject: [PATCH 125/145] bump agp version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a95b2ff8e3f3c23857e93c07d9712a3ea99950a9..e4d037114f71a7f9c18412f84032153434adba70 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' + classpath 'com.android.tools.build:gradle:7.0.4' } } From eed5c5e74319520e7541102f906e02d81849ad62 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 18 Jan 2022 09:49:10 +0100 Subject: [PATCH 126/145] add additional logging to image compression --- .../java/eu/siacs/conversations/persistance/FileBackend.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 48ffae422f1741aae56e680255e8b4d074f0226b..a4644fd56006b71893e50f92d7910795d89d7e4d 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -746,12 +746,15 @@ public class FileBackend { final int imageMaxSize = mXmppConnectionService.getResources().getInteger(R.integer.auto_accept_filesize); while (!targetSizeReached) { os = new FileOutputStream(file); + Log.d(Config.LOGTAG, "compressing image with quality " + quality); boolean success = scaledBitmap.compress(Config.IMAGE_FORMAT, quality, os); if (!success) { throw new FileCopyException(R.string.error_compressing_image); } os.flush(); - targetSizeReached = file.length() <= imageMaxSize || quality <= 50; + final long fileSize = file.length(); + Log.d(Config.LOGTAG, "achieved file size of " + fileSize); + targetSizeReached = fileSize <= imageMaxSize || quality <= 50; quality -= 5; } scaledBitmap.recycle(); From b6442c0bd45f13ae701fa42863338b6192ee91e7 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 18 Jan 2022 11:30:17 +0100 Subject: [PATCH 127/145] add Samsung S4 to hardware aec blacklist fixes #4267 --- .../eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 6722f9f2ca93bf443a3b0b335ca6f16373ab84fc..0b990db43e533e21197b1b12a4b87b2aa4a889ac 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -64,8 +64,7 @@ public class WebRTCWrapper { private static final String EXTENDED_LOGGING_TAG = WebRTCWrapper.class.getSimpleName(); private final ExecutorService executorService = Executors.newSingleThreadExecutor(); - - //we should probably keep this in sync with: https://github.com/signalapp/Signal-Android/blob/master/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java#L296 + private static final Set HARDWARE_AEC_BLACKLIST = new ImmutableSet.Builder() .add("Pixel") .add("Pixel XL") @@ -79,6 +78,9 @@ public class WebRTCWrapper { .add("Redmi Note 5") .add("FP2") // Fairphone FP2 .add("MI 5") + .add("GT-I9515") // Samsung Galaxy S4 Value Edition (jfvelte) + .add("GT-I9515L") // Samsung Galaxy S4 Value Edition (jfvelte) + .add("GT-I9505") // Samsung Galaxy S4 (jfltexx) .build(); private static final int CAPTURING_RESOLUTION = 1920; From f2a67f899b5f4d5fbe1419f1b0568d9e84c35921 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 9 Feb 2022 12:17:29 +0100 Subject: [PATCH 128/145] pulled translations from transifex --- src/conversations/res/values-bg/strings.xml | 10 +- src/conversations/res/values-fi/strings.xml | 14 + src/main/res/values-bg/strings.xml | 188 +++- src/main/res/values-de/strings.xml | 14 +- src/main/res/values-es/strings.xml | 8 +- src/main/res/values-fi/strings.xml | 918 ++++++++++++++++++++ src/main/res/values-ru/strings.xml | 7 +- src/main/res/values-sv/strings.xml | 128 +++ src/quicksy/res/values-bg/strings.xml | 2 +- src/quicksy/res/values-fi/strings.xml | 12 + 10 files changed, 1279 insertions(+), 22 deletions(-) create mode 100644 src/conversations/res/values-fi/strings.xml create mode 100644 src/main/res/values-fi/strings.xml create mode 100644 src/quicksy/res/values-fi/strings.xml diff --git a/src/conversations/res/values-bg/strings.xml b/src/conversations/res/values-bg/strings.xml index 2981a6542b783e934c509c4b4c433ec53929541e..7ef32e0252a6fca28c58aafd24b9caf54b85e213 100644 --- a/src/conversations/res/values-bg/strings.xml +++ b/src/conversations/res/values-bg/strings.xml @@ -1,13 +1,13 @@ - Изберете вашият XMPP доставчик + Изберете своя XMPP доставчик Използвайте conversations.im Създаване не нов профил - Имате ли вече XMPP профил? Това може да се случи, ако вече използвате друг клиент на XMPP или сте използвали преди това Conversations. Ако не, можете да създадете нов XMPP профил в момента.\nСъвет: Някои доставчици на имейл също предоставят XMPP профили. + Имате ли вече XMPP профил? Може да имате, ако вече използвате друг клиент на XMPP или сте използвали Conversations и преди. Ако не, можете да създадете нов XMPP профил сега.\nСъвет: някои доставчици на е-поща също предоставят XMPP профили.   - XMPP е мрежа за общуване чрез мигновени съобщения, която не е обвързана с конкретен доставчик. Можете да използвате клиента с всеки сървър, който работи с XMPP.\nЗа Ваше удобство, ние предоставяме лесен начин да си създадете профил в conversations.im¹ — сървър, пригоден да работи добре с Conversations. - Бяхте поканен(а) в %1$s. Ще Ви преведем през процеса на създаване на акаунт.\nИзбирайки %1$s за доставчик, Вие ще можете да общувате и с потребители на други доставчици, като им предоставите своя пълен адрес за XMPP. - Бяхте поканен(а) в %1$s. Вече Ви избрахме потребителско име. Ще Ви преведем през процеса на създаване на акаунт.\nЩе можете да общувате и с потребители на други доставчици, като им предоставите своя пълен адрес за XMPP. + XMPP е мрежа за общуване чрез мигновени съобщения, която не е обвързана с конкретен доставчик. Можете да използвате клиента с всеки сървър, който работи с XMPP.\nЗа Ваше удобство, обаче, ние предоставяме лесен начин да си създадете профил в conversations.im¹ — сървър, пригоден да работи най-добре с Conversations. + Получихте покана за %1$s. Ще Ви преведем през процеса на създаване на профил.\nИзбирайки %1$s за доставчик, Вие ще можете да общувате и с потребители на други доставчици, като им предоставите своя пълен XMPP адрес. + Получихте покана за %1$s. Вече Ви избрахме потребителско име. Ще Ви преведем през процеса на създаване на профил.\nЩе можете да общувате и с потребители на други доставчици, като им предоставите своя пълен XMPP адрес. Вашата покана за сървъра Неправилно форматиран код за достъп Докоснете бутона за споделяне, за да изпратите на контакта си покана за %1$s. diff --git a/src/conversations/res/values-fi/strings.xml b/src/conversations/res/values-fi/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..17c75a2977d6777910f2834fe9f874800b1462c2 --- /dev/null +++ b/src/conversations/res/values-fi/strings.xml @@ -0,0 +1,14 @@ + + + Valitse XMPP-palveluntarjoaja + Käytä conversations.im:ää + Luo uusi tili + Onko sinulla jo XMPP-tunnus? Jos käytät jo toista XMPP-sovellusta tai olet käyttänyt Conversationsia aiemmin, niin voi olla. Jos ei, voit tehdä uuden XMPP-tilin saman tien.\nVinkki: Jotkin sähköpostipalvelut tarjoavat myös XMPP-tilin. + XMPP on tietystä palveluntarjoasta riippumaton pikaviestiverkosto. Voit käyttää tätä asiakasohjelmaa minkä tahansa haluamasi XMPP-palvelimen kanssa.\nHelppouden nimissä olemme kuitenkin helpottaneet tilin luomista conversations.im:iin. + Sinut on kutsuttu %1$s:iin. Opastamme sinua tilin luomisen kanssa.\nValitessasi palvelimen %1$s palveluntarjoajaksesi voit jutella muiden palveluntajoajien käyttäjien kanssa kertomalla heille koko XMPP-osoitteesi. + Sinut on kutsuttu palvelimelle %1$s. Käyttäjänimesi on valittu valmiiksi puolestasi. Opastamme sinua tilin luomisen kanssa.\nVoit jutella muiden palveluntarjoajien käyttäjien kanssa kertomalle heille koko XMPP-osoitteesi. + Kutsusi palvelimelle + Virheellisesti muotoiltu koodi + Jos henkilö on lähellä, hän voi myös hyväksyä kutsun lukemalla allaolevan koodin. + Jaa kutsu sovelluksella... + \ No newline at end of file diff --git a/src/main/res/values-bg/strings.xml b/src/main/res/values-bg/strings.xml index c1465e59ce3e64972b141eb19da3f9a4104be2b9..ee4687dfa13d9d9e38e55294d4fb48dc86470cad 100644 --- a/src/main/res/values-bg/strings.xml +++ b/src/main/res/values-bg/strings.xml @@ -263,7 +263,7 @@ Публикуване… Сървърът отказа Вашето публикуване Снимката Ви не може да бъде преобразувана - Неуспешно запазване на аватара на диска + Аватарът не може да бъде запазен на диска (Или задръжте, за да върнете началното) Сървърът Ви не поддържа публикуване на аватари прошепна @@ -367,7 +367,7 @@ Няма повече история на сървъра Актуализиране… Паролата е променена! - Неуспешна промяна на паролата + Паролата не може да бъде променена Промяна на паролата Текуща парола Нова парола @@ -388,16 +388,20 @@ Премахване на правомощията на собственик Премахване от груповия разговор Премахване от канала - Неуспешна промяна на принадлежността на %s + Принадлежността на %s не може да бъде променена Забраняване на достъпа до груповия разговор Забраняване на достъпа до канала Опитвате се да премахнете%s от публичен канал. Единственият начин да направите това е да блокирате завинаги потребителя. Забраняване на достъпа сега - Неуспешна промяна на ролята на %s + Ролята на %s не може да бъде променена + Настройка на частен групов разговор + Настройка на публичен групов разговор Частно, само за членове + Нека XMPP адресите бъдат видими за всички + Нека каналът да се модерира Вие не участвате Настройките на груповия разговор бяха променени! - Неуспешна промяна на настройките на груповия разговор + Настройките на груповия разговор не могат да бъдат променени Никога До отмяна Отлагане @@ -405,11 +409,13 @@ Отбелязване като прочетено Въвеждане Enter изпраща + Използвайте клавиша Enter, за да изпратите съобщение. Винаги може да използвате Ctrl+Enter за изпращане на съобщение, дори тази настройка да е изключена. Показване на клавиша Enter Смяна на клавиша за емотикони с клавиша Enter аудио видео изображение + векторна графика PDF документ Приложение за Андроид Контакт @@ -425,8 +431,11 @@ Така контактите Ви ще разбират, когато им пишете съобщения Изпращане на местоположението Показване на местоположението + Няма намерено приложение за показване на местоположението Местоположение Conversation се затвори + Напуснахте частния групов разговор + Напуснахте публичния канал Да не се вярва на системните сертификати Всички сертификати трябва да бъдат одобрени на ръка Премахване на сертификатите @@ -439,6 +448,7 @@ %d сертификат е изтрит %d сертификата са изтрити
+ Замяна на бутона „Изпращане“ с бързо действие Бързо действие Нищо Използвани наскоро @@ -446,6 +456,7 @@ Търсене в контактите Търсене в отметките Изпращане на лично съобщение + %1$s напусна груповия разговор Потребителско име Потребителско име Това не е правилно потребителско име @@ -455,16 +466,28 @@ Неуспешно сваляне: Файлът не може да бъде записан Мрежата на Тор е недостъпна Грешка при свързване + Сървърът не отговаря за този домейн Повредено Присъствие + Отсъстващ при заключено устройство + Показване като „отсъстващ“, когато устройството е заключено + Зает в тих режим + Показване като „зает“, когато устройството е в тих режим Тих режим при режим на вибриране + Показване като „зает“, когато устройството е на вибрация Разширени настройки за връзката Показване на настройките за сървър и порт при установка на профил xmpp.example.com + Влизане със сертификат + Сертификатът не може да бъде прочетен Настройки за архивирането Настройки за архивирането на сървъра Получаване на настройките за архивирането. Моля, изчакайте… + Настройките за архивирането не могат да бъдат получени + Проверката е задължителна Въведете текста от горното изображение + Недоверен верижен сертификат + XMPP адресът не съответства на сертификата Подновяване на сертификата Грешка при получаването на ключа за OMEMO! Ключът за OMEMO беше потвърден със сертификат! @@ -474,6 +497,7 @@ Всички връзки да минават през мрежата на Тор. Изисква Орбот Име на сървър Порт + Адрес на сървър или .onion Това не е правилен номер на порт Това не е правилно име на сървър %1$d от %2$d свързани профила @@ -482,25 +506,41 @@ %d съобщения Зареждане на още съобщения + Файлът е споделен с %s + Изображението е споделено с %s + Изображенията са споделени с %s + Текстът е споделен с %s Дайте на %1$s разрешение за достъп до външната памет Дайте на %1$s разрешение за достъп до камерата Синхронизиране с контактите + %1$s иска разрешение за достъп до адресната Ви книга, за да потърси съвпадения със списъка от контакти в XMPP.\nТова ще покаже пълните имена и аватари на контактите Ви.\n\n%1$s само ще прочете адресната книга и ще потърси съвпадения на това устройство – нищо няма да се качва на сървъра Ви.
Ние няма да пазим копия на тези телефонни номера.\n\nЗа повече информация, прочетете декларацията ни за поверителност.

Сега ще Ви помолим да дадете достъп до контактите си.]]>
Известяване за всички съобщения Известяване само при споменаване Известията са изключени Известията са спрени временно Компресия на изображенията + Съвет: използвайте „Изберете файл“ вместо „Изберете снимка“, за да изпращате снимките некомпресирани, независимо от тази настройка. Винаги + Само за големи изображения Оптимизациите за използв. на батерията са вкл. + Устройството Ви прилага сериозни оптимизации за използването на батерията върху %1$s, които може да доведат до забавени известия и дори пропуснати съобщения.\nПрепоръчително е да ги изключите. + Устройството Ви прилага сериозни оптимизации за използването на батерията върху %1$s, които може да доведат до забавени известия и дори пропуснати съобщения.\nСега ще бъдете помолен(а) да ги изключите. Изключване Избраната област е твърде голяма (Няма активирани профили) Това поле е задължително Поправяне на съобщението Изпращане на поправеното съобщение + Вече сте потвърдили доверието си в този човек, чрез защитена проверка на отпечатъка му. Ако изберете „Готово“, ще потвърдите само това, че %s е част от този групов разговор. Вие сте деактивирали този профил + Грешка в сигурността: неправилен достъп до файл! + Няма намерено приложение за споделяне на адреса Споделяне на адреса с… +
Трябва да се регистрирате чрез телефонния си номер, след което Quicksy автоматично ще претърси телефонните номера в указателя Ви и ще Ви предложи контакти в приложението.

Регистрирайки се, Вие се съгласявате с нашата декларация за поверителност.]]>
+ Съгласяване и продължаване + На conversations.im има ръководство за създаване на профил.\nИзбирайки conversations.im за доставчик, Вие ще можете да общувате и с потребители на други доставчици, като им предоставите своя пълен адрес за XMPP. + Пълният Ви XMPP адрес ще бъде: %s Създаване на профил Използване на собствен доставчик Изберете потребителското си име @@ -523,6 +563,8 @@ Кратко Средно Дълго + Информиране за използването + Позволява на контактите Ви да знаят кога използвате Conversations Поверителност Тема Изберете цветовата схема @@ -531,6 +573,7 @@ Тъмна Зелен фон Получените съобщения ще бъдат на зелен фон + Свързването с OpenKeychain е невъзможно Това устройство вече не се използва Компютър Мобилен телефон @@ -538,21 +581,29 @@ Браузър Конзола Изисква се плащане + Дайте разрешение за достъп до Интернет Аз Контакт моли за абонамент за присъствието Позволяване Няма позволение за достъп до %s Отдалеченият сървър не е намерен Времето за изчакване на отдалечения сървър изтече + Профилът не може да бъде обновен Докладване този XMPP адрес за спам. Изтриване на идентификаторите OMEMO + Пресъздайте своите ключове OMEMO. Всички Ваши контакти ще трябва да Ви потвърдят отново. Използвайте това само в краен случай. Изтриване на избраните ключове. Трябва да бъдете свързан(а), за да публикувате аватара си. Показване на грешка Съобщение за грешка - Съхранението на данни е включено + Пестенето на данни е включено + Операционната Ви система не позволява на %1$s да се свързва с Интернет когато работи на заден фон. За да получавате известия за новите съобщения, трябва да дадете на %1$s неограничен достъп когато пестенето на данни е включено.\n%1$s ще продължи да се опитва да записва данните когато е възможно. + Устройството Ви не поддържа изключването на пестенето на данни за %1$s. + Не може да се създаде временен файл Това устройство е потвърдено Копиране на отпечатъка + Потвърдили сте всички ключове OMEMO, които притежавате + Баркодът не съдържа отпечатъци за този разговор. Потвърдени отпечатъци Използвайте камерата, за да сканирате баркода на контакт Моля, изчакайте получаването на ключовете @@ -560,8 +611,11 @@ Споделяне като адрес на XMPP Споделяне като връзка в Интернет Доверяване на сляпо преди потвърждение + Нови устройства на непотвърдени контакти автоматично получават доверие, но нови устройства на потвърдени контакти изискват ръчно потвърждаване. + Доверени на сляпо ключове OMEMO, което означава, че това може да е някой друг, или че някой може да е получил неправомерен достъп. Неприети Грешен 2-измерен баркод + Изчистване на папката с кеша (използвана от камерата) Изчистване на кеша Изчистване на личното място за съхранение Изчистване на мястото, където се съхраняват личните файлове. (Те могат да бъдат повторно изтеглени от сървъра.) @@ -571,6 +625,7 @@ Показване на неактивните Скриване на неактивните Сваляне на доверието + Наистина ли искате да заличите потвърждението на това устройство?\nТова устройство и съобщенията от него ще бъдат отбелязани като „недоверени“. %d секунда %d секунди @@ -708,9 +763,13 @@ Изходящи обаждания Тихи съобщения Тази категория известия се използва за показване на известия, които не бива да изпълняват звук. Това може да се използва, например, докато използвате друго устройство (по време на Период на пренебрегване). + Неуспешни доставяния + Настройки на известията за съобщения + Настройки на известията за обаждания Важност, звук, вибрация Компресия на видеото Преглед на медийното съдържание + Участници Разглеждане на медийното съдържание Файлът е пропуснат поради нарушение на сигурността. Качество на видеото @@ -748,6 +807,9 @@ Кодът, който Ви изпратихме, е с изтекла давност. Неизвестна мрежова грешка. Непознат отговор от сървъра. + Свързването със сървъра е невъзможно. + Установяването на защитена връзка е невъзможно. + Сървърът не може да бъде намерен. Нещо се обърка при обработването на заявката Ви. Неправилно въведени данни Временно недостъпно. Опитайте отново по-късно. @@ -776,20 +838,132 @@ Възстановяване Въведете паролата си за профила %s, за да направите възстановяване от резервно копие. Не използвайте възможността за възстановяване от резервно копие, за да клонирате (да изпълнявате едновременно) инсталацията. Възстановяването от резервно копие е предназначено за мигриране или в случай, че сте загубили устройството си. + Не може да се извърши възстановяване от резервно копие. + Резервното копие не може да бъде дешифрирано. Правилна ли е паролата? + Резервни копия и възстановяване + Въведете XMPP адрес Създаване на групов разговор Присъединяване към публичен канал + Създаване на частен групов разговор Създаване на публичен канал + Име на канала XMPP адрес + Моля, задайте име за канала + Моля, задайте XMPP адрес + Това е XMPP адрес. Моля, задайте име. Създаване на публичен канал… + Този канал вече съществува Присъединихте се към съществуващ канал + Настройката на канала не може да бъде запазена + Нека всеки може да редактира темата + Нека всеки може да кани други хора + Всеки може да редактира темата. + Собствениците могат да редактират темата. + Администраторите могат да редактират темата. Собствениците могат да канят други хора. Всеки може да кани други хора. + XMPP адресите са видими за администраторите. + XMPP адресите са видими за всички. В този публичен канал няма никакви участници. Поканете контактите си или използвайте бутона за споделяне, за да разпространите XMPP-адреса на канала. + В този частен групов разговор няма никакви участници. + Управление на правомощията + Търсене на участници + Файлът е твърде голям + Прикачане + Откриване на канали + Търсене на канали + Възможно нарушаване на декларацията за поверителност! + search.jabber.network.

Ако използвате тази функционалност, Вашият IP адрес и въведеният текст за търсене ще бъдат изпратени до сървъра на тази услуга. Разгледайте нейната Декларация за поверителност за повече информация.]]>
+ Вече имам профил Добавяне на съществуващ профил + Регистриране на нов профил + Това прилича на адрес на домейн + Добавяне въпреки това + Това прилича на адрес на канал + Споделяне на файловете с резервни копия + Резервно копие от Conversations + Събитие + Отваряне на резервно копие + Избраният файл не е резервно копие от Conversations + Този профил вече е настроен + Въведете паролата за този профил + Това действие не може да бъде извършено Присъединяване към публичен канал… + Приложението за споделяне не даде разрешение за достъп до този файл. + + jabber.network + Локален сървър Повечето потребители трябва да изберат „jabber.network“ за по-добри предложения от цялата публична екосистема на XMPP. + Метод за откриване на канали + Резервно копие + Относно + Моля, активирайте профил + Направете обаждане + Входящо обаждане + Входящо видео-обаждане + Свързване + Установена връзка + Приемане на обаждане + Приключване на обаждане + Отговор + Отхвърляне + Откриване на устройства + Позвъняване Зает + Свързването с разговора е невъзможно + Връзката беше прекъсната + Върнат разговор + Грешка в приложението + Проблем с потвърждението + Затваряне + Текущо обаждане + Текущо видео-обаждане + Изключете Tor, за да правите обаждания + Входящо обаждане + Входящо обаждане · %s + Пропуснато обаждане · %s + Изходящо обаждане + Изходящо обаждане · %s + Пропуснато обаждане + Гласово обаждане + Видео обаждане + Помо + Превключване към разговор + Микрофонът не е наличен + Не може да има повече от едно обаждане едновременно. + Обратно към текущия разговор + Закачане горе + Откачане от горе + Съобщението не може да бъде поправено + Всички разговори + Този разговор + Вашият аватар + Аватар за %s + Шифровано с OMEMO + Шифровано с OpenPGP + Нешифровано + Изход + Запис на гласова поща + Възпроизвеждане на звука + Пауза на звука + Добавете контакт, създайте или се присъединете към групов разговор, или разгледайте каналите + + Преглед на %1$d член + Преглед на %1$d членове + + + Едно съобщение не може да бъде доставено + Някои съобщения не могат да бъдат доставени + + Неуспешни доставяния + Още настройки + Няма намерено приложение + Канене в Conversations Поканата не може да бъде анализирана Сървърът не поддържа създаването на покани + Нито един от активните профили не поддържа тази функционалност + Създаването на резервно копие е стартирано. Ще получите известие, когато приключи. Видеото не може да бъде включено. - + Обикновен текстов документ + + diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index 4318db444e92f9c918da5549539cbd90615df9c7..787025a67e1f09f5c004fcbb9b179a43b6a80478 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -264,7 +264,7 @@ Der Server hat die Veröffentlichung des Avatars abgelehnt. Bild konnte nicht konvertiert werden Avatar kann nicht gespeichert werden - (Oder klicke lange, um Standard wiederherzustellen) + (Oder klicke lange, um den Standard wiederherzustellen) Dein Server unterstützt die Veröffentlichung von Avataren nicht private Nachricht: an %s @@ -326,7 +326,7 @@ Bestätigen Erneut versuchen Vordergrunddienst - Verhindert, dass Android Conversations beendet und die Verbindung unterbricht + Verhindert, dass das Betriebssystem deine Verbindung unterbricht Sicherung erstellen Sicherungsdateien werden gespeichert in %s Erstelle Sicherungsdateien @@ -364,7 +364,7 @@ Für diesen Kontakt sind keine nutzbaren Schlüssel verfügbar.\nEs konnten keine neuen Schlüssel vom Server abgerufen werden. Gibt es vielleicht ein Problem mit dem Server deines Kontaktes? Für diesen Kontakt sind keine benutzbaren Schlüssel verfügbar.\nStelle sicher, dass ihre beide gegenseitig den Online-Status aktiviert habt. Etwas ist schief gelaufen - Lade Chatverlauf… + Lade Chatverlauf vom Server Keine weiteren Nachrichten vorhanden Aktualisieren… Passwort geändert! @@ -557,7 +557,7 @@ Dein Gerät unterstützt kein Ausschalten der Akkuoptimierung Registrierung fehlgeschlagen: Bitte später versuchen Registrierung fehlgeschlagen: Passwort zu schwach - Mitglieder wählen + Teilnehmer wählen Erstelle Gruppenchat… Erneut einladen Deaktivieren @@ -606,7 +606,7 @@ Du hast alle in deinem Besitz befindlichen OMEMO-Schlüssel überprüft Der Barcode enthält keine Fingerabdrücke für diese Unterhaltung. Überprüfte Fingerabdrücke - Nutze Kamera, um Barcodes deiner Kontakte zu scannen + Nutze die Kamera, um Barcodes deiner Kontakte zu scannen Bitte warten, bis die Schlüssel abgerufen werden Als Barcode teilen Als XMPP URI teilen @@ -896,7 +896,7 @@ Lokaler Server Die meisten Benutzer sollten hier ‘jabber.network’ auswählen, um bessere Vorschläge aus dem gesamten, öffentlichen XMPP-Ökosystem zu bekommen. Channelsuchmethode - Sicherungskopie + Sicherung Über Bitte aktiviere ein Konto Anrufen @@ -965,7 +965,7 @@ Einladung kann nicht gelesen werden Server unterstützt keine Generierung von Einladungen Keine aktiven Konten unterstützen diese Funktion - Das Backup wurde gestartet. Du bekommst eine Benachrichtigung sobald es fertig ist. + Die Sicherung wurde gestartet. Du bekommst eine Benachrichtigung, sobald sie fertig ist. Video kann nicht aktiviert werden. Textdokument diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 1383e8b9821c0434ed507497e72da9b6c370a975..535209db8edc6c543a7783046b61163e1d1245f1 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -132,6 +132,8 @@ Al enviar las trazas de error estás ayudando en el desarrollo Confirmar mensajes Permitir a tus contactos saber cuando has recibido y leído sus mensajes + Impedir capturas de pantalla + Ocultar el contenido de la aplicación en el selector de aplicaciones y bloquear las capturas de pantalla Pantalla OpenKeychain causó un error. Clave errónea para el cifrado. @@ -414,6 +416,7 @@ audio vídeo imagen + gráfico de vectores documento PDF Android App Contacto @@ -912,6 +915,7 @@ Conexión perdida Llamada rechazada Fallo en la aplicación + Problema de verificación Colgar Llamada saliente Video llamada saliente @@ -963,4 +967,6 @@ Ninguna cuenta activa soporta esta característica La copia de seguridad ha empezado. Recibirás una notificación cuando se haya completado. No se ha podido habilitar el vídeo. - + Documento de texto plano + + diff --git a/src/main/res/values-fi/strings.xml b/src/main/res/values-fi/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..3fa9ad89f6f06952bf70e84ab7f59ab16322f909 --- /dev/null +++ b/src/main/res/values-fi/strings.xml @@ -0,0 +1,918 @@ + + + Asetukset + Uusi keskustelu + Hallitse tilejä + Hallinnoi tiliä + Päätä keskustelu + Yhteystiedot + Ryhmäkeskustelun tiedot + Kanavan tiedot + Lisää tili + Muokkaa nimeä + Lisää yhteystietoihin + Poista yhteystietolistasta + Estä yhteystieto + Peru yhteystiedon esto + Estä verkkotunnus + Peru verkkotunnuksen esto + Estä osallistuja + Peru osallistujan esto + Hallitse tilejä + Asetukset + Aloita keskustelu + Valitse yhteystieto + Valitse yhteystiedot + Jaa tilillä + Estolista + äskettäin + minuutti sitten + %d minuuttia sitten + + %d lukematon keskustelu + + + %d lukematonta keskustelua + + + lähettää... + Puretaan viestin salausta. Odota hetki... + OpenPGP-salattu viesti + Nimimerkki on jo käytössä + Nimimerkki on virheellinen + Ylläpitäjä + Omistaja + Moderaattori + Osallistuja + Vierailija + Poistetaanko %s yhteystiedoistasi? Keskustelujasi hänen kanssaan ei poisteta. + Estetäänkö %s lähettämästä viestejä sinulle? + Perutaanko %s:n esto lähettää viestejä sinulle? + Estetäänkö kaikki yhteydet verkkotunnuksesta %s? + Perutaanko kaikkien verkkotunnuksen %s käyttäjien esto? + Yhteystieto estetty + Estetty + Poistetaanko %s kirjanmerkeistä? Mitään keskustelujasi sen kanssa ei poisteta. + Rekisteröi uusi tili palvelimella + Vaihda salasanaa palvelimelle + Aloita keskustelu + Kutsu yhteystieto + Kutsu + Yhteystiedot + Yhteystieto + Peruuta + Aseta + Lisää + Muokkaa + Poista + Estä + Peruuta esto + Tallenna + OK + %1$s kaatui + Virheenkorjaustietojen lähettäminen XMPP-tililläsi helpottaa %1$s:n kehitystyötä. + Lähetä nyt + Älä koskaan pyydä lähettämään + Tiliin ei saatu yhteyttä + Useisiin tileihin ei saatu yhteyttä + Napauta hallinnoidaksesi tilejä + Liitä tiedosto + Lisätäänkö tämä puuttuva yhteystieto listaasi? + Lisää yhteystieto + toimitus epäonnistui + Valmistaudutaan lähettämään kuva + Valmistaudutaan lähettämään kuvat + Jaetaan tiedostoja. Odota hetki... + Pyyhi historia + Pyyhi keskusteluhistoria + Poistetaanko kaikki keskustelun viestit?\n\nVaroitus: Muilla laitteilla tai palvelimilla säilytettyjä kopioita ei poisteta. + Poista tiedosto + Haluatko varmasti poistaa tämän tiedoston?\n\nVaroitus: Muilla laitteilla tai palvelimilla olevia kopioita ei poisteta. + Päätä keskustelu myös + Valitse laite + Lähetä salaamaton viesti + Lähetä viesti + Lähetä viesti henkilölle %s + Lähetä OMEMO-salattu viesti + Lähetä v\\OMEMO-salattu viesti + Lähetä OpenPGP-salattu viesti + Uusi nimimerkki on jo varattu + Lähetä salaamaton + Salauksen purku epäonnistui. Sinulle ei varmaan ole oikeaa salaista avainta. + OpenKeychain + OpenKeychainia viestien salaamiseeen ja salauksen purkamiseen, sekä julkisten avaintesi hallinointiin.

Se on GPLv3+-lisensoitu ja saatavilla F-Droidista sekä Google Playsta.

(Käynnistä %1$s uudelleen asennettuasi sovelluksen.)]]>
+ Käynnistä uudelleen + Asenna + Asenna OpenKeychain + tarjotaan... + odotetaan... + OpenPGP-avainta ei löydy + Viestin salaaminen ei onnistu koska vastaanottaja ei mainosta julkista avaintaan.\n\nPyydä kontaktiasi ottamaan OpenPGP käyttöön. + OpenPGP-avaimia ei löydy + Viestin salaaminen ei onnistu koska kontaktisi eivät mainosta julkisia avaimiaan.\n\nPyydä heitä ottamaan OpenPGP käyttöön. + Yleinen + Lataa tiedostot + Lataa automaattisesti tiedostot jotka ovat pienempiä kuin... + Liitteet + Ilmoitus + Värinä + Värise uuden viestin saapuessa + LED-ilmoitus + Vilkuta ilmoitusvaloa vastaanotettuasi uuden viestin + Soittoääni + Ilmoitusääni + Uusien viestien ilmoitusääni + Soittoääni saamillesi puheluille + Rauhanaika + Kuinka pitkäksi aikaa ilmoitukset hiljennetään kun jollain toisella laitteillasi tehdään jotain. + Edistyneet + Älä koskaan lähetä vikailmoituksia + Vikailmoituksia lähettämällä autat kehitystyötä + Lukukuittaus + Ilmoita lähettäjälle kun olet vastaanottanut ja lukenut viestin + Estä kuvankaappaukset + Piilota sovelluksen sisältö sovellusvaihtajassa ja estä ruutukaappaukset + Käyttöliittymä + OpenKeychain-virhe + Avain ei kelpaa salaamiseen. + Hyväksy + Virhe tapahtui + Virhe + Tilisi + Lähetä tilapäivityksiä + Vastaanota tilapäivityksiä + Pyydä tilapäivityksiä + Valitse kuva + Ota kuva + Alustavasti hyväksy liittymispyynnöt + Valitsemasi tiedosto ei ole kuva + Kuvatiedoston pakkaaminen epäonnistui + Tiedostoa ei löytynyt + Yleinen I/O-virhe. Ehkä vapaa tallennustila loppui kesken? + Kuvan valitsemiseen käyttämäsi sovellus ei myöntänyt riittävästi oikeuksia kuvan lukemiseen.\n\nKäytä toista tiedostonhallintaohjelmaa kuvan valitsemiseen. + Jakamiseen käyttämäsi sovellus ei myöntänyt riittävästi oikeuksia. + Tuntematon + Väliaikaisesti poistettu käytöstä + Paikalla + Yhdistäää\u2026 + Poissa + Ei sallittu + Palvelinta ei löydy + Ei yhteyttä + Rekisteröinti epäonnistui + Käyttäjänimi on varattu + Rekisteröinti valmis + Palvelin ei tue rekisteröintiä + Viallinen rekisteröintitunnus + TLS-kättely epäonnistui + Verkkotunnuksen varmentaminen epäonnistui + Yhteensopimaton palvelin + Salaamaton + OTR + OpenPGP + OMEMO + Poista tili + Poista käytöstä väliaikaisesti + Julkaise proofilikuva + Julkaise OpenPGP julkinen avain + Poista OpenPGP julkinen avain + Haluatko varmasti poistaa OpenPGP-avaimesi tilamainostuksistasi?\nYhteystietosi eivät voi enää lähettää sinulle OpenPGP-salattuja viestejä. + OpenPGP julkinen avain julkaistu. + Ota tunnus käyttöön + Oletko varma? + Tilin poistaminen pyyhkii koko keskusteluhistoriasi + Nauhoita ääntä + XMPP-osoite + Estä XMPP-osoite + käyttäjä@esimerkki.fi + Salasana + Tämä ei ole kunnollinen XMPP-osoite + Muisti loppui. Kuva on liian suuri. + Lisätäänkö %s osoitekirjaan? + Tietoa palvelimesta + XEP-0313: MAM + XEP-0280: Viestin kopiot + XEP-0352: Asiakkaan tilan vihjaus + XEP-0191: Estokomennot + XEP-0237: Yhteystietolistan versiointi + XEP-0198: Virran hallinta + XEP-0215: Ulkoisten palveluiden löytäminen + XEP-0163: PEP (Profiilikuvat / OMEMO) + XEP-0363: Tiedoston lähetys HTTP:llä + XEP-0357: Työntö + käytössä + ei käytössä + Julkisten avainten mainostus puuttuu + nähty juuri äsken + nähty minuutti sitten + nähty %d minuuttia sitten + nähty tunti sitten + nähty %d tuntia sitten + nähty päivä sitten + nähty %d päivää sitten + Salattu viesti. Asenna OpenKeychain purkaaksesi salauksen. + Löytyi uusi OpenPGP-salattu viesti + OpenPGP-avaimen tunniste + OMEMO-sormenjälki + v\\OMEMO-sormenjälki + OMEMO-sormenjälki (viestin lähettäjä) + v\\OMEMO-sormenjälki (viestin lähettäjä) + Muut laitteet + Luota OMEMO-sormenjälkiin + Haetaan avaimia... + Valmis + Pura salaus + Kirjanmerkit + Haku + Syötä yhteystieto + Poista yhteystieto + Näytä yhteystieto + Estä yhteystieto + Peruuta yhteystiedon esto + Luo + Valitse + Yhteystieto on jo olemassa + Liity + kanava@kokous.esimerkki.fi/nimimerkki + kanava@kokous.esimerkki.fi + Tallenna kirjanmerkkinä + Poista kirjanmerkki + Tuhoa ryhmäkeskustelu + Tuhoa kanava + Haluatko varmasti tuhota tämän ryhmäkeskustelun?\n\nVaroitus: Ryhmäkeskustelu poistetaan lopullisesti palvelimelta. + Haluatko varmasti tuhota tämän julkisen kanavan?\n\nVaroitus: Kanava poistetaan lopullisesti palvelimelta. + Ryhmäkeskustelun tuhomainen epäonnistui + Kanavan tuhoaminen epäonnistui + Muokkaa ryhmäkeskustelun aihetta + Aihe + Liitytään ryhmäkeskusteluun... + Poistu + Yhteystieto lisätty luetteloon + Lisää takaisin + %s on lukenut tähän asti + %s ovat lukeneet tähän asti + %1$s ja %2$d muuta on lukenut tähän asti + Kaikki ovat lukeneet tähän asti + Julkaise + Napauta profiilikuvaa valitaksesi kuvan galleriasta + Julkaistaan... + Palvelin hylkäsi julkaisusi + Kuvan muuntaminen epäonnistui + Profiilikuvan tallentaminen levylle epäonnistui + (Tai paina pitkään palauttaaksesi oletuksen) + Palvelin ei tue profiilikuvien julkaisua + kuiskasi + %s:lle + Lähetä yksityisviesti %s:lle + Yhdistä + Tili on jo olemassa + Seuraava + Istunto aloitettu + Ohita + Poista ilmoitukset käytöstä + Ota käyttöön + Ryhmäkeskustelu vaatii salasanan + Kirjoita salasana + Pyydä yhteystietoa ensin lähettämään tilapäivityksiä.\n\nTätä käytetään sen tunnistamiseen mitä sovellusta tämä käyttää. + Pyydä nyt + Ohita + Varoitus: Tämän lähettäminen ilman molemminpuolisia tilapäivityksiä voi aiheuttaa odottamattomia ongelmia.\n\nMene \"Yhteystiedon tietoihin\" tarkistaaksesi tilapäivitysten tilauksesi. + Turvallisuus + Salli viestien korjaaminen + Mahdollistaa muiden muokata sinulle lähettämiään viestejä jälkikäteen + Edistyneet asetukset + Ole varovainen näiden kanssa + Tietoa %s + Hiljaisuus + Alku + Loppu + Ota käyttöön hiljaisuus + Ilmoitukset vaimennetaan hiljaisuuden aikana + Muut + Synkronoi kirjanmerkkien kanssa + Liity ryhmään automaattisesti jos se on kirjanmerkeissäsi + OMEMO-sormenjälki kopioitu leikepöydälle + Sinut on estetty tästä ryhmäkeskustelusta + Tämä ryhmäkeskustelu on vain jäsenille + Resurssin rajallisuus + Sinut on poistettu tästä ryhmästä + Ryhmäkeskustelu on suljettu + Et ole enää tässä ryhmäkeskustelussa + käytetään tiliä %s + palvelimella %s + Tarkistetaan %s HTTP-palvelimella + Et ole yhteydessä. Yritä myöhemmin uudelleen + Tarkista %s:n koko + Tarkista %1$s:n koko palvelimella %2$s + Toiminnot + Lainaa + Liitä lainauksena + Kopio alkuperäinen URL + Lähetä uudestaan + Tiedoston URL + URL kopioitu leikepöydälle + XMPP-osoite kopioitu leikepöydälle + Vikailmoitus kopioitu leikepöydälle + web-osoite + Lue 2D-viivakoodi + Näytä 2D-viivakoodi + Näytä estolista + Tilitiedot + Vahvista + Yritä uudelleen + Palvelu etualalla + Estää käyttöjärjestelmää katkaisemasta yhteyttäsi + Tee varmuuskopio + Varmuuskopioiden säilytyspaikka: %s + Tehdään varmuuskopiota + Varmuuskopiosi on luotu + Varmuuskopiotiedostot tallennettu kansioon %s + Palautetaan varmuuskopiota + Varmuuskopiosi on palautettu + Älä unohda ottaa tiliä käyttöön. + Valitse tiedosto + Vastaanotetaan %1$s (%2$d%% valmis) + Lataa %s + Poista %s + tiedosto + Avaa %s + Lähetetään (%1$d%% valmis) + Valmistellaan tiedoston lähettämistä + %s tarjottu ladattavaksi + Peru siirto + tiedoston jako epäonnistui + tiedoston siirto peruttu + Tiedosto poistettu + Tiedoston avaamiseen sopivaa sovellusta ei löytynyt + Linkin avaamiseen sopivaa sovellusta ei löytynyt + Yhteystiedon näyttämiseen sopivaa sovellusta ei löytynyt + Dynaamiset tunnisteet + Näytä tunnisteet yhteystiedon alla (vain luku) + Näytä ilmoitukset + Ryhmäkeskustelupalvelinta ei löytynyt + Ryhmäkeskustelun luominen epäonnistui + Tilin profiilikuva + Kopioi OMEMO-sormenjälki leikepöydälle + Uusi OMEMO-avain + Poista laitteet + Haluatko poistaa kaikki muut laitteet OMEMO-mainoksistasi? Kun laite seuraavan kerran yhdistää, se lisää itsensä, mutta ennen sitä lähetettyjä viestejä et välttämättä saa. + Jokin meni pieleen + Haetaan historiaa palvelimelta + Palvelimella ei ollut enempää historiaa + Päivitetään... + Salasana vaihdettu! + Salasanan vaihto epäonnistui + Vaihda salasana + Nykyinen salasana + Uusi salasana + Salasana ei voi olla tyhjä + Kaikki tilit käyttöön + Kaikki tilit pois käytöstä + Poissa + Hylkiö + Jäsen + Edistynyt tila + Myönnä jäsenyys + Peru jäsenyys + Myönnä ylläpitäjän oikeudet + Peru ylläpitäjän oikeudet + Myönnä omistajuus + Peru omistajuus + Poista ryhmäkeskustelusta + Poista kanavalta + Estä ryhmäkeskustelusta + Estä kanavalta + Olet poistamassa %s:n julkiselta kanavalta. Ainoa tapa tehdä se on estää kyseinen käyttäjä ikuisesti. + Estä nyt + %s:n roolin muuttaminen epäonnistui + Yksityisen ryhmäkeskustelun asetukset + Julkisen kanavan asetukset + Yksityinen, vain jäsenille + Tee XMPP-osoitteista kaikille näkyvät + Tee kanavasta moderoitu + Et osallistu + Ryhmäkeskustelun asetuksia muutettu! + Ryhmäkeskustelun asetuksia ei voitu muuttaa + Ei koskaan + Kunnes toisin mainitaan + Torkku + Vastaa + Merkitse luetuksi + Syöttö + Enter lähettää + Käytä Enteriä viestin lähetämiseen. Ctrl+Enter lähettää viestin joka tapauksessa. + Näytä enter-nappi + Vaihtaa emojinapin enteriksi + ääni + video + kuva + vektorigrafiikka + PDF-asiakirja + Android-sovellus + Yhteystieto + Profiilikuva julkaistu! + Lähetetään %s + Tarjotaan %s + Piilota poissaolevat + %s kirjoittaa... + %s lopetti kirjoittamisen + %s kirjoittavat... + %s lopettivat kirjoittamisen + Kirjoitusilmoitukset + Näyttää ytheystiedoillesi kun kirjoitat heille viestiä + Lähetä sijainti + Näytä sijainti + Sijainnin näyttämiseen sopivaa sovellusta ei löytynyt + Sijainti + Keskustelu suljettu + Poistuit yksityisestä ryhmäkeskustelusta + Poistuit julkisesta kanavasta + Älä luota varmenteiden myöntäjiin + Kaikki varmenteet täytyy hyväksyä käsin + Poista varmenteet + Poista käsin hyväksytyt varmenteet + Ei käsin hyväksyttyjä varmenteita + Poista varmenteet + Peruuta + + %d varmenne poistettiin + %d varmennetta poistettiin + + Korvaa \"Lähetä\"-nappi pikatoiminnolla + Pikatoiminto + Ei mikään + Viimeksi käytetty + Valitse pikatoiminto + Lähetä yksityisviesti + %1$s poistui ryhmäkeskustelusta + Käyttäjänimi + Käyttäjänimi + Lataus epäonnistui: Palvelinta ei löydy + Lataus epäonnistui: Tiedostoa ei löytynyt + Lataus epöonnistui: Isäntään ei saatu yhteyttä + Lataus epäonnistui: Tiedoston tallennus epäonnistui + Tor-verkkoa ei saavutettu + Palvelin ei vastaa tästä verkkotunnuksesta + Rikki + Saatavuus + Poissa kun laite on lukittu + Näytä minut poissaolevana kun näyttö on lukittu + Kiireinen kun laite on äänetön + Näytä minut kiireisenä kun laite on äänettömänäq + Kohtele vain värinä -tilaa äänettömän lailla + Näytä minut kiireisenä kun laite on vain värinä -tilassa + Laajemmat yhteysasetukset + Näytä isäntänimen ja portin valinta tiliä lisätessä + xmpp.esimerkki.fi + Kirjaudu varmenteella + Varmenteen jäsennys epäonnistui + Arkistointiasetukset + Palvelimen arkitsointiasetukset + Kysytään arkistointiasetuksia. Odota hetki... + Arkistointiasetusten haku epäonnistui + CAPTCHA vaaditaan + Kirjoita ylläolevassa kuvassa näkyvä teksti + Varmenneketju ei ole luotettu + XMPP-osoite on eri kuin varmenteessa + Uusi varmenne + OMEMO-avaimen lataus epäonnistui! + OMEMO-avain vahvistettu varmenteella! + Laitteesi ei tue käyttäjän varmenteen valitsemista! + Yhteys + Käytä Tor-verkkoa + Tunneloi kaikki yhteydet Tor-verkon kautta. Vaatii Orbot-sovelluksen + Isäntänimi + Portti + Palvelin- tai .onion-osoite + Porttinumero on virheellinen + Isäntänimi on virheellinen + %1$d tunnusta %2$d:sta yhdistetty + + %d viesti + %d viestiä + + Lataa lisää viestejä + Tiedosto jaettu %s:n kanssa + Kuva jaettu %s:n kanssa + Kuvat jaettu %s:n kanssa + Salli %1$s:n käyttää ulkoista tallennustilaa + Salli %1$s:n käyttää kameraa + Synkronoi yhteystietojen kanssa + %1$s haluaa pääsyn osoitekirjaasi yhdistääkseen sen XMPP-yhteystietojesi kanssa.\nTämä näyttää yhteystietojesi koko nimen ja kuvan.\n\n%1$s pelkästään lukee osoitekirjaasi ja vertailee niitä paikallisesti, lähettämättä mitään palvelimelle. + Ilmoita kaikista uusista viesteistä + Ilmoita vain kun minut mainitaan + Ilmoitukset pois käytöstä + Ilmoitukset keskeytetty + Kuvan pakkaus + Vinkki: Käytä \'Lähetä tiedosto\':a \'Lähetä kuva\':n sijaan lähettääksesi kuvan pakkaamattomana sillä kertaa tästä asetuksesta huolimatta. + Aina + Vain isot kuvat + Akun käytön optimointi käytössä + Poista käytöstä + Valittu alue on liian suuri + (Ei aktivoituja tilejä) + Tämä kenttä on pakollinen + Korjaa viestiä + Lähetä korjattu viesti + Olet jo varmistanut tämän henkilön OMEMO-sormenjäljen turvallisesti luottamuksen varmistamikseksi. Hyväksymällä varmistat vain että %s on osa tätä ryhmäkeskustelua. + Olet poistanut tämän tilin käytöstä + URI:n jakamiseen sopivaa sovellusta ei löytynyt + Jaa URI sovelluksella... + Hyväksy ja jatka + XMPP-osoitteesi tulee olemaan kokonaisuudessaan: %s + Luo tunnus + Käytän itse valitsemaani palveluntarjoajaa + Valitse käyttäjänimi + Hallitse saatavuutta käsin + Valitse saatavuutesi itse muokatessasi tilaviestiäsi. + Tila + Vapaa juttelemaan + Paikalla + Poissa + Ei saatavilla + Kiireellinen + Turvallinen salasana on luotu + Laitteesi ei tue akun kulutuksen optimoinnin ohittamista + Rekisteröinti epäonnistui: Yritä myöhemmin uudelleen + Rekisteröinti epäonnistui: Salasana on liian heikko + Valitse osallistujat + Luodaan ryhmää... + Kutsu uudestaan + Poista käytöstä + Lyhyt + Keskipitkä + Pitkä + Kertoo yhteystiedoillesi milloin käytät Conversationsia + Yksityisyys + Teema + Valitse väripaletti + Automaattinen + Vaalea + Tumma + Vihreä tausta + Näytä vastaanotetut viestit vihreällä taustalla + OpenKeychainiin ei saatu yhteyttä + Laite ei ole enää käytössä + Tietokone + Puhelin + Tabletti + Selain + Pääte + Maksu vaaditaan + Salli internetin käyttö + Minä + Salli + Käyttöoikeus %s puuttuu + Etäpalvelinta ei löytynyt + Etäpalvelimen aikakatkaisu + Tiliä ei saatu muutettua + Ilmoita tämä XMPP-osoite roskapostista. + Poista OMEMO-identiteetit + Tee uudet OMEMO-avaimet. Kaikkien yhteystietojesi pitää varmistaa sinut uudestaan. Käytä tätä ainoastaan viimeisenä oljenkortena. + Poista valitut avaimet + Yhteys vaaditaan profiilikuvan julkaisemista varten. + Näytä virheilmoitus + Virheilmoitus + Datansäästö käytössä + Käyttöjärjestelmäsi estää %1$s:tä käyttämästä nettiä ollessaan taustalla. Vastaanottaaksesi ilmoitukset uusista viesteistä, salli %1$s:n käyttää esteettä verkkoa datansäästön ollessa käytössä. %1$s tekee silti parhaansa käyttääkseen mahdollisimman vähän dataa. + Laitteesi ei tue datansäästön poistamista käytöstä sovellukselle %1$s. + Väliaikaisen tiedoston luominen epäonnistui + Laite on varmennettu + Kopioi sormenjälki + Olet varmentanut kaikki hallussasi olevat OMEMO-avaimet + Viivakoodi ei sisällä tähän keskusteluun liittyviä sormenjälkiä. + Varmennetut sormenjäljet + Käytä kameraa yhteystietosi viivakoodin lukemiseen + Odota hetki, avaimia ladataan + Jaa viivakoodina + Jaa XMPP-osoitteena + Jaa HTTP-linkkinä + Sokea luottamus ennen varmistusta + Luota uusiin laitteisiin varmistamattomilta yhteystiedoilta, mutta vaadi varmistettujen yhteystietojen uusien laitteiden manuaalinen hyväksyminen. + OMEMO-avaimiin luotetaan sokeasti, eli ne voivat olla jonkun muun tai joku voi salakuunnella. + Ei luotettu + Viallinen 2D-viivakoodi + Tyhjennä välimuisti (kamerasovelluksen käyttämä) + Tyhjennä välimuisti + Siivoa yksityinen tallennustila + Tyhjennä tallennustila jossa tiedostoja säilytetään (Ne voi ladata uudestaan palvelimelta) + Seurasin tätä linkkiä luotettavasta lähteestä + Olet varmistamassa yhteystiedon %1$s OMEMO-avaimia linkin avaamalla. Tämä on turvallista ainoastaan jos löysit linkin luotettavasta paikasta jossa vain %2$s on sen kyennyt julkaisemaan. + Varmista OMEMO-avaimet + + %d sekunti + %d sekuntia + + + %d minuutti + %d minuuttia + + + %d tunti + %d tuntia + + + %d päivä + %d päivää + + + %d viikko + %d viikkoa + + + %d kuukausi + %d kuukautta + + Viestien automaattinen poisto + Poista tältä laitteelta automaattisesti valittua vanhemmat viestit. + Salataan viestiä + Pakataan videota + Yhteystieto estettiin. + Ilmoitukset tuntemattomila + Ilmoita tuntemattomien henkilöiden viesteistä ja puheluista. + Sait viestin tuntemattomalta henkilöltä + Estä tuntematon + Estä koko verkkotunnus + paikalla juuri nyt + Yritä salauksen purkua uudestaan + Istuntovirhe + Alennettu SASL-mekanismi + Palvelin vaatii tekemään rekisteröinnin verkkosivulla + Avaa verkkosivu + Verkkosivun avaamiseen sopivaa sovellusta ei löytynyt + Tänään + Eilen + Varmenna verkkotunnus DNSSEC:llä + Palvelinvarmenteet jotka sisältävät varmennetun verkkotunnukset hyväksytään + Varmenne ei sisällä XMPP-osoitetta + osittain + Nauhoita video + Kopioi leikepöydälle + Viesti kopioitu leikepöydälle + Viesti + Yksityisviestit on poistettu käytöstä + Suojatut sovellukset + Saadaksesi ilmoituksia silloinkin kun näyttö on sammutettu, Conversations pitää lisätä suojattujen sovellusten luetteloon. + Hyväksytäänkö tuntematon varmenne? + Palvelimen varmenne ei ole luotetun myöntäjän allekirjoittama. + Hyväksytäänkö eriävä palvelimen nimi? + Palvelin ei voinut tunnistautua olevansa verkkotunnusta \"%s\". Varmenne sisältää vain seuraavat verkkotunnukset: + Haluatko yhdistää joka tapauksessa? + Varmenteen tiedot: + Kerran + QR-koodilukija tarvitsee luvan käyttää kameraa + Vieritä alas asti + Vieritä alas veistin lähetyksen jälkeen + Vaihda tila + Muokkaa tilaa + Poista salaus käytöstä + %1$s ei voi lähettää salattuja viestejä %2$s:lle. Se voi johtua siitä että vastaanottajan palvelin on vanhentunut tai hänen käyttämänsä sovellus ei tue OMEMO:a. + Laiteluettelon lataus epäonnistui + Salausavainten lataus epäonnistui + Vinkki: Jossain tapauksissa tämä ratkeaa kun molemmat osapuolet lisäävät toisena yhteystietoihinsa. + Haluatko varmasti poistaa OMEMO-salauksen käytöstä tässä keskustelussa?\nPalvelimen ylläpitäjä voi tällöin lukea viestinne, mutta se voi olla ainoa mahdollinen tapa keskustella vanhentunutta sovellusta käyttävän henkilön kanssa. + Poista käytöstä nyt + Luonnos: + OMEMO-salaus + OMEMO:a ei koskaan käytetä oletuksena uusissa keskusteluissa. + Luo pikakuvake + Kirjasinkoko + Kirjasimen suhteellinen koko sovelluksen sisällä. + Käytössä oletuksena + Oletuksena pois käytöstä + Pieni + Keksikokoinen + Suuri + Viestiä ei salattu tälle laitteelle + OMEMO-salatun viestin purku epäonnistui + peru + Sijainnin jakaminen on pois käytöstä + Kopioi sijainti + Jaa sijainti + Reittiohjeet + Jaa sijainti + Näytä sijainti + Jaa + Odota hetki... + Salli %1$s:n käyttää mikrofonia + GIF + Näytä keskustelu + Sijainnin jako -lisäosa + Käytä lisäosaa sisäänrakennetun kartan sijaan + Kopioi web-osoite + Kopioi XMPP-osoite + Tiedostonjako HTTP:llä S3:een + \'Aloita keskustelu\' -näytöllä avaa näppäimistö ja siirrä kursori hakukenttään + Ryhmän kuvake + Isäntäpalvelin ei tue ryhmäkeskustelun kuvakkeita + Vain omistaja voi vaihtaa kuvakkeen + Yhteystiedon nimi + Nimimerkki + Nimi + Nimen antaminen on vapaaehtoista + Ryhmäkeskustelun nimi + Ryhmäkeskustelu on tuhottu + Tallennetta ei voitu tallentaa + Edustapalvelu + Tilatieto + Yhteysongelmat + Viestit + Puhelut + Viestit + Saapuvat puhelut + Toimitusvirheet + Tärkeys, ääni, värinä + Videonpakkaus + Näytä media + Osallistujat + Mediaselain + Videon laatu + Heikompi laatu tarkoittaa pienempiä tiedostoja + Keski (360p) + Korkea (720p) + peruutettu + Olet jo aloittanut viestin luonnostelun + Ominaisuutta ei ole toteutettu + Virheellinen maakoodi + Valitse maa + puhelinnumero + Vahvista puhelinnumerosi + Quicksy lähettää viestin sinulle varmistaaksesi puhelinnumerosi. Operaattorisi saattaa laskuttaa siitä. Syötä maakoodi ja puhelinnumero: +
%s

Onko se oikein, vai haluaisitko muuttaa numeroa?]]>
+ %s ei ole kelvollinen puhelinnumero. + Syötä puhelinnumerosi. + Varmista %s + %s.]]> + Lähetimme sinulle uuden viestin 6-numeroisella koodilla. + Kirjoita 6-numeroinen PIN-koodi alapuolelle. + Lähetä uusi tekstiviesti + Lähetä uusi tekstivieti (%s) + Odota (%s) + takaisin + Mahdollinen PIN-koodi liitettiin leikepöydältä automaattisesti + Syötä 6-numeroinen PIN-koodisi. + Haluatko varmasti perua rekisteröintiprosessin? + Kyllä + Ei + Varmistetaan... + Pyydetään tekstiviestiä + Syöttämäsi PIN-koodi on väärä. + Lähettämämme PIN-koodi on vanhentunut. + Tuntematon verkkovirhe. + Tuntematon vastaus palvelimelta. + Palvelimeen ei saatu yhteyttä. + Turvallinen yhteys epäonnistui. + Palvelinta ei löytynyt. + Pyyntösi käsittelyssä tapahtui jokin virhe. + Ei verkkoyhteyttä + Odota %s ja yritä uudelleen + Liian monta yritystä + Käytät vanhentunutta versiota tästä sovelluksesta. + Päivitä + Puhelinnumerolla on tällä hetkellä kirjauduttu sisään toiselle laitteelle. + Nimesi + Kirjoita nimesi + Napauta muokkaa-nappia valitaksesi nimesi. + Hylkää pyyntö + Asenna Orbot + Käynnistä Orbot + Sovelluskauppaa ei löydy. + Tämä kanava julkaisee XMPP-osoitteesi + e-kirja + Alkuperäinen (pakkaamaton) + Avaa sovelluksella... + Conversations-profiilikuva + Valitse tili + Palauta varmuuskopiosta + Palauta + Syötä salasanasi tilille %s palauttaaksesi varmuuskopion. + Älä käytä varmuuskopion palautusta asennuksen kloonaamiseksi (käyttääksesi yhtä aikaa toisella laitteella). Varmuuskopion palautus on tarkoitettu ainoiastaan laitteen tai sovelluksen vaihtamiseksi tai jos olet kadottanut alkuperäisen laitteesi. + Varmuuskopion palautus epäonnistui. + Varmuuskopion salauksen purku epäonnistui. Onko salasana oikein? + Varmuuskopiointi & palautus + Syötä XMPP-osoite + Luo ryhmäkeskustelu + Liity julkiselle kanavalle + Luo yksityinen ryhmäkeskustelu + Luo julkinen kanava + Kanavan nimi + XMPP-osoite + Anna kanavalle nimi + Anna XMPP-osoite + Tämä on XMPP-osoite. Anna nimi sen sijaan. + Luodaan julkista kanavaa... + Kanava on jo olemassa + Liityit olemassa olevalle kanavalle + Kanavan asetuksia ei saatu tallennettua + Salli kenen tahansa vaihtaa aihetta + Salli kenen tahansa kutsua ryhmään + Kuka tahansa voi muokata aihetta. + Omistajat voivat muokata aihetta. + Ylläpitäjät voivat muokata aihetta. + Omistajat voivat kutsua muita. + Kuka tahansa voi kutsua muita. + XMPP-osoitteet ovat näkyvillä ylläpitäjille. + XMPP-osoiteet ovat näkyllä kaikille. + Tällä julkisella kanavalla ei ole osallistujia. Kutsu yhteystietojasi tai käytä jakopainiketta kanavan XMPP-osoitteen levittämiseen. + Tässä yksityisessä ryhmässä ei ole ketään. + Hallitse oikeuksia + Tiedosto on liian iso + Liitä + Löydä kanavia + Hae kanavia + Mahdollinen yksityisyyden loukkaus! + search.jabber.network.

Sen käyttö lähettää IP-osoitteesi ja hakusanat palvelulle. Katso heidän yksityisyyskäytännöstään lisätietoa.]]>
+ Minulla on jo tili + Lisää olemassa oleva tili + Rekisteröi uusi tili + Tämä vaikuttaa verkkotunnukselta + Lisää silti + Tämä vaikuttaa kanavan osoitteelta + Jaa varmuuskopiotiedostot + Tapahtuma + Avaa varmuuskopio + Valitsemasi tiedosto ei ole Conversationsin varmuuskopio + Tämä tili on jo asennettu + Syötä tämän tilin salasana + Toiminnon suorittaminen epäonnistui + Liity julkiselle kanavalle... + Jakava sovellus ei antanut tarvittavaa lupaa lukea tiedostoa. + + jabber.network + Paikallinen palvelin + Suurimman osan käyttäjistä kannattaa valita \'jabber.network\' sillä se tarjoaa parempia ehdotuksia koko julkisesta XMPP-ekosysteemistä. + Kanavien löytötapa + Varmuuskopio + Tietoa + Ota jokin tili käyttöön + Soita puhelu + Saapuva puhelu + Saapuva videopuhelu + Yhdistetään + Yhdistetty + Hyväksytään puhelua + Päätetään puhelua + Vastaa + Hylkää + Etsitään laitteita + Soi + Kiireinen + Puhelun yhdistäminen epäonnistui + Yhteys katkesi + Puhelu peruttu + Sovelluksen virhe + Varmennusvirhe + Päätä puhelu + Puhelu kesken + Videopuhelu kesken + Poista Tor käytöstä soittaaksesi puhelun + Saapuva puhelu + Saapuva puhelu · %s + Vastaamaton puhelu · %s + Lähtevä puhelu + Lähtevä puhelu · %s + Vastaamaton puhelu + Äänipuhelu + Videopuhelu + Apua + Vaihda keskusteluun + Mikrofoni ei käytettävissä + Voit osallistua vain yhteen puheluun kerrallaan. + Takaisin keskeneräiseen puheluun + Kameran vaihtaminen epäonnistui + Kiinnitä + Irrota + GPX-reitti + Viestin korjaaminen epäonnistui + Kaikki keskustelut + Tämä keskustelu + Profiilikuvasi + %s:n profiilikuva + OMEMO-salattu + OpenPGP-salattu + Salaamaton + Poistu + Jätä viesti vastaajaan + Toista ääni + Keskeytä ääni + Lisää yhteystieto, luo tai liity ryhmäkeskusteluun, tai etsi kanava + + Näytä %1$d osallistuja + Näytä %1$d osallistujaa + + + Viestiä ei saatu toimitettua + Joitain viestejä ei saatu toimitettua + + Toimitusvirheet + Lisää vaihtoehtoja + Sovellusta ei löytynyt + Kutsu Conversationsiin + Kutsun jäsentäminen epäonnistui + Palvelin ei tue kutsujen luomista + Yksikään aktiivinen tili ei tue tätä toimintoa + Varmuuskopion teko aloitettu. Saat ilmoituksen kun se on valmis. + Videon käyttöönotto epäonnistui + Perustekstiasiakirja + +
diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index e1405feedbc4e4bb25dde925614e035f471ee6fb..2c2f413d06a6ee71ab5680950d14c44835e4204a 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -138,6 +138,8 @@ Отправляя отчеты об ошибках, вы помогаете разработке этого приложения Отчёты о получении Позволяет вашим контактам видеть, когда вы получили и прочитали их сообщения + Запретить скриншоты + Прятать содержимое приложения при переключении приложений и запретить скриншоты Интерфейс OpenKeychain вызвал ошибку. Неподходящий ключ для шифрования. @@ -420,6 +422,7 @@ аудио звук изображение + векторная графика PDF-документ Приложение Android Контакт @@ -989,4 +992,6 @@ Ни один активный аккаунт не поддерживает эту функцию Резервное копирование было начато. Вы получите уведомление, как только оно будет завершено. Невозможно включить видео. - + Текстовые данные + + diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index 35666eb4a6cf25867f576d8b67059bd24454d8d2..13858623763a3e5bc709dd09e14a09739f734752 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -72,10 +72,12 @@ Spara Ok %1$s har kraschat + Att använda ditt XMPP-konto för att skicka in \'stack traces\' hjälper den pågående utvecklingen av %1$s. Skicka nu Fråga aldrig igen Kunde inte ansluta till konto Kunde inte ansluta till flera konton + Tryck för att hantera dina konton Bifoga fil Vill du lägga till den här saknade kontakten i din kontaktlista? Lägg till kontakt @@ -85,7 +87,9 @@ Delar filer. Vänta... Rensa historik Rensa konversationshistorik + Vill du radera alla meddelanden i den här konversationen?\n\nVarning: Det här påverkar inte meddelanden som finns lagrade på andra enheter eller servrar. Ta bort fil + Är du säker på att du vill ta bort den här filen?\n\nVarning: Den här åtgärden kommer inte att ta bort kopior av den här filen som finns lagrad på andra enheter eller servrar. Stäng denna konversation efteråt Välj enhet Skicka okrypterat meddelande @@ -98,13 +102,16 @@ Skicka okrypterat Avkryptering misslyckades. Du har kanske kanske inte rätt privat nyckel. OpenKeychain + OpenKeychain för att kryptera och avkryptera dina publika nycklar.

Programmet är licensierat under GPLv3+ och finns tillgänglig via F-Droid and Google Play.

(Var god och starta om %1$s efter installationen.)]]>
Starta om Installera Installera OpenKeychain erbjuder… väntar… Ingen OpenPGP-nyckel funnen + Det gick inte att kryptera ditt meddelande eftersom att din kontakt inte har annonserat sin publika nyckel.\n\nVänligen be din kontakt att sätta upp OpenPGP. Inga OpenPGP-nycklar funna + Det gick inte att kryptera ditt meddelande eftersom att din kontakt inte har annonserat sina publika nycklar.\n\nVänligen be din kontakt att sätta upp OpenPGP. Generellt Acceptera filer Acceptera automatiskt filer som är mindre än… @@ -119,10 +126,14 @@ Aviseringsljud för nya meddelande Ringsignal för inkommande samtal Notifieringsfrist + Tidsgräns för hur länge notiser ska tystas efter att aktivitet har upptäckts på en av dina andra enheter. Avancerat Skicka aldrig krasch-rapporter + Genom att skicka in stack traces hjälper du utvecklingen Bekräfta meddelanden Låt dina kontakter veta när du har mottagit och läst deras meddelanden + Förhindra skärmdumpar + Dölj innehållet från applikationen i applikationsväxlaren och blockera skärmdumpar Gränssnitt OpenKeychain genererade ett fel. Dålig krypterings-nyckel. @@ -140,6 +151,8 @@ Det gick inte att konvertera bildfilen Filen hittas ej Generellt I/O-fel. Du kanske fick slut på plats? + Applikationen som du använde för att välja den här bilden tillhandahöll inte tillräckligt med rättigheter för att läsa filen.\n\nVar god och använd en annan filhanterare för att välja en bild. + Applikationen du använde för att dela den här filen tillhandahöll inte tillräckligt med behörigheter. Okänd Tillfälligt inaktiverad Online @@ -152,10 +165,13 @@ Användarnamn används redan Registrering klar Registrering stöds ej av server + Ogiltigt registreringstoken TLS-förhandling misslyckades + Domänen kan inte verifieras Kränkning av policy Inkompatibel server Strömningsfel + Fel vid öppning av ström Okrypterat OTR OpenPGP @@ -166,14 +182,17 @@ Publicera OpenPGP publik nyckel Ta bort OpenPGP publik nyckel Är du säker på att du vill ta bort din OpenPGP publik nyckel från din tillgänglighetsuppdatering?\nDina kontakter kommer inte längre att kunna skicka dig OpenPGP-krypterade meddelande. + OpenPGP-nyckel har publicerats. Aktivera konto Är du säker? + Om du tar bort ditt konto raderas hela din konversationshistorik 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 XEP-0313: Message Archive @@ -200,6 +219,8 @@ OpenPGP-nyckel-ID OMEMO-fingeravtryck v\\OMEMO-fingeravtryck + OMEMO-fingeravtryck (meddelandets ursprung) + v\\OMEMO-fingeravtryck (meddelandets ursprung) Andra enheter Lita på OMEMO-fingeravtryck Hämtar nycklar... @@ -216,33 +237,50 @@ Välj Kontakten finns redan Gå med + rum@konferens.exempel.se/användarnamn + rum@konferens.exempel.se Spara som bokmärke Ta bort bokmärke + Förstör gruppchat + Förstör kanal + Är du säker på att du vill förstöra den här gruppchatten?\n\nVarning: Gruppchatten kommer att tas bort helt från servern. + Är du säker på att du vill förstöra den här publika chattgruppen?\n\nVarning: Den här gruppchatten kommer att tas bort helt från servern. + Det gick inte att ta bort gruppchatten + Det gick inte att ta bort kanalen + Redigera ämnet för gruppchatten Ämne Går med i gruppchatt... Lämna Kontakten lade till dig i sin kontaktlista Addera tillbaka %s har läst hit + %s har läst till den här punkten + %1$s +%2$d andra har läst till den här punkten Alla har läst fram till hit Publicera + Tryck på visningsbilden för att välja en bild från galleriet Publicerar… Servern kunde inte publicera + Det gick inte att konvertera din bild Kunde inte spara avatarbild till disk (Eller tryck länge för att få tillbaks förvald) + Din server stödjer inte publicering av visningsbilder privat meddelande till %s Skicka privat meddelande till %s Anslut Detta konto finns redan Nästa + Session etablerad Hoppa över Inaktivera notifieringar Aktivera Gruppchatten kräver lösenord Fyll i lösenord + Var god begär närvarouppdateringar från din kontakt först.\n\nDetta kommer att användas för att avgöra vilken chattapplikationen din kontakt använder. Begär nu Ignorera + Varning: Att skicka detta utan ömsesidiga närvarouppdateringar kan orsaka oväntade problem.\n\nGå till \"Kontaktuppgifter\" för att verifiera dina närvaroprenumerationer. Säkerhet Tillåt korrigeringar av meddelanden Tillåt att dina kontakter kan ändra sina meddelanden i efterhand @@ -256,9 +294,16 @@ Notifieringar kommer vara tysta under tysta timmar Annat Synkronisera med bokmärken + Gå med i gruppchattar automatiskt om bokmärket säger det + OMEMO-fingeravtryck kopierat till urklipp + Du är avstängd från denna gruppchatt + Denna gruppchatt är endast för medlemmar Resursbegränsning + Du har blivit sparkad från den här gruppchatten Gruppchatten stängdes ner + Du är inte längre med i denna gruppchatt använder konto %s + huseras hos %s Kontrollerar %s på webbserver Du är inte ansluten. Försök igen senare Kontrollera filstorleken på %s @@ -279,6 +324,7 @@ Kontodetaljer Bekräfta Försök igen + Förgrundstjänst Förehindrar operativsystemet att ta ner uppkopplingen Skapa säkerhetskopia Säkerhetskopians filer lagras i %s @@ -295,14 +341,27 @@ fil Öppna %s skickar (%1$d%% klart) + Förbereder för delning av fil %s erbjuden för nedladdning Avbryt överföring + det gick inte att dela fil + filöverföring avbruten + Fil borttagen + Ingen applikation som kunde öppna filen hittades + Ingen applikation som kunde öppna länken hittades + Ingen applikation som kunde visa kontakten hittades + Dynamiska etiketter Visa skrivskyddade taggar under kontakter Aktivera notifieringar + Ingen gruppchattserver hittades + Det gick inte att skapa gruppchatten Kontots avatarbild Kopiera OMEMO-fingeravtryck till urklipp Regenerera OMEMO-nyckel Rensa enheter + Är du säker på att du vill ta bort alla andra enheter från OMEMO-tillkännagivandet? Nästa gång dina enheter ansluter, kommer de att tillkännage sig själva igen, men de kanske inte får meddelanden som skickas under tiden. + Det finns inga användbara nycklar tillgängliga för den här kontakten.\nDet gick inte att hämta nya nycklar från servern. Kanske är det något fel på din kontakts server? + Det finns inga användbara nycklar tillgängliga för den här kontakten.\nSe till att ni båda har närvaroprenumeration. Något gick fel Hämtar historik från server Ingen mer historik på server @@ -312,6 +371,7 @@ Byt lösenord Nuvarande lösenord Nytt lösenord + Lösenord kan inte vara tomma Aktivera alla konton Deaktivera alla konton Utför åtgärden med @@ -320,26 +380,42 @@ Utstött Medlem Avancerat läge + Bevilja medlemsprivilegier + Återkalla medlemsprivilegier Bevilja administratörsbehörighet Återkalla administratörsbehörighet + Bevilja ägarprivilegier + Återkalla ägarprivilegier Ta bort från gruppchatt Ta bort från kanal Kunde inte ändra tillhörigheten för %s + Förbjud från gruppchatt + Förbjud från kanal + Du försöker ta bort %s från en offentlig kanal. Det enda sättet att göra det är att förbjuda den användaren för alltid. Bannlys nu Kunde inte ändra rollen för %s + Privat gruppchattskonfiguration + Publik kanalkonfiguration Privat, medlemsskap krävs Gör XMPP-adresser synliga för alla + Gör kanalen modererad Du deltar ej + Ändrade gruppchattalternativ! + Det gick inte att ändra alternativ för gruppchatt Aldrig Tills vidare + Snooza + Svara Läsmarkera Input Skicka med enter + Använd Enter-tangenten för att skicka meddelandet. Du kan alltid använda Ctrl+Enter för att skicka meddelandet, även om det här alternativet är inaktiverat. Visa enter-knappen Byt ut emoticons-tangenten mot en enter-tangent ljud video bild + vektorgrafik PDF-dokument Android-app Kontakt @@ -355,8 +431,11 @@ Låt dina kontakter veta när du skriver meddelande till dem Skicka position Visa position + Ingen applikation hittades för att visa platsdata Position Konversation stängd + Lämnade privat gruppchatt + Lämnade publik kanal Lita inte på systemets CAs Alla certifikat måste manuellt godkännas Ta bort certifikat @@ -369,11 +448,15 @@ %d certifikat borttaget %d certifikat borttagna
+ Ersätt \"Skicka\"-knappen med snabbåtgärd Snabbfunktion Ingen Senast använd Välj snabbfunktion + Sök kontakter + Sök bokmärken Skicka privat meddelande + %1$s har lämnat gruppchatten Användarnamn Användarnamn Inte ett giltigt användanamn @@ -383,16 +466,28 @@ Nerladdning gick fel: Kunde inte skriva fil Tor-nätverk ej tillgängligt Bind-fel + Den här servern ansvarar inte för den här domänen Sönder Tillgänglighet + Frånvarande när enheten är låst + Visa som frånvarande när enheten är låst + Upptagen i ljudlöst läge + Visa som Upptagen i ljudlöst läge Hantera vibrationsläge som tyst läge + Visa som Upptagen när enheten är satt på att endast vibrera Utökade anslutningsinställningar Visa val av servernamn och port vid inställning av konto xmpp.example.com + Logga in med certifikat + Det gick inte att analysera certifikatet Arkiveringsinställningar Arkiveringsinställningar på servern Hämtar arkiveringsinställningar, vänta... + Det gick inte att hämta arkiveringsinställningar + CAPTCHA behövs Skriv i texten från bilden ovan + Otillförlitlig certifikatkedja + XMPP-adressen matchar inte certifikatet Förnya certifikat Misslyckades med att hämta OMEMO-nyckel! Verifierade OMEMO-nyckel med certifikat! @@ -402,6 +497,7 @@ Tunnla alla anslutningar genom Tor-nätverket. Kräver Orbot Servernamn Port + Server- eller .onion-adress Inte ett giltigt portnummer Inte ett giltigt servernamn %1$d av %2$d konton anslutna @@ -410,20 +506,36 @@ %d meddelanden Ladda fler meddelanden + Fil delad med %s + Bild delad med %s + Bilder som delats med %s + Text som delats med %s + Ge %1$s åtkomst till extern lagring + Ge %1$s åtkomst till kameran Synkronisera med kontakter + %1$s vill ha behörighet att komma åt din adressbok för att matcha den med din XMPP-kontaktlista.\nDetta visar dina kontakters fullständiga namn och visningsbilder.\n\n%1$s kommer bara att läsa din adressbok och matcha den lokalt, utan att ladda upp något till din server. +
Vi kommer inte att lagra någon kopia av dessa telefonnummer.\n\nLäs vår integritetspolicy för mer information.

Du kommer nu bli ombedd att ge åtkomst till dina kontakter.]]>
Notifiera för alla meddelanden + Notis endast vid omnämnande Notifieringar deaktiverade Notifieringar pausade Bildkomprimering + Tips: Använd \"Välj fil\" istället för \"Välj bild\" för att skicka enskilda bilder okomprimerade, oavsett denna inställning. Alltid + Endast stora bilder Batterioptimeringar aktiverade + Din enhet använder kraftiga batterioptimeringar för %1$s, vilket kan leda till försenade aviseringar eller till och med förlust av meddelanden.\nVi rekommenderar att du inaktiverar dem. + Din enhet använder kraftiga batterioptimeringar för %1$s, vilket kan leda till försenade aviseringar eller till och med förlust av meddelanden.\nDu kommer nu att bli ombedd att inaktivera dem. Deaktivera The valda området är för stort (Inget konto aktiverat) Detta fält måste fyllas i Korrigera meddelanden Skicka korrigerat meddelande + Du har redan validerat den här personens fingeravtryck säkert för att bekräfta förtroendet. Genom att välja \"Klar\" bekräftar du bara att %s är en del av den här gruppchatten. Du har deaktiverat detta konto + Säkerhetsfel: Ogiltig filåtkomst! + Ingen applikation hittades för att dela URI Dela URI med... Din fullständiga XMPP-adress kommer att vara: %s Skapa konto @@ -450,8 +562,11 @@ Tema Välj färgschema Automatisk + Ljus + Mörk Grön bakgrund Använd grön bakgrund för mottagna meddelanden + Det gick inte att ansluta till OpenKeychain Denna enhet används inte längre Dator Mobiltelefon @@ -459,20 +574,26 @@ Webbläsare Konsoll Betalning krävs + Ge behörighet till att använda Internet Jag Kontakt ber om tillgänglighetsuppdateringar Tillåt Saknar rättigheter för access till %s Fjärrserver hittas inte + Timeout för fjärrserver Kunde inte uppdatera konto + Rapportera den här XMPP-adressen för spam. Ta bort OMEMO identiteter + Återskapa dina OMEMO-nycklar. Alla dina kontakter måste verifiera dig igen. Använd endast det här som en sista utväg. Ta bort valda nycklar Du måste vara ansluten för att publicera din avatarbild Visa felmeddelande Felmeddelande Databesparing + Det gick inte att skapa en tillfällig fil Denna enhet har verifierats Kopiera fingeravtryck + Du har verifierat alla OMEMO-nycklar i din ägo Streckkoden innehåller inte fingeravtryck för denna konversation. Verifierade fingeravtryck Använd kameran för att scanna en kontakts streckkod @@ -481,8 +602,11 @@ Dela som XMPP URI Dela som HTTP länk Blint förtroende före verifiering + Lita på nya enheter från icke-verifierade kontakter, men begär manuell bekräftelse av nya enheter för verifierade kontakter. + Att blint lita på OMEMO-nycklar, innebär att det skulle kunna vara någon annan eller att någon annan har fått åtkomst. Ej betrodd Ogiltig 2D-streckkod + Töm cache-mapp (används av kameraapplikationen) Rensa cache Rensa private lagring Rensa privat lagring där filer lagras (De kan om-laddas från servern) @@ -530,6 +654,10 @@ online just nu Försök dekryptera igen Sessionsfel + Öppna webbsida + Ingen applikation hittades för att kunna öppna webbsidan + Se upp-notifikationer + Visa se upp-notifikationer Idag Igår Bekräfta värdnamn med DNSSEC diff --git a/src/quicksy/res/values-bg/strings.xml b/src/quicksy/res/values-bg/strings.xml index 1854c3e3c8f3365b331254a354772db63dbeca62..c41bf67c992b3938f3550656f89c68aa501f2ac7 100644 --- a/src/quicksy/res/values-bg/strings.xml +++ b/src/quicksy/res/values-bg/strings.xml @@ -3,7 +3,7 @@ Времето, през което Quicksy няма да прави нищо, след като забележи дейност на друго устройство Изпращайки проследявания на стека, Вие помагате за непрекъснатото развитие на Quicksy Така всичките Ви контакти ще знаят кога използвате Quicksy - Ако искате да продължите да получавате известия дори когато екранът е заключен, трябва да добавите „Quicksy“ към списъка от защитени приложения. + Ако искате да продължите да получавате известия дори когато екранът е заключен, трябва да добавите Quicksy към списъка със защитени приложения. Профилна снимка за Quicksy Quicksy не може да се използва във Вашата страна. Идентичността на сървъра не може да бъде потвърдена. diff --git a/src/quicksy/res/values-fi/strings.xml b/src/quicksy/res/values-fi/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..9a988a15db678f4e9df6f9968f143c15690945b1 --- /dev/null +++ b/src/quicksy/res/values-fi/strings.xml @@ -0,0 +1,12 @@ + + + Kuinka kauan Quicksy pysyy hiljaa nähtyään toisella laitteellasi toimintaa + Lähettämällä virheenkorjaustietoja autat Quicksyn kehittäjiä + Kerro kaikille yhteystiedoillesi kun käytät Quicksya + Saadaksesi ilmoituksia silloinkin kun näyttö on sammutettu, Quicksy pitää lisätä suojattujen sovellusten luetteloon. + Quicksy-profiilikuva + Quicksy ei ole saatavilla maassasi. + Palvelimen identiteetin varmennus epäonnistui. + Tuntematon turvallisuusvirhe. + Palvelimeen yhdistäminen aikakatkaistiin. + From ecdb5af5473437ddc7d76cd5a5d620a8ea83db68 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 9 Feb 2022 12:26:39 +0100 Subject: [PATCH 129/145] bump agp version --- build.gradle | 15 ++++++++------- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index e4d037114f71a7f9c18412f84032153434adba70..27777bde5d53cabb37be5c2facec35915a1cb7b3 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.4' + classpath 'com.android.tools.build:gradle:7.1.1' } } @@ -250,9 +250,6 @@ android { buildTypes.release.signingConfig = signingConfigs.release } - lintOptions { - disable 'MissingTranslation', 'InvalidPackage','AppCompatResource' - } subprojects { @@ -267,11 +264,15 @@ android { } } - packagingOptions { - exclude 'META-INF/BCKEY.DSA' - exclude 'META-INF/BCKEY.SF' + resources { + excludes += ['META-INF/BCKEY.DSA', 'META-INF/BCKEY.SF'] + } } + lint { + disable 'MissingTranslation', 'InvalidPackage', 'AppCompatResource' + } + android.applicationVariants.all { variant -> variant.outputs.each { output -> diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 84fa7550afd4b0101dcc7202c1d85d6c9aef6280..162dd9b7f25a2ebf7f029a7f7d681aeca4074879 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-7.0.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip From d7f38a3e5aca544eb2275f04c5b9c8f40d327a57 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 12 Feb 2022 10:19:54 +0100 Subject: [PATCH 130/145] fix precondition for timeout handling --- .../eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 12ba3573310b96018828779ca5c219926181d281..9ed4d188f5eba3c8c88d9723c07fa62c11393d05 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1107,7 +1107,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private void handleIqTimeoutResponse(final IqPacket response) { - Preconditions.checkArgument(response.getType() == IqPacket.TYPE.ERROR); + Preconditions.checkArgument(response.getType() == IqPacket.TYPE.TIMEOUT); Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received IQ timeout in RTP session with " + id.with + ". terminating with connectivity error"); if (isTerminated()) { Log.i(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring error because session was already terminated"); From cf9d6e5ca324092dde52b9838063886120284729 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 12 Feb 2022 10:20:07 +0100 Subject: [PATCH 131/145] version bump to 2.10.3-beta --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 27777bde5d53cabb37be5c2facec35915a1cb7b3..557c582aa33689e201815c3f4cad8b9e77068823 100644 --- a/build.gradle +++ b/build.gradle @@ -92,8 +92,8 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 42023 - versionName "2.10.2" + versionCode 42024 + versionName "2.10.3-beta" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId From fecc34431cd321131bedce0085d3aea291170fdf Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 13 Feb 2022 10:19:06 +0100 Subject: [PATCH 132/145] bump dependencies --- build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 557c582aa33689e201815c3f4cad8b9e77068823..2a070856a328c440e15b335fb14858925fb434a3 100644 --- a/build.gradle +++ b/build.gradle @@ -33,15 +33,15 @@ configurations { dependencies { implementation 'androidx.viewpager:viewpager:1.0.0' - playstoreImplementation('com.google.firebase:firebase-messaging:22.0.0') { + playstoreImplementation('com.google.firebase:firebase-messaging:23.0.0') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' } conversationsPlaystoreCompatImplementation("com.android.installreferrer:installreferrer:2.2") conversationsPlaystoreSystemImplementation("com.android.installreferrer:installreferrer:2.2") - quicksyPlaystoreCompatImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.1' - quicksyPlaystoreSystemImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.1' + quicksyPlaystoreCompatImplementation 'com.google.android.gms:play-services-auth-api-phone:18.0.1' + quicksyPlaystoreSystemImplementation 'com.google.android.gms:play-services-auth-api-phone:18.0.1' implementation 'org.sufficientlysecure:openpgp-api:10.0' implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0' implementation 'androidx.appcompat:appcompat:1.3.1' @@ -73,7 +73,7 @@ dependencies { implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:converter-gson:2.9.0" - implementation "com.squareup.okhttp3:okhttp:4.9.2" + implementation "com.squareup.okhttp3:okhttp:4.9.3" implementation 'com.google.guava:guava:30.1.1-android' quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.36' From 12463911f171792f17138b276c047de347feb225 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 13 Feb 2022 10:22:31 +0100 Subject: [PATCH 133/145] allow verification of own omemo keys via uri --- src/main/AndroidManifest.xml | 1 + .../persistance/FileBackend.java | 14 +++--- .../conversations/ui/EditAccountActivity.java | 43 ++++++++++++++++--- src/main/res/values/strings.xml | 2 + 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index c46fad8b1e2e743123b8423487777fea32c3f19b..ff41c07c2049b0efaf9ce05d13dcc7e274c1c5db 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -194,6 +194,7 @@ android:launchMode="singleTop" /> { + if (isTrustedSource.isChecked()) { + processFingerprintVerification(xmppUri, false); + } else { + finish(); + } + }); + builder.setNegativeButton(R.string.cancel, (dialog, which) -> finish()); + AlertDialog dialog = builder.create(); + dialog.setCanceledOnTouchOutside(false); + dialog.setOnCancelListener(d -> finish()); + dialog.show(); + } + @Override public void onNewIntent(final Intent intent) { super.onNewIntent(intent); @@ -749,7 +780,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } @Override - public void onSaveInstanceState(final Bundle savedInstanceState) { + public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) { if (mAccount != null) { savedInstanceState.putString("account", mAccount.getJid().asBareJid().toEscapedString()); savedInstanceState.putBoolean("initMode", mInitMode); diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index ff18945336465421f4e79cf0ee9e3e54b42484fd..8b5e67eb22ee82c50e17cadd645f916f404389c1 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -622,6 +622,8 @@ Clean private storage where files are kept (They can be re-downloaded from the server) I followed this link from a trusted source You are about to verify the OMEMO keys of %1$s after clicking a link. This is only secure if you followed this link from a trusted source where only %2$s could have published this link. + You are about to verify the OMEMO keys of your own account. This is only secure if you followed this link from a trusted source where only you could have published this link. + Continue Verify OMEMO keys Show inactive Hide inactive From 364ef2543d55e6f6424fd84518ce04798f76bc79 Mon Sep 17 00:00:00 2001 From: Lockywolf Date: Fri, 11 Feb 2022 15:20:18 +0800 Subject: [PATCH 134/145] Clarify build instructions. --- README.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1696e43d3d3446dc1b9247b6ea67b5e4ec62e2a8..689af33d51f6d1907445be1f433c824a233dda8c 100644 --- a/README.md +++ b/README.md @@ -385,20 +385,77 @@ you can get access to the the latest beta version by signing up using [this link #### How do I build Conversations -**Note:** Starting with version 2.8.0 you will need to compile libwebrtc. -[Instructions](https://webrtc.github.io/webrtc-org/native-code/android/) can be found on the WebRTC -website. Place the resulting libwebrtc.aar in the `libs/` directory. The PlayStore release currently +##### Compiling WebRTC. + +WebRTC is a standard for Internet audio and video communication. libwebrtc, also used in the Google Chrome web browser, implementing the WebRTC standard. + +**Note:** Starting with version 2.8.0 you will need to compile libwebrtc from source because there are no fresh binary releases available to download. + +[Instructions](https://webrtc.github.io/webrtc-org/native-code/android/) can be found on the WebRTC website, however, there build method used by Conversations developers is slightly different. + +``` +mkdir -p ~/Prerequisites-for-Conversations +cd ~/Prerequisites-for-Conversations +git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git +export PATH=~/Prerequisites-for-Conversations/depot_tools:$PATH +mkdir webrtc +cd webrtc +fetch --nohooks webrtc_android +# ...wait for 20Gb of stuff... +gclient sync +# ...wait for more 5Gb of stuff... +cd src +unset _JAVA_OPTS +./tools_webrtc/android/build_aar.py +``` + +It will take some time and build webrtc for all popular Android architectures. +The result will be the file `./libwebrtc.aar` + + +##### Building Conversations itself + +Place the resulting libwebrtc.aar in the `libs/` directory. The PlayStore release currently uses the stable M90 release and renamed the file name to `libwebrtc-m90.aar` put potentially you can -reference any file name by modifying `build.gradle`. +reference any file name by modifying `build.gradle`. Search for `libwebrtc-m90.aar`, and replace it with `libwebrtc.aar`. + Make sure to have ANDROID_HOME point to your Android SDK. Use the Android SDK Manager to install missing dependencies. +Alternatively (and to avoid thinking about environment variables), create a file called local.properties, in the root of the Conversations build tree, +with the following contents: + +``` +## This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Wed May 20 16:21:35 CST 2020 +ndk.dir=Path-To-Ndk +sdk.dir=Path-To-Sdk +``` + +Then issue the following commands in order to build the apk. + git clone https://github.com/inputmice/Conversations.git cd Conversations ./gradlew assembleConversationsFreeSystemDebug There are two build flavors available. *free* and *playstore*. Unless you know what you are doing you only need *free*. +You will find the apks in the `./build/outputs/apk/conversationsFreeSystem/debug/` directory. + +Be careful, the resulting apks will not install unless you delete your existing Conversations installation (which will delete all the messages from your phone, and if you have used OMEMO, you will not be able to restore them from the server). +Do it at your own risk. + +You, though, can make your own build a "test build", that can be installed alongside the normal (F-Droid or Google Play) Conversations: + +In the file `build.gradle`, find the line `applicationId "eu.siacs.conversations"` , and replace it with `applicationId "my.conversations.fork"`, also below replace "Conversations" appName with "MyCFork". +Then the resulting APK can be installed ALONGSIDE normal Conversations. And have a different name so it's not confusing + +WARNING: DO NOT REPLACE ANYTHING ELSE ANYWHERE ELSE, DO NOT REPLACE THIS PROJECT WIDE. JUST 2 strings in THAT specific file! [![Build Status](https://travis-ci.org/inputmice/Conversations.svg?branch=development)](https://travis-ci.org/inputmice/Conversations) From 2553895300bfccf9b9e593acb034e4a679feacd1 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sat, 11 Dec 2021 15:53:53 +0100 Subject: [PATCH 135/145] Fix #4249. --- .../java/eu/siacs/conversations/ui/util/QuoteHelper.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java index 4beee8a2294dbe5bc62da102398d8c6686e6f936..cf49be76703dcefb04958c979c4fc53c15b51b2e 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java @@ -30,14 +30,18 @@ public class QuoteHelper { } public static boolean isPositionAltQuoteStart(CharSequence body, int pos) { - return isPositionAltQuoteCharacter(body, pos) && !isPositionFollowedByAltQuoteEnd(body, pos); + return isPositionAltQuoteCharacter(body, pos) + && isPositionPrecededByPreQuote(body, pos) + && !isPositionFollowedByAltQuoteEnd(body, pos); } public static boolean isPositionFollowedByQuoteChar(CharSequence body, int pos) { return body.length() > pos + 1 && isPositionQuoteCharacter(body, pos + 1); } - // 'Prequote' means anything we require or can accept in front of a QuoteChar + /** + * 'Prequote' means anything we require or can accept in front of a QuoteChar. + */ public static boolean isPositionPrecededByPreQuote(CharSequence body, int pos) { return UIHelper.isPositionPrecededByLineStart(body, pos); } From cdc239b040678348b26cb19a4de39d13efc9f313 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 14 Feb 2022 10:27:12 +0100 Subject: [PATCH 136/145] code clean up in TagWriter --- .../eu/siacs/conversations/xml/TagWriter.java | 215 +++++++++--------- 1 file changed, 105 insertions(+), 110 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xml/TagWriter.java b/src/main/java/eu/siacs/conversations/xml/TagWriter.java index 0e03fc1e8f19fb04321aed93a5d0e1a0440fddbd..4f429377a166b3cf7d31075cb58ed0937a0a9c0c 100644 --- a/src/main/java/eu/siacs/conversations/xml/TagWriter.java +++ b/src/main/java/eu/siacs/conversations/xml/TagWriter.java @@ -14,114 +14,109 @@ import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; public class TagWriter { - private OutputStreamWriter outputStream; - private boolean finished = false; - private final LinkedBlockingQueue writeQueue = new LinkedBlockingQueue(); - private CountDownLatch stanzaWriterCountDownLatch = null; - - private final Thread asyncStanzaWriter = new Thread() { - - @Override - public void run() { - stanzaWriterCountDownLatch = new CountDownLatch(1); - while (!isInterrupted()) { - if (finished && writeQueue.size() == 0) { - break; - } - try { - AbstractStanza output = writeQueue.take(); - outputStream.write(output.toString()); - if (writeQueue.size() == 0) { - outputStream.flush(); - } - } catch (Exception e) { - break; - } - } - stanzaWriterCountDownLatch.countDown(); - } - - }; - - public TagWriter() { - } - - public synchronized void setOutputStream(OutputStream out) throws IOException { - if (out == null) { - throw new IOException(); - } - this.outputStream = new OutputStreamWriter(out); - } - - public TagWriter beginDocument() throws IOException { - if (outputStream == null) { - throw new IOException("output stream was null"); - } - outputStream.write(""); - outputStream.flush(); - return this; - } - - public synchronized TagWriter writeTag(Tag tag) throws IOException { - if (outputStream == null) { - throw new IOException("output stream was null"); - } - outputStream.write(tag.toString()); - outputStream.flush(); - return this; - } - - public synchronized TagWriter writeElement(Element element) throws IOException { - if (outputStream == null) { - throw new IOException("output stream was null"); - } - outputStream.write(element.toString()); - outputStream.flush(); - return this; - } - - public TagWriter writeStanzaAsync(AbstractStanza stanza) { - if (finished) { - Log.d(Config.LOGTAG,"attempting to write stanza to finished TagWriter"); - return this; - } else { - if (!asyncStanzaWriter.isAlive()) { - try { - asyncStanzaWriter.start(); - } catch (IllegalThreadStateException e) { - // already started - } - } - writeQueue.add(stanza); - return this; - } - } - - public void finish() { - this.finished = true; - } - - public boolean await(long timeout, TimeUnit timeunit) throws InterruptedException { - if (stanzaWriterCountDownLatch == null) { - return true; - } else { - return stanzaWriterCountDownLatch.await(timeout, timeunit); - } - } - - public boolean isActive() { - return outputStream != null; - } - - public synchronized void forceClose() { - asyncStanzaWriter.interrupt(); - if (outputStream != null) { - try { - outputStream.close(); - } catch (IOException e) { - //ignoring - } - } - outputStream = null; - } + private OutputStreamWriter outputStream; + private boolean finished = false; + private final LinkedBlockingQueue writeQueue = new LinkedBlockingQueue(); + private CountDownLatch stanzaWriterCountDownLatch = null; + + private final Thread asyncStanzaWriter = new Thread() { + + @Override + public void run() { + stanzaWriterCountDownLatch = new CountDownLatch(1); + while (!isInterrupted()) { + if (finished && writeQueue.size() == 0) { + break; + } + try { + AbstractStanza output = writeQueue.take(); + outputStream.write(output.toString()); + if (writeQueue.size() == 0) { + outputStream.flush(); + } + } catch (Exception e) { + break; + } + } + stanzaWriterCountDownLatch.countDown(); + } + + }; + + public TagWriter() { + } + + public synchronized void setOutputStream(OutputStream out) throws IOException { + if (out == null) { + throw new IOException(); + } + this.outputStream = new OutputStreamWriter(out); + } + + public void beginDocument() throws IOException { + if (outputStream == null) { + throw new IOException("output stream was null"); + } + outputStream.write(""); + outputStream.flush(); + } + + public synchronized void writeTag(Tag tag) throws IOException { + if (outputStream == null) { + throw new IOException("output stream was null"); + } + outputStream.write(tag.toString()); + outputStream.flush(); + } + + public synchronized void writeElement(Element element) throws IOException { + if (outputStream == null) { + throw new IOException("output stream was null"); + } + outputStream.write(element.toString()); + outputStream.flush(); + } + + public void writeStanzaAsync(AbstractStanza stanza) { + if (finished) { + Log.d(Config.LOGTAG, "attempting to write stanza to finished TagWriter"); + } else { + if (!asyncStanzaWriter.isAlive()) { + try { + asyncStanzaWriter.start(); + } catch (IllegalThreadStateException e) { + // already started + } + } + writeQueue.add(stanza); + } + } + + public void finish() { + this.finished = true; + } + + public boolean await(long timeout, TimeUnit timeunit) throws InterruptedException { + if (stanzaWriterCountDownLatch == null) { + return true; + } else { + return stanzaWriterCountDownLatch.await(timeout, timeunit); + } + } + + public boolean isActive() { + return outputStream != null; + } + + public synchronized void forceClose() { + asyncStanzaWriter.interrupt(); + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + //ignoring + } + } + outputStream = null; + } } From 6bd552f6a32ca93826cb491f9b4bd757f9698227 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 14 Feb 2022 11:46:57 +0100 Subject: [PATCH 137/145] flush stanzas in batches --- .../eu/siacs/conversations/xml/TagWriter.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xml/TagWriter.java b/src/main/java/eu/siacs/conversations/xml/TagWriter.java index 4f429377a166b3cf7d31075cb58ed0937a0a9c0c..2c2b8ac2cc1f1f5c3dc286760aef5ea514083f33 100644 --- a/src/main/java/eu/siacs/conversations/xml/TagWriter.java +++ b/src/main/java/eu/siacs/conversations/xml/TagWriter.java @@ -8,12 +8,15 @@ import java.io.OutputStreamWriter; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import eu.siacs.conversations.Config; import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; public class TagWriter { + private static final int FLUSH_DELAY = 400; + private OutputStreamWriter outputStream; private boolean finished = false; private final LinkedBlockingQueue writeQueue = new LinkedBlockingQueue(); @@ -21,6 +24,8 @@ public class TagWriter { private final Thread asyncStanzaWriter = new Thread() { + private final AtomicInteger batchStanzaCount = new AtomicInteger(0); + @Override public void run() { stanzaWriterCountDownLatch = new CountDownLatch(1); @@ -29,12 +34,21 @@ public class TagWriter { break; } try { - AbstractStanza output = writeQueue.take(); - outputStream.write(output.toString()); - if (writeQueue.size() == 0) { + final AbstractStanza stanza = writeQueue.poll(FLUSH_DELAY, TimeUnit.MILLISECONDS); + if (stanza != null) { + batchStanzaCount.incrementAndGet(); + outputStream.write(stanza.toString()); + } else { + final int batch = batchStanzaCount.getAndSet(0); + if (batch > 1) { + Log.d(Config.LOGTAG, "flushing " + batch + " stanzas"); + } outputStream.flush(); + final AbstractStanza nextStanza = writeQueue.take(); + batchStanzaCount.incrementAndGet(); + outputStream.write(nextStanza.toString()); } - } catch (Exception e) { + } catch (final Exception e) { break; } } From 358271b767b4c594833541b87a9ad66352528b12 Mon Sep 17 00:00:00 2001 From: Ketroc <21rocket@gmail.com> Date: Sat, 3 Jul 2021 11:20:38 -0400 Subject: [PATCH 138/145] WIP - dialpad and dtmf sending --- .../conversations/ui/RtpSessionActivity.java | 60 +++ .../conversations/ui/widget/DialpadView.java | 73 ++++ .../xmpp/jingle/JingleRtpConnection.java | 12 + .../xmpp/jingle/ToneManager.java | 2 +- .../xmpp/jingle/WebRTCWrapper.java | 37 ++ src/main/res/layout/activity_rtp_session.xml | 18 +- src/main/res/layout/dialpad.xml | 368 ++++++++++++++++++ src/main/res/menu/activity_rtp_session.xml | 6 + src/main/res/values/dimens.xml | 7 + src/main/res/values/strings.xml | 1 + src/main/res/values/styles.xml | 12 +- 11 files changed, 589 insertions(+), 7 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java create mode 100644 src/main/res/layout/dialpad.xml diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 65beae35dbfc0c658d835db41e0f507b3deefa14..2f08eff86c966e494bbc5d578ba5101688989474 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -38,6 +38,7 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jetbrains.annotations.NotNull; import org.webrtc.RendererCommon; import org.webrtc.SurfaceViewRenderer; import org.webrtc.VideoTrack; @@ -149,6 +150,35 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); this.binding = DataBindingUtil.setContentView(this, R.layout.activity_rtp_session); setSupportActionBar(binding.toolbar); + + //TODO: remove this - for testing dialpad input + //((DialpadView)findViewById(R.id.action_dialpad)). + + findViewById(R.id.dialpad_1_holder).setOnClickListener(view -> dialpadPressed(view)); + findViewById(R.id.dialpad_2_holder).setOnClickListener(view -> dialpadPressed(view)); + findViewById(R.id.dialpad_3_holder).setOnClickListener(view -> dialpadPressed(view)); + findViewById(R.id.dialpad_4_holder).setOnClickListener(view -> dialpadPressed(view)); + findViewById(R.id.dialpad_5_holder).setOnClickListener(view -> dialpadPressed(view)); + findViewById(R.id.dialpad_6_holder).setOnClickListener(view -> dialpadPressed(view)); + findViewById(R.id.dialpad_7_holder).setOnClickListener(view -> dialpadPressed(view)); + findViewById(R.id.dialpad_8_holder).setOnClickListener(view -> dialpadPressed(view)); + findViewById(R.id.dialpad_9_holder).setOnClickListener(view -> dialpadPressed(view)); + findViewById(R.id.dialpad_0_holder).setOnClickListener(view -> dialpadPressed(view)); + findViewById(R.id.dialpad_asterisk_holder).setOnClickListener(view -> dialpadPressed(view)); + findViewById(R.id.dialpad_pound_holder).setOnClickListener(view -> dialpadPressed(view)); + + if (savedInstanceState != null) { + int dialpad_visibility = savedInstanceState.getInt("dialpad_visibility"); + System.out.println("dialpad_visibility onCreate = " + dialpad_visibility); + findViewById(R.id.dialpad).setVisibility(dialpad_visibility); + } + } + + + + private void dialpadPressed(View dialpadKeyHolderView) { + JingleRtpConnection rtpConnection = requireRtpConnection(); + rtpConnection.applyDtmfTone(dialpadKeyHolderView.getTag().toString()); } @Override @@ -156,8 +186,10 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe getMenuInflater().inflate(R.menu.activity_rtp_session, menu); final MenuItem help = menu.findItem(R.id.action_help); final MenuItem gotoChat = menu.findItem(R.id.action_goto_chat); + final MenuItem dialpad = menu.findItem(R.id.action_dialpad); help.setVisible(isHelpButtonVisible()); gotoChat.setVisible(isSwitchToConversationVisible()); + dialpad.setVisible(isAudioOnlyConversation()); return super.onCreateOptionsMenu(menu); } @@ -192,12 +224,29 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe return connection != null && STATES_SHOWING_SWITCH_TO_CHAT.contains(connection.getEndUserState()); } + private boolean isAudioOnlyConversation() { + final JingleRtpConnection connection = + this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null; + return connection != null && + connection.getEndUserState() == RtpEndUserState.CONNECTED && + !connection.isVideoEnabled(); + } + private void switchToConversation() { final Contact contact = getWith(); final Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true); switchToConversation(conversation); } + private void toggleDialpadVisibility() { + if (binding.dialpad.getVisibility() == View.VISIBLE) { + binding.dialpad.setVisibility(View.GONE); + } + else { + binding.dialpad.setVisibility(View.VISIBLE); + } + } + public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.action_help: @@ -206,6 +255,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe case R.id.action_goto_chat: switchToConversation(); break; + case R.id.action_dialpad: + toggleDialpadVisibility(); + break; } return super.onOptionsItemSelected(item); } @@ -1187,6 +1239,14 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe } } + @Override + protected void onSaveInstanceState(@NonNull @NotNull Bundle outState) { + super.onSaveInstanceState(outState); + int visibility = findViewById(R.id.action_dialpad).getVisibility(); + System.out.println("visibility onSave = " + visibility); + outState.putInt("dialpad_visibility", visibility); + } + private void updateRtpSessionProposalState(final Account account, final Jid with, final RtpEndUserState state) { final Intent currentIntent = getIntent(); final String withExtra = currentIntent == null ? null : currentIntent.getStringExtra(EXTRA_WITH); diff --git a/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java b/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java new file mode 100644 index 0000000000000000000000000000000000000000..3bd13541d54c774c3b0a29e8b41a6e5267cf737e --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package eu.siacs.conversations.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +import androidx.constraintlayout.widget.ConstraintLayout; +import eu.siacs.conversations.R; +import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; + +public class DialpadView extends ConstraintLayout implements View.OnClickListener { + + public DialpadView(Context context) { + super(context); + init(); + } + + public DialpadView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public DialpadView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + inflate(getContext(), R.layout.dialpad, this); + initViews(); + } + + private void initViews() { + findViewById(R.id.dialpad_1_holder).setOnClickListener(this); + findViewById(R.id.dialpad_2_holder).setOnClickListener(this); + findViewById(R.id.dialpad_3_holder).setOnClickListener(this); + findViewById(R.id.dialpad_4_holder).setOnClickListener(this); + findViewById(R.id.dialpad_5_holder).setOnClickListener(this); + findViewById(R.id.dialpad_6_holder).setOnClickListener(this); + findViewById(R.id.dialpad_7_holder).setOnClickListener(this); + findViewById(R.id.dialpad_8_holder).setOnClickListener(this); + findViewById(R.id.dialpad_9_holder).setOnClickListener(this); + findViewById(R.id.dialpad_0_holder).setOnClickListener(this); + findViewById(R.id.dialpad_asterisk_holder).setOnClickListener(this); + findViewById(R.id.dialpad_pound_holder).setOnClickListener(this); + } + + @Override + public void onClick(View v) { + /* TODO: this widget doesn't know anything about the RTP Connection, + so how to make this widget generic but also able to send touch-tone sounds + */ + System.out.println("v.getTag() = " + v.getTag()); + } + +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 9ed4d188f5eba3c8c88d9723c07fa62c11393d05..f221d6fadd464de0b8b07a6792945e23e35481c6 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import org.webrtc.DtmfSender; import org.webrtc.EglBase; import org.webrtc.IceCandidate; import org.webrtc.PeerConnection; @@ -231,6 +232,17 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } } + //TODO: remove - hack to test dtmfSending + public DtmfSender getDtmfSender() { + return webRTCWrapper.getDtmfSender(); + } + + //FIXME: possible implementation + public boolean applyDtmfTone(String tone) { + return webRTCWrapper.applyDtmfTone(tone); + } + + private void receiveSessionTerminate(final JinglePacket jinglePacket) { respondOk(jinglePacket); final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason(); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java index e368d3b09ef0e7a6d4f7ca8761121c755db69402..5c49b34e1934f99a60e9913a64139297e9503c30 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java @@ -148,7 +148,7 @@ class ToneManager { } } - private void startTone(final int toneType, final int durationMs) { + public void startTone(final int toneType, final int durationMs) { if (toneGenerator != null) { this.toneGenerator.startTone(toneType, durationMs); } else { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 0b990db43e533e21197b1b12a4b87b2aa4a889ac..ae001ac610406488dc0388dd0d6fb1ab8d4b7863 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -1,6 +1,7 @@ package eu.siacs.conversations.xmpp.jingle; import android.content.Context; +import android.media.ToneGenerator; import android.os.Build; import android.os.Handler; import android.os.Looper; @@ -8,6 +9,7 @@ import android.util.Log; import com.google.common.base.Optional; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.Futures; @@ -25,6 +27,7 @@ import org.webrtc.CandidatePairChangeEvent; import org.webrtc.DataChannel; import org.webrtc.DefaultVideoDecoderFactory; import org.webrtc.DefaultVideoEncoderFactory; +import org.webrtc.DtmfSender; import org.webrtc.EglBase; import org.webrtc.IceCandidate; import org.webrtc.MediaConstraints; @@ -47,6 +50,7 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Queue; +import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -83,6 +87,25 @@ public class WebRTCWrapper { .add("GT-I9505") // Samsung Galaxy S4 (jfltexx) .build(); + private static final int TONE_DURATION = 200; + private static final Map TONE_CODES; + static { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + builder.put("0", ToneGenerator.TONE_DTMF_0); + builder.put("1", ToneGenerator.TONE_DTMF_1); + builder.put("2", ToneGenerator.TONE_DTMF_2); + builder.put("3", ToneGenerator.TONE_DTMF_3); + builder.put("4", ToneGenerator.TONE_DTMF_4); + builder.put("5", ToneGenerator.TONE_DTMF_5); + builder.put("6", ToneGenerator.TONE_DTMF_6); + builder.put("7", ToneGenerator.TONE_DTMF_7); + builder.put("8", ToneGenerator.TONE_DTMF_8); + builder.put("9", ToneGenerator.TONE_DTMF_9); + builder.put("*", ToneGenerator.TONE_DTMF_S); + builder.put("#", ToneGenerator.TONE_DTMF_P); + TONE_CODES = builder.build(); + } + private static final int CAPTURING_RESOLUTION = 1920; private static final int CAPTURING_MAX_FRAME_RATE = 30; @@ -507,6 +530,20 @@ public class WebRTCWrapper { return peerConnection; } + //TODO: remove - hack to test dtmfSending + public DtmfSender getDtmfSender() { + return peerConnection.getSenders().get(0).dtmf(); + } + + public boolean applyDtmfTone(String tone) { + if (toneManager == null || peerConnection.getSenders().isEmpty()) { + return false; + } + peerConnection.getSenders().get(0).dtmf().insertDtmf(tone, TONE_DURATION, 100); + toneManager.startTone(TONE_CODES.get(tone), TONE_DURATION); + return true; + } + void addIceCandidate(IceCandidate iceCandidate) { requirePeerConnection().addIceCandidate(iceCandidate); } diff --git a/src/main/res/layout/activity_rtp_session.xml b/src/main/res/layout/activity_rtp_session.xml index 0bdca47760059988ea8b33915154d7466e20fb39..fc37e7df8e37967179cf251d1c0a7df4fd85375a 100644 --- a/src/main/res/layout/activity_rtp_session.xml +++ b/src/main/res/layout/activity_rtp_session.xml @@ -77,6 +77,14 @@ android:textAppearance="@style/TextAppearance.Conversations.Title.Monospace" tools:text="01:23" /> + + + tools:visibility="gone" /> + tools:visibility="gone" /> @@ -189,7 +197,7 @@ android:layout_centerVertical="true" android:layout_margin="@dimen/in_call_fab_margin" android:layout_toStartOf="@+id/end_call" - android:visibility="gone" + android:visibility="visible" app:backgroundTint="?color_background_primary" app:elevation="4dp" app:fabSize="mini" @@ -215,7 +223,7 @@ android:layout_centerVertical="true" android:layout_margin="@dimen/in_call_fab_margin" android:layout_toEndOf="@+id/end_call" - android:visibility="gone" + android:visibility="visible" app:backgroundTint="?color_background_primary" app:elevation="4dp" app:fabSize="mini" @@ -228,7 +236,7 @@ android:layout_centerVertical="true" android:layout_margin="@dimen/in_call_fab_margin" android:layout_toEndOf="@+id/in_call_action_right" - android:visibility="gone" + android:visibility="visible" app:backgroundTint="?color_background_primary" app:elevation="4dp" app:fabSize="mini" diff --git a/src/main/res/layout/dialpad.xml b/src/main/res/layout/dialpad.xml new file mode 100644 index 0000000000000000000000000000000000000000..2a2208c945ed3b3febe4b4ae03e8dd743d990c12 --- /dev/null +++ b/src/main/res/layout/dialpad.xml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/res/menu/activity_rtp_session.xml b/src/main/res/menu/activity_rtp_session.xml index 04756490ab87c062ab6cd44a434920b05eda16c6..cbe68daaf34a174fa764158078a2d3fb8362df97 100644 --- a/src/main/res/menu/activity_rtp_session.xml +++ b/src/main/res/menu/activity_rtp_session.xml @@ -12,5 +12,11 @@ android:id="@+id/action_goto_chat" android:icon="?attr/icon_goto_chat" android:title="@string/switch_to_conversation" + app:showAsAction="ifRoom" /> + + \ No newline at end of file diff --git a/src/main/res/values/dimens.xml b/src/main/res/values/dimens.xml index baa9d4ea902bb97ceb5e50d31bd16b7ec07186f0..16a8cef1ac844d79e10e7aab61292cc6f4f4a8d6 100644 --- a/src/main/res/values/dimens.xml +++ b/src/main/res/values/dimens.xml @@ -44,4 +44,11 @@ 128dp 96dp 24dp + + 30sp + 12sp + 8dp + 16dp + 4dp + 20sp diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 8b5e67eb22ee82c50e17cadd645f916f404389c1..5ad945e6f25d8b903419df06af6b596c02256e5d 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -1,6 +1,7 @@ Settings + Dialpad New conversation Manage accounts Manage account diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml index b6b8b76b0cb71caedcd947635e8e3ee4bd4b766c..05b2841147610db013835c9a7e1b46a89f11e4bb 100644 --- a/src/main/res/values/styles.xml +++ b/src/main/res/values/styles.xml @@ -159,4 +159,14 @@ - \ No newline at end of file + + + + + From c20a82640e91c0a08aec05821b99f74efad3d739 Mon Sep 17 00:00:00 2001 From: root21 Date: Mon, 11 Oct 2021 09:38:13 -0600 Subject: [PATCH 139/145] Cleaned up DTMF code and click handling. Min API version change was required for user interface lambdas. --- build.gradle | 2 +- .../conversations/ui/RtpSessionActivity.java | 27 +++---------------- .../conversations/ui/widget/DialpadView.java | 12 +++++---- .../xmpp/jingle/JingleRtpConnection.java | 7 ----- .../xmpp/jingle/WebRTCWrapper.java | 5 ---- src/main/res/layout/activity_rtp_session.xml | 10 +++---- 6 files changed, 17 insertions(+), 46 deletions(-) diff --git a/build.gradle b/build.gradle index 2a070856a328c440e15b335fb14858925fb434a3..d57de07d218aa44cd46732035b9bb13e11079ea6 100644 --- a/build.gradle +++ b/build.gradle @@ -90,7 +90,7 @@ android { compileSdkVersion 29 defaultConfig { - minSdkVersion 21 + minSdkVersion 24 targetSdkVersion 29 versionCode 42024 versionName "2.10.3-beta" diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 2f08eff86c966e494bbc5d578ba5101688989474..7fb120d88707d958f8625978ba1b7e678bbc210f 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -57,6 +57,7 @@ import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.services.AppRTCAudioManager; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.widget.DialpadView; import eu.siacs.conversations.ui.util.AvatarWorkerTask; import eu.siacs.conversations.ui.util.MainThreadExecutor; import eu.siacs.conversations.ui.util.Rationals; @@ -151,36 +152,16 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe this.binding = DataBindingUtil.setContentView(this, R.layout.activity_rtp_session); setSupportActionBar(binding.toolbar); - //TODO: remove this - for testing dialpad input - //((DialpadView)findViewById(R.id.action_dialpad)). - - findViewById(R.id.dialpad_1_holder).setOnClickListener(view -> dialpadPressed(view)); - findViewById(R.id.dialpad_2_holder).setOnClickListener(view -> dialpadPressed(view)); - findViewById(R.id.dialpad_3_holder).setOnClickListener(view -> dialpadPressed(view)); - findViewById(R.id.dialpad_4_holder).setOnClickListener(view -> dialpadPressed(view)); - findViewById(R.id.dialpad_5_holder).setOnClickListener(view -> dialpadPressed(view)); - findViewById(R.id.dialpad_6_holder).setOnClickListener(view -> dialpadPressed(view)); - findViewById(R.id.dialpad_7_holder).setOnClickListener(view -> dialpadPressed(view)); - findViewById(R.id.dialpad_8_holder).setOnClickListener(view -> dialpadPressed(view)); - findViewById(R.id.dialpad_9_holder).setOnClickListener(view -> dialpadPressed(view)); - findViewById(R.id.dialpad_0_holder).setOnClickListener(view -> dialpadPressed(view)); - findViewById(R.id.dialpad_asterisk_holder).setOnClickListener(view -> dialpadPressed(view)); - findViewById(R.id.dialpad_pound_holder).setOnClickListener(view -> dialpadPressed(view)); + binding.dialpad.setClickConsumer(tag -> { + requireRtpConnection().applyDtmfTone(tag); + }); if (savedInstanceState != null) { int dialpad_visibility = savedInstanceState.getInt("dialpad_visibility"); - System.out.println("dialpad_visibility onCreate = " + dialpad_visibility); findViewById(R.id.dialpad).setVisibility(dialpad_visibility); } } - - - private void dialpadPressed(View dialpadKeyHolderView) { - JingleRtpConnection rtpConnection = requireRtpConnection(); - rtpConnection.applyDtmfTone(dialpadKeyHolderView.getTag().toString()); - } - @Override public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.activity_rtp_session, menu); diff --git a/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java b/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java index 3bd13541d54c774c3b0a29e8b41a6e5267cf737e..5b16dce17230ede9937db7e61c65c242a9018a7b 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java @@ -23,10 +23,11 @@ import android.view.View; import androidx.constraintlayout.widget.ConstraintLayout; import eu.siacs.conversations.R; -import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; public class DialpadView extends ConstraintLayout implements View.OnClickListener { + protected java.util.function.Consumer clickConsumer = null; + public DialpadView(Context context) { super(context); init(); @@ -42,6 +43,10 @@ public class DialpadView extends ConstraintLayout implements View.OnClickListene init(); } + public void setClickConsumer(java.util.function.Consumer clickConsumer) { + this.clickConsumer = clickConsumer; + } + private void init() { inflate(getContext(), R.layout.dialpad, this); initViews(); @@ -64,10 +69,7 @@ public class DialpadView extends ConstraintLayout implements View.OnClickListene @Override public void onClick(View v) { - /* TODO: this widget doesn't know anything about the RTP Connection, - so how to make this widget generic but also able to send touch-tone sounds - */ - System.out.println("v.getTag() = " + v.getTag()); + clickConsumer.accept(v.getTag().toString()); } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index f221d6fadd464de0b8b07a6792945e23e35481c6..46d89ec3f84e073f9704549ef8aeab915f6ef8fb 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -232,17 +232,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } } - //TODO: remove - hack to test dtmfSending - public DtmfSender getDtmfSender() { - return webRTCWrapper.getDtmfSender(); - } - - //FIXME: possible implementation public boolean applyDtmfTone(String tone) { return webRTCWrapper.applyDtmfTone(tone); } - private void receiveSessionTerminate(final JinglePacket jinglePacket) { respondOk(jinglePacket); final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason(); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index ae001ac610406488dc0388dd0d6fb1ab8d4b7863..a579ccd77bb219f7a8ba8e2d5a6579656085482e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -530,11 +530,6 @@ public class WebRTCWrapper { return peerConnection; } - //TODO: remove - hack to test dtmfSending - public DtmfSender getDtmfSender() { - return peerConnection.getSenders().get(0).dtmf(); - } - public boolean applyDtmfTone(String tone) { if (toneManager == null || peerConnection.getSenders().isEmpty()) { return false; diff --git a/src/main/res/layout/activity_rtp_session.xml b/src/main/res/layout/activity_rtp_session.xml index fc37e7df8e37967179cf251d1c0a7df4fd85375a..a9c51087fe298c34ab3b23a9133f6dc0d21e5330 100644 --- a/src/main/res/layout/activity_rtp_session.xml +++ b/src/main/res/layout/activity_rtp_session.xml @@ -171,7 +171,7 @@ app:elevation="4dp" app:fabCustomSize="72dp" app:maxImageSize="36dp" - tools:visibility="gone" /> + tools:visibility="visible" /> + tools:visibility="visible" /> @@ -197,7 +197,7 @@ android:layout_centerVertical="true" android:layout_margin="@dimen/in_call_fab_margin" android:layout_toStartOf="@+id/end_call" - android:visibility="visible" + android:visibility="gone" app:backgroundTint="?color_background_primary" app:elevation="4dp" app:fabSize="mini" @@ -223,7 +223,7 @@ android:layout_centerVertical="true" android:layout_margin="@dimen/in_call_fab_margin" android:layout_toEndOf="@+id/end_call" - android:visibility="visible" + android:visibility="gone" app:backgroundTint="?color_background_primary" app:elevation="4dp" app:fabSize="mini" @@ -236,7 +236,7 @@ android:layout_centerVertical="true" android:layout_margin="@dimen/in_call_fab_margin" android:layout_toEndOf="@+id/in_call_action_right" - android:visibility="visible" + android:visibility="gone" app:backgroundTint="?color_background_primary" app:elevation="4dp" app:fabSize="mini" From 39fc0ccdd0adba4a73f054301e8474902b2dc942 Mon Sep 17 00:00:00 2001 From: root21 Date: Mon, 11 Oct 2021 11:57:22 -0600 Subject: [PATCH 140/145] Changed dialpad icon to something more recognizable. Included SVG of icon in assets. --- src/main/res/drawable/ic_dialpad_white_24dp.xml | 9 +++++++++ src/main/res/menu/activity_rtp_session.xml | 13 ++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 src/main/res/drawable/ic_dialpad_white_24dp.xml diff --git a/src/main/res/drawable/ic_dialpad_white_24dp.xml b/src/main/res/drawable/ic_dialpad_white_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..18e745c10df0824f8b191280e3bcd074b0fea56a --- /dev/null +++ b/src/main/res/drawable/ic_dialpad_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/main/res/menu/activity_rtp_session.xml b/src/main/res/menu/activity_rtp_session.xml index cbe68daaf34a174fa764158078a2d3fb8362df97..420896b091fa75c48dab19dd78b00a54263cc532 100644 --- a/src/main/res/menu/activity_rtp_session.xml +++ b/src/main/res/menu/activity_rtp_session.xml @@ -8,15 +8,14 @@ android:icon="?attr/icon_help" android:title="@string/help" app:showAsAction="always" /> + - - - \ No newline at end of file + From 9fa4b88784b6af1511c72b4078daf9c48c68142e Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 15 Oct 2021 11:46:24 +0100 Subject: [PATCH 141/145] RtpSessionActivity: Fix NPE from using incorrect view id --- .../java/eu/siacs/conversations/ui/RtpSessionActivity.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 7fb120d88707d958f8625978ba1b7e678bbc210f..6dacf29db78f0b4d49a2edb675384b75cec7c464 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -1223,8 +1223,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe @Override protected void onSaveInstanceState(@NonNull @NotNull Bundle outState) { super.onSaveInstanceState(outState); - int visibility = findViewById(R.id.action_dialpad).getVisibility(); - System.out.println("visibility onSave = " + visibility); + int visibility = findViewById(R.id.dialpad).getVisibility(); outState.putInt("dialpad_visibility", visibility); } From e65ee62cd3eec4c01d4c5752b5ba1aecd87a09bb Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Tue, 26 Oct 2021 10:19:44 -0500 Subject: [PATCH 142/145] Polyfill to allow use on Android 21 --- build.gradle | 2 +- .../eu/siacs/conversations/ui/widget/DialpadView.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index d57de07d218aa44cd46732035b9bb13e11079ea6..2a070856a328c440e15b335fb14858925fb434a3 100644 --- a/build.gradle +++ b/build.gradle @@ -90,7 +90,7 @@ android { compileSdkVersion 29 defaultConfig { - minSdkVersion 24 + minSdkVersion 21 targetSdkVersion 29 versionCode 42024 versionName "2.10.3-beta" diff --git a/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java b/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java index 5b16dce17230ede9937db7e61c65c242a9018a7b..fa4d46ec5781fc499cad2da56e9eb77908c858d9 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java @@ -26,7 +26,7 @@ import eu.siacs.conversations.R; public class DialpadView extends ConstraintLayout implements View.OnClickListener { - protected java.util.function.Consumer clickConsumer = null; + protected Consumer clickConsumer = null; public DialpadView(Context context) { super(context); @@ -43,7 +43,7 @@ public class DialpadView extends ConstraintLayout implements View.OnClickListene init(); } - public void setClickConsumer(java.util.function.Consumer clickConsumer) { + public void setClickConsumer(Consumer clickConsumer) { this.clickConsumer = clickConsumer; } @@ -72,4 +72,8 @@ public class DialpadView extends ConstraintLayout implements View.OnClickListene clickConsumer.accept(v.getTag().toString()); } + // Based on java.util.function.Consumer to avoid Android 24 dependency + public interface Consumer { + void accept(T t); + } } From 74bd6f0ca2eabdbd339c3060101b39309dd5dd3e Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Tue, 26 Oct 2021 13:38:55 -0500 Subject: [PATCH 143/145] Switch onClicks to use DataBinding --- .../conversations/ui/widget/DialpadView.java | 27 +- src/main/res/layout/dialpad.xml | 733 +++++++++--------- 2 files changed, 385 insertions(+), 375 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java b/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java index fa4d46ec5781fc499cad2da56e9eb77908c858d9..ccf91cad4bfc76b7a533a2ded7a29041b84f29bc 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java @@ -19,9 +19,12 @@ package eu.siacs.conversations.ui.widget; import android.content.Context; import android.util.AttributeSet; +import android.view.LayoutInflater; import android.view.View; import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.databinding.DataBindingUtil; +import eu.siacs.conversations.databinding.DialpadBinding; import eu.siacs.conversations.R; public class DialpadView extends ConstraintLayout implements View.OnClickListener { @@ -48,23 +51,13 @@ public class DialpadView extends ConstraintLayout implements View.OnClickListene } private void init() { - inflate(getContext(), R.layout.dialpad, this); - initViews(); - } - - private void initViews() { - findViewById(R.id.dialpad_1_holder).setOnClickListener(this); - findViewById(R.id.dialpad_2_holder).setOnClickListener(this); - findViewById(R.id.dialpad_3_holder).setOnClickListener(this); - findViewById(R.id.dialpad_4_holder).setOnClickListener(this); - findViewById(R.id.dialpad_5_holder).setOnClickListener(this); - findViewById(R.id.dialpad_6_holder).setOnClickListener(this); - findViewById(R.id.dialpad_7_holder).setOnClickListener(this); - findViewById(R.id.dialpad_8_holder).setOnClickListener(this); - findViewById(R.id.dialpad_9_holder).setOnClickListener(this); - findViewById(R.id.dialpad_0_holder).setOnClickListener(this); - findViewById(R.id.dialpad_asterisk_holder).setOnClickListener(this); - findViewById(R.id.dialpad_pound_holder).setOnClickListener(this); + DialpadBinding binding = DataBindingUtil.inflate( + LayoutInflater.from(getContext()), + R.layout.dialpad, + this, + true + ); + binding.setDialpadView(this); } @Override diff --git a/src/main/res/layout/dialpad.xml b/src/main/res/layout/dialpad.xml index 2a2208c945ed3b3febe4b4ae03e8dd743d990c12..4a18363fe67aa16381b38bfbac7b56856bf4c356 100644 --- a/src/main/res/layout/dialpad.xml +++ b/src/main/res/layout/dialpad.xml @@ -1,368 +1,385 @@ - - - - - - - - + + + + + - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - + android:background="?attr/selectableItemBackgroundBorderless" + app:layout_constraintBottom_toBottomOf="@+id/dialpad_0_holder" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/dialpad_0_holder" + app:layout_constraintTop_toTopOf="@+id/dialpad_0_holder"> + + + + + From 5b94198c15acd6713f440c79f9cc02be2a22acce Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Thu, 28 Oct 2021 20:15:53 -0500 Subject: [PATCH 144/145] Use a boolean for this state --- .../java/eu/siacs/conversations/ui/RtpSessionActivity.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 6dacf29db78f0b4d49a2edb675384b75cec7c464..1a1ab8f6cfa4749061244078c9054556e0e0c72c 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -157,8 +157,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe }); if (savedInstanceState != null) { - int dialpad_visibility = savedInstanceState.getInt("dialpad_visibility"); - findViewById(R.id.dialpad).setVisibility(dialpad_visibility); + boolean dialpadVisible = savedInstanceState.getBoolean("dialpad_visible"); + binding.dialpad.setVisibility(dialpadVisible ? View.VISIBLE : View.GONE); } } @@ -1223,8 +1223,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe @Override protected void onSaveInstanceState(@NonNull @NotNull Bundle outState) { super.onSaveInstanceState(outState); - int visibility = findViewById(R.id.dialpad).getVisibility(); - outState.putInt("dialpad_visibility", visibility); + outState.putBoolean("dialpad_visible", binding.dialpad.getVisibility() == View.VISIBLE); } private void updateRtpSessionProposalState(final Account account, final Jid with, final RtpEndUserState state) { From 47cc06365c761132061572a7f4d3810919921329 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Thu, 28 Oct 2021 20:16:08 -0500 Subject: [PATCH 145/145] Detect a video call in a consistent way --- .../java/eu/siacs/conversations/ui/RtpSessionActivity.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 1a1ab8f6cfa4749061244078c9054556e0e0c72c..e10d2259cbb1a3bedeb1fa1ae08b196a88adcd37 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -208,9 +208,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe private boolean isAudioOnlyConversation() { final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null; - return connection != null && - connection.getEndUserState() == RtpEndUserState.CONNECTED && - !connection.isVideoEnabled(); + + return connection != null && !connection.getMedia().contains(Media.VIDEO); } private void switchToConversation() {