diff --git a/.travis.yml b/.travis.yml index 3d647d9e8..664d3cf55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ android: - '.+' before_script: - mkdir libs - - wget -O libs/libwebrtc-m89.aar https://gultsch.de/files/libwebrtc-m89.aar + - wget -O libs/libwebrtc-m90.aar https://gultsch.de/files/libwebrtc-m90.aar script: - ./gradlew assembleQuicksyFreeCompatDebug - ./gradlew assembleQuicksyFreeSystemDebug diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c82b957e..9da1fec84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +### Version 2.9.13 + +* minor A/V improvements + +### Version 2.9.12 + +* Always verify domain name. No user overwrite +* Support roster pre authentication + ### Version 2.9.11 * Fixed 'No Connectivity' issues on Android 7.1 diff --git a/build.gradle b/build.gradle index bca7843f2..1011071d7 100644 --- a/build.gradle +++ b/build.gradle @@ -77,7 +77,7 @@ dependencies { implementation 'com.google.guava:guava:30.1-android' quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.18' - // implementation fileTree(include: ['libwebrtc-m89.aar'], dir: 'libs') + // implementation fileTree(include: ['libwebrtc-m90.aar'], dir: 'libs') implementation 'org.webrtc:google-webrtc:1.0.32006' } @@ -93,13 +93,14 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 42013 - versionName "2.9.11" + versionCode 42015 + versionName "2.9.13" archivesBaseName += "-$versionName" applicationId "eu.sum7.conversations" resValue "string", "applicationId", applicationId - resValue "string", "app_name", "Conv6ations" - buildConfigField "String", "LOGTAG", "\"conver6ations\"" + def appName = "Conv6ations" + resValue "string", "app_name", appName + buildConfigField "String", "APP_NAME", "\"$appName\""; } @@ -129,9 +130,11 @@ android { quicksy { dimension "mode" applicationId = "im.quicksy.client" - resValue "string", "app_name", "Quicksy" resValue "string", "applicationId", applicationId - buildConfigField "String", "LOGTAG", "\"quicksy\"" + + def appName = "Quicksy" + resValue "string", "app_name", appName + buildConfigField "String", "APP_NAME", "\"$appName\""; } conversations { diff --git a/metadata/en-US/changelogs/42015.txt b/metadata/en-US/changelogs/42015.txt new file mode 100644 index 000000000..53dbc6087 --- /dev/null +++ b/metadata/en-US/changelogs/42015.txt @@ -0,0 +1,3 @@ +• Always verify domain name. No user overwrite +• Support roster pre authentication +• minor A/V improvements diff --git a/src/conversations/res/values-vi/strings.xml b/src/conversations/res/values-vi/strings.xml new file mode 100644 index 000000000..ff010bd3e --- /dev/null +++ b/src/conversations/res/values-vi/strings.xml @@ -0,0 +1,16 @@ + + + Chọn nhà cung cấp XMPP của bạn + Sử dụng conversations.im + Tạo tài khoản mới + Bạn đã có tài khoản XMPP chưa? Điều này có thể đúng nếu bạn đang dùng một ứng dụng khách cho XMPP khác hoặc đã sử dụng Conversations trước đó. Nếu không, bạn có thể tạo tài khoản XMPP mới ngay bây giờ.\nGợi ý: Một số nhà cung cấp email cũng cung cấp tài khoản XMPP. + XMPP là một mạng nhắn tin ngay lập tức không phụ thuộc vào nhà cung cấp. Bạn có thể sử dụng ứng dụng khách này với bất kỳ máy chủ XMPP nào mà bạn chọn.\nTuy nhiên, vì sự thuận tiện của bạn, chúng tôi đã làm cho việc tạo tài khoản trên conversations.im¹ được dễ dàng; một nhà cung cấp đặc biệt phù hợp với việc sử dụng Conversations. + Bạn đã được mời vào %1$s. Chúng tôi sẽ hướng dẫn bạn trong quá trình tạo tài khoản.\nKhi chọn %1$s là 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. + Bạn đã được mời vào %1$s. Một tên người dùng đã được chọn sẵn cho bạn. Chúng tôi sẽ hướng dẫn bạn trong quá trình tạo tài khoản.\nBạ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. + Lời mời vào máy chủ của bạn + Mã cung cấp không được định dạng đúng + Nhấn nút chia sẻ để gửi lời mời vào %1$s đến liên hệ của bạn. + Nếu liên hệ của bạn ở gần đây, họ cũng có thể quét mã ở dưới để chấp nhận lời mời của bạn. + Hãy tham gia vào %1$s và trò chuyện với tôi: %2$s + Chia sẻ lời mời với... + \ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 4b771abc6..34c023e40 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -6,6 +6,7 @@ import android.net.Uri; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import eu.siacs.conversations.crypto.XmppDomainVerifier; import eu.siacs.conversations.xmpp.Jid; @@ -35,7 +36,7 @@ public final class Config { return (ENCRYPTION_MASK & (ENCRYPTION_MASK - 1)) != 0; } - public static final String LOGTAG = BuildConfig.LOGTAG; + public static final String LOGTAG = BuildConfig.APP_NAME.toLowerCase(Locale.US); public static final Jid BUG_REPORTS = Jid.of("bugs@chat.sum7.eu"); public static final Uri HELP = Uri.parse("https://sum7.eu/chat"); @@ -107,7 +108,6 @@ public final class Config { public static final boolean USE_BOOKMARKS2 = false; - public static final boolean PROCESS_EXTMAP_ALLOW_MIXED = false; public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb public static final boolean USE_DIRECT_JINGLE_CANDIDATES = true; public static final boolean DISABLE_HTTP_UPLOAD = false; diff --git a/src/main/java/eu/siacs/conversations/crypto/DomainHostnameVerifier.java b/src/main/java/eu/siacs/conversations/crypto/DomainHostnameVerifier.java index b6695611e..4349db45e 100644 --- a/src/main/java/eu/siacs/conversations/crypto/DomainHostnameVerifier.java +++ b/src/main/java/eu/siacs/conversations/crypto/DomainHostnameVerifier.java @@ -1,10 +1,11 @@ package eu.siacs.conversations.crypto; import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; public interface DomainHostnameVerifier extends HostnameVerifier { - boolean verify(String domain, String hostname, SSLSession sslSession); + boolean verify(String domain, String hostname, SSLSession sslSession) throws SSLPeerUnverifiedException; } diff --git a/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java b/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java index 7b741b864..ed17c5695 100644 --- a/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java +++ b/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java @@ -3,6 +3,8 @@ package eu.siacs.conversations.crypto; import android.util.Log; import android.util.Pair; +import com.google.common.collect.ImmutableList; + import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.DERIA5String; import org.bouncycastle.asn1.DERTaggedObject; @@ -18,15 +20,17 @@ import java.io.IOException; import java.net.IDN; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; +import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; -public class XmppDomainVerifier implements DomainHostnameVerifier { +public class XmppDomainVerifier { private static final String LOGTAG = "XmppDomainVerifier"; @@ -94,68 +98,93 @@ public class XmppDomainVerifier implements DomainHostnameVerifier { return false; } - @Override - public boolean verify(final String unicodeDomain,final String unicodeHostname, SSLSession sslSession) { + public boolean verify(final String unicodeDomain, final String unicodeHostname, SSLSession sslSession) throws SSLPeerUnverifiedException { final String domain = IDN.toASCII(unicodeDomain); final String hostname = unicodeHostname == null ? null : IDN.toASCII(unicodeHostname); + final Certificate[] chain = sslSession.getPeerCertificates(); + if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) { + return false; + } + final X509Certificate certificate = (X509Certificate) chain[0]; + final List commonNames = getCommonNames(certificate); + if (isSelfSigned(certificate)) { + if (commonNames.size() == 1 && matchDomain(domain, commonNames)) { + Log.d(LOGTAG, "accepted CN in self signed cert as work around for " + domain); + return true; + } + } try { - final Certificate[] chain = sslSession.getPeerCertificates(); - if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) { - return false; - } - final X509Certificate certificate = (X509Certificate) chain[0]; - final List commonNames = getCommonNames(certificate); - if (isSelfSigned(certificate)) { - if (commonNames.size() == 1 && matchDomain(domain, commonNames)) { - Log.d(LOGTAG, "accepted CN in self signed cert as work around for " + domain); - return true; - } - } - final Collection> alternativeNames = certificate.getSubjectAlternativeNames(); - final List xmppAddrs = new ArrayList<>(); - final List srvNames = new ArrayList<>(); - final List domains = new ArrayList<>(); - if (alternativeNames != null) { - for (List san : alternativeNames) { - final Integer type = (Integer) san.get(0); - if (type == 0) { - final Pair otherName = parseOtherName((byte[]) san.get(1)); - if (otherName != null && otherName.first != null && otherName.second != null) { - switch (otherName.first) { - case SRV_NAME: - srvNames.add(otherName.second.toLowerCase(Locale.US)); - break; - case XMPP_ADDR: - xmppAddrs.add(otherName.second.toLowerCase(Locale.US)); - break; - default: - Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second); - } - } - } else if (type == 2) { - final Object value = san.get(1); - if (value instanceof String) { - domains.add(((String) value).toLowerCase(Locale.US)); - } - } - } - } - if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) { - domains.addAll(commonNames); - } - Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + srvNames + " xmppAddrs: " + xmppAddrs + " domains:" + domains); + final ValidDomains validDomains = parseValidDomains(certificate); + Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + validDomains.srvNames + " xmppAddrs: " + validDomains.xmppAddrs + " domains:" + validDomains.domains); if (hostname != null) { Log.d(LOGTAG, "also trying to verify hostname " + hostname); } - return xmppAddrs.contains(domain) - || srvNames.contains("_xmpp-client." + domain) - || matchDomain(domain, domains) - || (hostname != null && matchDomain(hostname, domains)); + return validDomains.xmppAddrs.contains(domain) + || validDomains.srvNames.contains("_xmpp-client." + domain) + || matchDomain(domain, validDomains.domains) + || (hostname != null && matchDomain(hostname, validDomains.domains)); } catch (final Exception e) { return false; } } + public static ValidDomains parseValidDomains(final X509Certificate certificate) throws CertificateParsingException { + final List commonNames = getCommonNames(certificate); + final Collection> alternativeNames = certificate.getSubjectAlternativeNames(); + final List xmppAddrs = new ArrayList<>(); + final List srvNames = new ArrayList<>(); + final List domains = new ArrayList<>(); + if (alternativeNames != null) { + for (List san : alternativeNames) { + final Integer type = (Integer) san.get(0); + if (type == 0) { + final Pair otherName = parseOtherName((byte[]) san.get(1)); + if (otherName != null && otherName.first != null && otherName.second != null) { + switch (otherName.first) { + case SRV_NAME: + srvNames.add(otherName.second.toLowerCase(Locale.US)); + break; + case XMPP_ADDR: + xmppAddrs.add(otherName.second.toLowerCase(Locale.US)); + break; + default: + Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second); + } + } + } else if (type == 2) { + final Object value = san.get(1); + if (value instanceof String) { + domains.add(((String) value).toLowerCase(Locale.US)); + } + } + } + } + if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) { + domains.addAll(commonNames); + } + return new ValidDomains(xmppAddrs, srvNames, domains); + } + + public static final class ValidDomains { + final List xmppAddrs; + final List srvNames; + final List domains; + + private ValidDomains(List xmppAddrs, List srvNames, List domains) { + this.xmppAddrs = xmppAddrs; + this.srvNames = srvNames; + this.domains = domains; + } + + public List all() { + ImmutableList.Builder all = new ImmutableList.Builder<>(); + all.addAll(xmppAddrs); + all.addAll(srvNames); + all.addAll(domains); + return all.build(); + } + } + private boolean isSelfSigned(X509Certificate certificate) { try { certificate.verify(certificate.getPublicKey()); @@ -164,9 +193,4 @@ public class XmppDomainVerifier implements DomainHostnameVerifier { return false; } } - - @Override - public boolean verify(String domain, SSLSession sslSession) { - return verify(domain, null, sslSession); - } } diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 969a9c765..0796a1c00 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -635,6 +635,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable REGISTRATION_INVALID_TOKEN(true,false), REGISTRATION_PASSWORD_TOO_WEAK(true, false), TLS_ERROR, + TLS_ERROR_DOMAIN, INCOMPATIBLE_SERVER, TOR_NOT_AVAILABLE, DOWNGRADE_ATTACK, @@ -701,6 +702,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable return R.string.account_status_regis_invalid_token; case TLS_ERROR: return R.string.account_status_tls_error; + case TLS_ERROR_DOMAIN: + return R.string.account_status_tls_error_domain; case INCOMPATIBLE_SERVER: return R.string.account_status_incompatible_server; case TOR_NOT_AVAILABLE: diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index 7a3ce765d..706b50043 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Locale; import java.util.TimeZone; +import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.AxolotlService; @@ -65,7 +66,6 @@ public abstract class AbstractGenerator { Namespace.JINGLE_MESSAGE }; protected XmppConnectionService mXmppConnectionService; - private String mVersion = null; AbstractGenerator(XmppConnectionService service) { this.mXmppConnectionService = service; @@ -77,18 +77,11 @@ public abstract class AbstractGenerator { } String getIdentityVersion() { - if (mVersion == null) { - this.mVersion = PhoneHelper.getVersionName(mXmppConnectionService); - } - return this.mVersion; + return BuildConfig.VERSION_NAME; } String getIdentityName() { - return mXmppConnectionService.getString(R.string.app_name); - } - - public String getUserAgent() { - return mXmppConnectionService.getString(R.string.app_name) + '/' + getIdentityVersion(); + return BuildConfig.APP_NAME; } String getIdentityType() { diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java index 0701afcfe..1485385bc 100644 --- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java @@ -25,12 +25,19 @@ public class PresenceGenerator extends AbstractGenerator { return packet; } - public PresencePacket requestPresenceUpdatesFrom(Contact contact) { + public PresencePacket requestPresenceUpdatesFrom(final Contact contact) { + return requestPresenceUpdatesFrom(contact, null); + } + + public PresencePacket requestPresenceUpdatesFrom(final Contact contact, final String preAuth) { PresencePacket packet = subscription("subscribe", contact); String displayName = contact.getAccount().getDisplayName(); if (!TextUtils.isEmpty(displayName)) { packet.addChild("nick", Namespace.NICK).setContent(displayName); } + if (preAuth != null) { + packet.addChild("preauth", Namespace.PARS).setAttribute("token", preAuth); + } return packet; } diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java index a16242be0..566ce1e6a 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java @@ -19,10 +19,10 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; +import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Message; @@ -41,7 +41,24 @@ public class HttpConnectionManager extends AbstractConnectionManager { public static final Executor EXECUTOR = Executors.newFixedThreadPool(4); - private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient(); + public static final OkHttpClient OK_HTTP_CLIENT; + + static { + OK_HTTP_CLIENT = new OkHttpClient.Builder() + .addInterceptor(chain -> { + final Request original = chain.request(); + final Request modified = original.newBuilder() + .header("User-Agent", getUserAgent()) + .build(); + return chain.proceed(modified); + }) + .build(); + } + + + public static String getUserAgent() { + return String.format("%s/%s", BuildConfig.APP_NAME, BuildConfig.VERSION_NAME); + } public HttpConnectionManager(XmppConnectionService service) { super(service); @@ -124,7 +141,6 @@ public class HttpConnectionManager extends AbstractConnectionManager { private void setupTrustManager(final OkHttpClient.Builder builder, final boolean interactive) { final X509TrustManager trustManager; - final HostnameVerifier hostnameVerifier = mXmppConnectionService.getMemorizingTrustManager().wrapHostnameVerifier(new StrictHostnameVerifier(), interactive); if (interactive) { trustManager = mXmppConnectionService.getMemorizingTrustManager().getInteractive(); } else { @@ -133,7 +149,7 @@ public class HttpConnectionManager extends AbstractConnectionManager { try { final SSLSocketFactory sf = new TLSSocketFactory(new X509TrustManager[]{trustManager}, mXmppConnectionService.getRNG()); builder.sslSocketFactory(sf, trustManager); - builder.hostnameVerifier(hostnameVerifier); + builder.hostnameVerifier(new StrictHostnameVerifier()); } catch (final KeyManagementException | NoSuchAlgorithmException ignored) { } } diff --git a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java index 3d33eefa5..c46568c3e 100644 --- a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java +++ b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java @@ -50,7 +50,7 @@ public class ChannelDiscoveryService { } void initializeMuclumbusService() { - final OkHttpClient.Builder builder = new OkHttpClient.Builder(); + final OkHttpClient.Builder builder = HttpConnectionManager.OK_HTTP_CLIENT.newBuilder(); if (service.useTorToConnect()) { builder.proxy(HttpConnectionManager.getProxy()); } diff --git a/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java b/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java index e94c1c5c0..b51b8de41 100644 --- a/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java +++ b/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java @@ -42,16 +42,15 @@ import android.util.SparseArray; import androidx.appcompat.app.AppCompatActivity; import com.google.common.base.Charsets; +import com.google.common.base.Joiner; import com.google.common.io.CharStreams; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -63,12 +62,10 @@ import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; -import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Collection; import java.util.Enumeration; import java.util.List; import java.util.Locale; @@ -76,14 +73,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.DomainHostnameVerifier; +import eu.siacs.conversations.crypto.XmppDomainVerifier; import eu.siacs.conversations.entities.MTMDecision; import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.persistance.FileBackend; @@ -101,12 +96,12 @@ import eu.siacs.conversations.ui.MemorizingActivity; */ public class MemorizingTrustManager { + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US); final static String DECISION_INTENT = "de.duenndns.ssl.DECISION"; public final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId"; public final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert"; public final static String DECISION_TITLE_ID = DECISION_INTENT + ".titleId"; - final static String DECISION_INTENT_CHOICE = DECISION_INTENT + ".decisionChoice"; final static String NO_TRUST_ANCHOR = "Trust anchor for certification path not found."; private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); @@ -114,7 +109,6 @@ public class MemorizingTrustManager { private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); private final static Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName()); - private final static int NOTIFICATION_ID = 100509; static String KEYSTORE_DIR = "KeyStore"; static String KEYSTORE_FILE = "KeyStore.bks"; private static int decisionId = 0; @@ -168,20 +162,6 @@ public class MemorizingTrustManager { this.defaultTrustManager = getTrustManager(null); } - /** - * Changes the path for the KeyStore file. - *

- * The actual filename relative to the app's directory will be - * app_dirname/filename. - * - * @param dirname directory to store the KeyStore. - * @param filename file name for the KeyStore. - */ - public static void setKeyStoreFile(String dirname, String filename) { - KEYSTORE_DIR = dirname; - KEYSTORE_FILE = filename; - } - private static boolean isIp(final String server) { return server != null && ( PATTERN_IPV4.matcher(server).matches() @@ -217,9 +197,7 @@ public class MemorizingTrustManager { MessageDigest md = MessageDigest.getInstance(digest); md.update(cert.getEncoded()); return hexString(md.digest()); - } catch (java.security.cert.CertificateEncodingException e) { - return e.getMessage(); - } catch (java.security.NoSuchAlgorithmException e) { + } catch (CertificateEncodingException | NoSuchAlgorithmException e) { return e.getMessage(); } } @@ -240,7 +218,7 @@ public class MemorizingTrustManager { } } - void init(Context m) { + void init(final Context m) { master = m; masterHandler = new Handler(m.getMainLooper()); notificationManager = (NotificationManager) master.getSystemService(Context.NOTIFICATION_SERVICE); @@ -263,36 +241,6 @@ public class MemorizingTrustManager { appKeyStore = loadAppKeyStore(); } - /** - * Binds an Activity to the MTM for displaying the query dialog. - *

- * This is useful if your connection is run from a service that is - * triggered by user interaction -- in such cases the activity is - * visible and the user tends to ignore the service notification. - *

- * You should never have a hidden activity bound to MTM! Use this - * function in onResume() and @see unbindDisplayActivity in onPause(). - * - * @param act Activity to be bound - */ - public void bindDisplayActivity(AppCompatActivity act) { - foregroundAct = act; - } - - /** - * Removes an Activity from the MTM display stack. - *

- * Always call this function when the Activity added with - * {@link #bindDisplayActivity(AppCompatActivity)} is hidden. - * - * @param act Activity to be unbound - */ - public void unbindDisplayActivity(AppCompatActivity act) { - // do not remove if it was overridden by a different activity - if (foregroundAct == act) - foregroundAct = null; - } - /** * Get a list of all certificate aliases stored in MTM. * @@ -307,21 +255,6 @@ public class MemorizingTrustManager { } } - /** - * Get a certificate for a given alias. - * - * @param alias the certificate's alias as returned by {@link #getCertificates()}. - * @return the certificate associated with the alias or null if none found. - */ - public Certificate getCertificate(String alias) { - try { - return appKeyStore.getCertificate(alias); - } catch (KeyStoreException e) { - // this should never happen, however... - throw new RuntimeException(e); - } - } - /** * Removes the given certificate from MTMs key store. * @@ -340,32 +273,6 @@ public class MemorizingTrustManager { keyStoreUpdated(); } - /** - * Creates a new hostname verifier supporting user interaction. - * - *

This method creates a new {@link HostnameVerifier} that is bound to - * the given instance of {@link MemorizingTrustManager}, and leverages an - * existing {@link HostnameVerifier}. The returned verifier performs the - * following steps, returning as soon as one of them succeeds: - * /p> - *

    - *
  1. Success, if the wrapped defaultVerifier accepts the certificate.
  2. - *
  3. Success, if the server certificate is stored in the keystore under the given hostname.
  4. - *
  5. Ask the user and return accordingly.
  6. - *
  7. Failure on exception.
  8. - *
- * - * @param defaultVerifier the {@link HostnameVerifier} that should perform the actual check - * @return a new hostname verifier using the MTM's key store - * @throws IllegalArgumentException if the defaultVerifier parameter is null - */ - public DomainHostnameVerifier wrapHostnameVerifier(final HostnameVerifier defaultVerifier, final boolean interactive) { - if (defaultVerifier == null) - throw new IllegalArgumentException("The default verifier may not be null"); - - return new MemorizingHostnameVerifier(defaultVerifier, interactive); - } - X509TrustManager getTrustManager(KeyStore ks) { try { TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); @@ -452,16 +359,8 @@ public class MemorizingTrustManager { } } - private boolean isExpiredException(Throwable e) { - do { - if (e instanceof CertificateExpiredException) - return true; - e = e.getCause(); - } while (e != null); - return false; - } - public void checkCertTrusted(X509Certificate[] chain, String authType, String domain, boolean isServer, boolean interactive) + private void checkCertTrusted(X509Certificate[] chain, String authType, String domain, boolean isServer, boolean interactive) throws CertificateException { LOGGER.log(Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")"); try { @@ -470,13 +369,8 @@ public class MemorizingTrustManager { appTrustManager.checkServerTrusted(chain, authType); else appTrustManager.checkClientTrusted(chain, authType); - } catch (CertificateException ae) { + } catch (final CertificateException ae) { LOGGER.log(Level.FINER, "checkCertTrusted: appTrustManager failed", ae); - // if the cert is stored in our appTrustManager, we ignore expiredness - if (isExpiredException(ae)) { - LOGGER.log(Level.INFO, "checkCertTrusted: accepting expired certificate from keystore"); - return; - } if (isCertKnown(chain[0])) { LOGGER.log(Level.INFO, "checkCertTrusted: accepting cert already stored in keystore"); return; @@ -632,14 +526,24 @@ public class MemorizingTrustManager { return myId; } - private void certDetails(StringBuffer si, X509Certificate c) { - SimpleDateFormat validityDateFormater = new SimpleDateFormat("yyyy-MM-dd"); + private void certDetails(final StringBuffer si, final X509Certificate c, final boolean showValidFor) { + si.append("\n"); - si.append(c.getSubjectDN().toString()); + if (showValidFor) { + try { + si.append("Valid for: "); + si.append(Joiner.on(", ").join(XmppDomainVerifier.parseValidDomains(c).all())); + } catch (final CertificateParsingException e) { + si.append("Unable to parse Certificate"); + } + si.append("\n"); + } else { + si.append(c.getSubjectDN()); + } si.append("\n"); - si.append(validityDateFormater.format(c.getNotBefore())); + si.append(DATE_FORMAT.format(c.getNotBefore())); si.append(" - "); - si.append(validityDateFormater.format(c.getNotAfter())); + si.append(DATE_FORMAT.format(c.getNotAfter())); si.append("\nSHA-256: "); si.append(certHash(c, "SHA-256")); si.append("\nSHA-1: "); @@ -652,7 +556,7 @@ public class MemorizingTrustManager { private String certChainMessage(final X509Certificate[] chain, CertificateException cause) { Throwable e = cause; LOGGER.log(Level.FINE, "certChainMessage for " + e); - StringBuffer si = new StringBuffer(); + final StringBuffer si = new StringBuffer(); if (e.getCause() != null) { e = e.getCause(); // HACK: there is no sane way to check if the error is a "trust anchor @@ -667,46 +571,13 @@ public class MemorizingTrustManager { si.append(master.getString(R.string.mtm_connect_anyway)); si.append("\n\n"); si.append(master.getString(R.string.mtm_cert_details)); - for (X509Certificate c : chain) { - certDetails(si, c); + si.append('\n'); + for(int i = 0; i < chain.length; ++i) { + certDetails(si, chain[i], i == 0); } return si.toString(); } - private String hostNameMessage(X509Certificate cert, String hostname) { - StringBuffer si = new StringBuffer(); - - si.append(master.getString(R.string.mtm_hostname_mismatch, hostname)); - si.append("\n\n"); - try { - Collection> sans = cert.getSubjectAlternativeNames(); - if (sans == null) { - si.append(cert.getSubjectDN()); - si.append("\n"); - } else for (List altName : sans) { - Object name = altName.get(1); - if (name instanceof String) { - si.append("["); - si.append(altName.get(0)); - si.append("] "); - si.append(name); - si.append("\n"); - } - } - } catch (CertificateParsingException e) { - e.printStackTrace(); - si.append("\n"); - } - si.append("\n"); - si.append(master.getString(R.string.mtm_connect_anyway)); - si.append("\n\n"); - si.append(master.getString(R.string.mtm_cert_details)); - certDetails(si, cert); - return si.toString(); - } - /** * Returns the top-most entry of the activity stack. * @@ -764,17 +635,6 @@ public class MemorizingTrustManager { } } - boolean interactHostname(X509Certificate cert, String hostname) { - switch (interact(hostNameMessage(cert, hostname), R.string.mtm_accept_servername)) { - case MTMDecision.DECISION_ALWAYS: - storeCert(hostname, cert); - case MTMDecision.DECISION_ONCE: - return true; - default: - return false; - } - } - public X509TrustManager getNonInteractive(String domain) { return new NonInteractiveMemorizingTrustManager(domain); } @@ -791,57 +651,6 @@ public class MemorizingTrustManager { return new InteractiveMemorizingTrustManager(null); } - class MemorizingHostnameVerifier implements DomainHostnameVerifier { - private final HostnameVerifier defaultVerifier; - private final boolean interactive; - - public MemorizingHostnameVerifier(HostnameVerifier wrapped, boolean interactive) { - this.defaultVerifier = wrapped; - this.interactive = interactive; - } - - @Override - public boolean verify(String domain, String hostname, SSLSession session) { - LOGGER.log(Level.FINE, "hostname verifier for " + domain + ", trying default verifier first"); - // if the default verifier accepts the hostname, we are done - if (defaultVerifier instanceof DomainHostnameVerifier) { - if (((DomainHostnameVerifier) defaultVerifier).verify(domain, hostname, session)) { - return true; - } - } else { - if (defaultVerifier.verify(domain, session)) { - return true; - } - } - - - // otherwise, we check if the hostname is an alias for this cert in our keystore - try { - X509Certificate cert = (X509Certificate) session.getPeerCertificates()[0]; - //Log.d(TAG, "cert: " + cert); - if (cert.equals(appKeyStore.getCertificate(domain.toLowerCase(Locale.US)))) { - LOGGER.log(Level.FINE, "certificate for " + domain + " is in our keystore. accepting."); - return true; - } else { - LOGGER.log(Level.FINE, "server " + domain + " provided wrong certificate, asking user."); - if (interactive) { - return interactHostname(cert, domain); - } else { - return false; - } - } - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } - - @Override - public boolean verify(String domain, SSLSession sslSession) { - return verify(domain, null, sslSession); - } - } - private class NonInteractiveMemorizingTrustManager implements X509TrustManager { private final String domain; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index b570330d0..759a0d727 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -3448,15 +3448,23 @@ public class XmppConnectionService extends Service { } } - public void createContact(Contact contact, boolean autoGrant) { + public void createContact(final Contact contact, final boolean autoGrant) { + createContact(contact, autoGrant, null); + } + + public void createContact(final Contact contact, final boolean autoGrant, final String preAuth) { if (autoGrant) { contact.setOption(Contact.Options.PREEMPTIVE_GRANT); contact.setOption(Contact.Options.ASKING); } - pushContactToServer(contact); + pushContactToServer(contact, preAuth); } public void pushContactToServer(final Contact contact) { + pushContactToServer(contact, null); + } + + private void pushContactToServer(final Contact contact, final String preAuth) { contact.resetOption(Contact.Options.DIRTY_DELETE); contact.setOption(Contact.Options.DIRTY_PUSH); final Account account = contact.getAccount(); @@ -3472,7 +3480,7 @@ public class XmppConnectionService extends Service { sendPresencePacket(account, mPresenceGenerator.sendPresenceUpdatesTo(contact)); } if (ask) { - sendPresencePacket(account, mPresenceGenerator.requestPresenceUpdatesFrom(contact)); + sendPresencePacket(account, mPresenceGenerator.requestPresenceUpdatesFrom(contact, preAuth)); } } else { syncRoster(contact.getAccount()); diff --git a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java index 455058d01..f2cd1e348 100644 --- a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java +++ b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java @@ -5,24 +5,26 @@ import android.content.Intent; import android.preference.Preference; import android.util.AttributeSet; +import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.R; import eu.siacs.conversations.utils.PhoneHelper; public class AboutPreference extends Preference { public AboutPreference(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); - final String appName = context.getString(R.string.app_name); - setSummary(appName +' '+ PhoneHelper.getVersionName(context)); - setTitle(context.getString(R.string.title_activity_about_x, appName)); + setSummaryAndTitle(context); } public AboutPreference(final Context context, final AttributeSet attrs) { super(context, attrs); - final String appName = context.getString(R.string.app_name); - setSummary(appName +' '+ PhoneHelper.getVersionName(context)); - setTitle(context.getString(R.string.title_activity_about_x, appName)); + setSummaryAndTitle(context); } + private void setSummaryAndTitle(final Context context) { + setSummary(String.format("%s %s", BuildConfig.APP_NAME, BuildConfig.VERSION_NAME)); + setTitle(context.getString(R.string.title_activity_about_x, BuildConfig.APP_NAME)); + } + @Override protected void onClick() { super.onClick(); diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 1e9ea0062..e6d3ebf6e 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -39,6 +39,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; @@ -525,7 +526,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } else if (contact.showInRoster()) { throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists)); } else { - xmppConnectionService.createContact(contact, true); + final String preAuth = invite == null ? null : invite.getParameter(XmppUri.PARAMETER_PRE_AUTH); + xmppConnectionService.createContact(contact, true, preAuth); if (invite != null && invite.hasFingerprints()) { xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints()); } @@ -731,7 +733,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { if (mRequestedContactsPermission.compareAndSet(false, true)) { - if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) { + if (QuickConversationsService.isQuicksy() || shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); final AtomicBoolean requestPermission = new AtomicBoolean(false); builder.setTitle(R.string.sync_with_contacts); @@ -740,20 +742,26 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } else { builder.setMessage(getString(R.string.sync_with_contacts_long, getString(R.string.app_name))); } - builder.setPositiveButton(R.string.next, (dialog, which) -> { + @StringRes int confirmButtonText; + if (QuickConversationsService.isConversations()) { + confirmButtonText = R.string.next; + } else { + confirmButtonText = R.string.confirm; + } + builder.setPositiveButton(confirmButtonText, (dialog, which) -> { if (requestPermission.compareAndSet(false, true)) { requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS); } }); builder.setOnDismissListener(dialog -> { - if (requestPermission.compareAndSet(false, true)) { + if (QuickConversationsService.isConversations() && requestPermission.compareAndSet(false, true)) { requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS); } }); - builder.setCancelable(false); + builder.setCancelable(QuickConversationsService.isQuicksy()); final AlertDialog dialog = builder.create(); - dialog.setCanceledOnTouchOutside(false); + dialog.setCanceledOnTouchOutside(QuickConversationsService.isQuicksy()); dialog.setOnShowListener(dialogInterface -> { final TextView tv = dialog.findViewById(android.R.id.message); if (tv != null) { diff --git a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java index f868e3337..57e1aadcb 100644 --- a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java @@ -95,7 +95,8 @@ public class UriHandlerActivity extends AppCompatActivity { } @Override - public void onNewIntent(Intent intent) { + public void onNewIntent(final Intent intent) { + super.onNewIntent(intent); handleIntent(intent); } @@ -120,7 +121,7 @@ public class UriHandlerActivity extends AppCompatActivity { startActivity(intent); return; } - if (xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter(XmppUri.PARAMETER_IBR))) { + 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); diff --git a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java index 0238dc975..a894cab67 100644 --- a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java @@ -35,17 +35,4 @@ public class PhoneHelper { cursor.close(); return uri == null ? null : Uri.parse(uri); } - - public static String getVersionName(Context context) { - final String packageName = context == null ? null : context.getPackageName(); - if (packageName != null) { - try { - return context.getPackageManager().getPackageInfo(packageName, 0).versionName; - } catch (final PackageManager.NameNotFoundException | RuntimeException e) { - return "unknown"; - } - } else { - return "unknown"; - } - } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index bc20d3c5f..5827ddfa7 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -46,6 +46,7 @@ import java.util.regex.Matcher; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509KeyManager; @@ -401,7 +402,7 @@ public class XmppConnection implements Runnable { return tag != null && tag.isStart("stream"); } - private TlsFactoryVerifier getTlsFactoryVerifier() throws NoSuchAlgorithmException, KeyManagementException, IOException { + private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException { final SSLContext sc = SSLSocketHelper.getSSLContext(); final MemorizingTrustManager trustManager = this.mXmppConnectionService.getMemorizingTrustManager(); final KeyManager[] keyManager; @@ -412,9 +413,7 @@ public class XmppConnection implements Runnable { } final String domain = account.getServer(); sc.init(keyManager, new X509TrustManager[]{mInteractive ? trustManager.getInteractive(domain) : trustManager.getNonInteractive(domain)}, mXmppConnectionService.getRNG()); - final SSLSocketFactory factory = sc.getSocketFactory(); - final DomainHostnameVerifier verifier = trustManager.wrapHostnameVerifier(new XmppDomainVerifier(), mInteractive); - return new TlsFactoryVerifier(factory, verifier); + return sc.getSocketFactory(); } @Override @@ -789,19 +788,25 @@ public class XmppConnection implements Runnable { } private SSLSocket upgradeSocketToTls(final Socket socket) throws IOException { - final TlsFactoryVerifier tlsFactoryVerifier; + final SSLSocketFactory sslSocketFactory; try { - tlsFactoryVerifier = getTlsFactoryVerifier(); + sslSocketFactory = getSSLSocketFactory(); } catch (final NoSuchAlgorithmException | KeyManagementException e) { throw new StateChangingException(Account.State.TLS_ERROR); } final InetAddress address = socket.getInetAddress(); - final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true); + final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, address.getHostAddress(), socket.getPort(), true); SSLSocketHelper.setSecurity(sslSocket); SSLSocketHelper.setHostname(sslSocket, IDN.toASCII(account.getServer())); SSLSocketHelper.setApplicationProtocol(sslSocket, "xmpp-client"); - if (!tlsFactoryVerifier.verifier.verify(account.getServer(), this.verifiedHostname, sslSocket.getSession())) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed"); + final XmppDomainVerifier xmppDomainVerifier = new XmppDomainVerifier(); + try { + if (!xmppDomainVerifier.verify(account.getServer(), this.verifiedHostname, sslSocket.getSession())) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate domain verification failed"); + FileBackend.close(sslSocket); + throw new StateChangingException(Account.State.TLS_ERROR_DOMAIN); + } + } catch (final SSLPeerUnverifiedException e) { FileBackend.close(sslSocket); throw new StateChangingException(Account.State.TLS_ERROR); } @@ -1711,19 +1716,6 @@ public class XmppConnection implements Runnable { UNKNOWN } - private static class TlsFactoryVerifier { - private final SSLSocketFactory factory; - private final DomainHostnameVerifier verifier; - - TlsFactoryVerifier(final SSLSocketFactory factory, final DomainHostnameVerifier verifier) throws IOException { - this.factory = factory; - this.verifier = verifier; - if (factory == null || verifier == null) { - throw new IOException("could not setup ssl"); - } - } - } - private class MyKeyManager implements X509KeyManager { @Override public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { 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 9a506513b..39031c4a9 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java @@ -215,7 +215,7 @@ public class SessionDescription { mediaAttributes.put("extmap", id + " " + uri); } - if (Config.PROCESS_EXTMAP_ALLOW_MIXED && description.hasChild("extmap-allow-mixed", Namespace.JINGLE_RTP_HEADER_EXTENSIONS)) { + if (description.hasChild("extmap-allow-mixed", Namespace.JINGLE_RTP_HEADER_EXTENSIONS)) { mediaAttributes.put("extmap-allow-mixed", ""); } 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 e518d3d69..7b9caa66c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -136,13 +136,6 @@ public class WebRTCWrapper { @Override public void onAddStream(MediaStream mediaStream) { Log.d(EXTENDED_LOGGING_TAG, "onAddStream(numAudioTracks=" + mediaStream.audioTracks.size() + ",numVideoTracks=" + mediaStream.videoTracks.size() + ")"); - final List videoTracks = mediaStream.videoTracks; - if (videoTracks.size() > 0) { - remoteVideoTrack = videoTracks.get(0); - Log.d(Config.LOGTAG, "remote video track enabled?=" + remoteVideoTrack.enabled()); - } else { - Log.d(Config.LOGTAG, "no remote video tracks found"); - } } @Override @@ -164,10 +157,9 @@ public class WebRTCWrapper { public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) { final MediaStreamTrack track = rtpReceiver.track(); Log.d(EXTENDED_LOGGING_TAG, "onAddTrack(kind=" + (track == null ? "null" : track.kind()) + ",numMediaStreams=" + mediaStreams.length + ")"); - if (track != null) { - Log.d(EXTENDED_LOGGING_TAG,"onAddTrack(class="+track.getClass().getName()+")"); + if (track instanceof VideoTrack) { + remoteVideoTrack = (VideoTrack) track; } - } @Override diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 7e1b66976..684632474 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -964,4 +964,4 @@ 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. - + diff --git a/src/main/res/values-da-rDK/strings.xml b/src/main/res/values-da-rDK/strings.xml index e9e2a1653..9129da103 100644 --- a/src/main/res/values-da-rDK/strings.xml +++ b/src/main/res/values-da-rDK/strings.xml @@ -960,4 +960,4 @@ 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. - + diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index dd81765ac..ab5fa6b91 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -150,6 +150,7 @@ 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, die du zum Teilen dieser Datei verwendet hast, hat nicht die erforderlichen Berechtigungen bereitgestellt. Unbekannt Vorübergehend abgeschaltet Online @@ -164,6 +165,7 @@ Registrierung wird vom Server nicht unterstützt Ungültiger Registrierungstoken TLS-Aushandlung fehlgeschlagen + Domain nicht überprüfbar Verstoß gegen die Richtlinien Inkompatibler Server Stream Fehler @@ -960,4 +962,5 @@ 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. + Video kann nicht aktiviert werden. diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index a5232f422..1caf22f35 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -150,6 +150,7 @@ 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 que usaches para compartir este ficheiro non concedeu os permisos suficientes. Descoñecido Desactivado temporalmente Conectado @@ -164,6 +165,7 @@ O servidor non permite o rexistro O testemuño de rexistro non é válido Fallo a negociación TLS + Dominio non verificable Violación da política Servidor incompatible Erro de fluxo @@ -269,7 +271,7 @@ Esta conta xa existe Seguinte Sesión establecida - Saltar + Omitir Desactivar notificacións Habilitar A conversa en grupo require contrasinal @@ -960,4 +962,5 @@ O servidor non soporta a creación de convites 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 32b52121e..0da6a9e9f 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -960,4 +960,4 @@ Il server non supporta la generazione di inviti Nessun account attivo supporta questa funzione Il backup è iniziato. Riceverai una notifica una volta completato. - + diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 0ba8b6c53..83b981881 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -7,7 +7,7 @@ 会話を閉じる 連絡先の詳細 グループチャットの詳細 - チャンネルの詳細 + 談話室の詳細 アカウントを追加 名前を編集 アドレス帳に追加 @@ -234,11 +234,11 @@ ブックマークとして保存 ブックマークを削除 グループチャットを破棄する - チャンネルを破棄する + 談話室を破棄する このグループチャットを破棄してもよろしいですか?\n\n警告: グループチャットはサーバーから完全に削除されます。 - この公開談話室を破棄してもよろしいですか?\n\n警告: チャンネルはサーバーから完全に削除されます。 + この公開談話室を破棄してもよろしいですか?\n\n警告: 談話室はサーバーから完全に削除されます。 グループチャットを破棄できません - チャンネルを破棄できません + 談話室を破棄できません グループチャットの題を編集 トピック グループチャットに参加しています… @@ -286,7 +286,7 @@ 消音時間の間、通知は無音になります その他 ブックマークと同期 - ブックマークに従って、グループチャットに自動参加します。 + ブックマークに従って、グループチャットに自動で参加します。 OMEMO フィンガープリントをクリップボードにコピーしました このグループチャットから追い出されています このグループチャットはメンバー制です @@ -379,10 +379,10 @@ 所有者権限を付与 所有者権限を取消 グループチャットから削除 - チャンネルから削除 + 談話室から削除 %s の所属を変更できません グループチャットから追い出す - チャンネルから追い出す + 談話室から追い出す あなたは公開談話室から %s を削除しようとしています。その唯一の手段は、そのユーザーを永久に追い出すことです。 今すぐ追い出す %s の役割を変更できません @@ -390,7 +390,7 @@ 公開談話室の環境設定 非公開、メンバーのみ XMPPアドレスを誰でも見れるようにする - チャンネルの調停をする + 談話室の調停をする あなたは参加していません グループチャットのオプションが変更されました! グループチャットのオプションを変更できませんでした @@ -633,7 +633,7 @@ %dか月 - 自動メッセージ削除 + 自動でメッセージを削除 設定された期間よりも古いメッセージを、このデバイスから自動的に削除します。 メッセージの暗号化中 ローカル保存期間のためにメッセージを取得しません。 @@ -737,6 +737,11 @@ ステータス情報 接続の問題 この通知カテゴリーは、アカウントへの接続に問題があった場合に、通知を表示するために使用されます。 + メッセージ + 通話 + メッセージ + 着信 + 発信 この通知グループは、音を鳴らしてはいけない通知を表示するために使用します。例えば、他のデバイスでアクティブになっているときなどです (猶予期間)。 配信に失敗 メッセージ通知設定 @@ -802,9 +807,9 @@ Orbot をインストール Orbot を開始 マーケットアプリがインストールされていません。 - このチャンネルでは、あなたのXMPPアドレスを公開します + この談話室では、あなたのXMPPアドレスを公開します 電子書籍 - 原物(非圧縮) + 原物 (非圧縮) …で開く Conversations プロフィール写真 アカウントを選択 @@ -820,15 +825,15 @@ 公開談話室に参加 非公開グループチャットを作成 公開談話室を作成 - チャンネル名 + 談話室名 XMPP アドレス - チャンネルの名前をご記入ください + 談話室の名前をご記入ください XMPP アドレスをご記入ください これは XMPP アドレスです。名前をご記入ください。 公開談話室を作成中… - このチャンネルは既に存在します - 存在しているチャンネルに参加しています - チャンネルの環境設定を保存できません + この談話室は既に存在します + 存在している談話室に参加しています + 談話室の環境設定を保存できません 誰にでもトピックの編集を許可 誰にでも他の人の招待を許可 誰でもトピックを編集できます。 @@ -844,16 +849,16 @@ 参加者を検索 ファイルが大きすぎます 添付 - チャンネルを発見 - チャンネルを検索 + 談話室発見 + 談話室を検索 プライバシー侵害の可能性あり! - search.jabber.networkを利用します。

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

この機能を使うと、あなたののIPアドレスや検索キーワードがそのサービスに送信されます。詳しくは、プライバシーポリシーをご覧ください。]]>
私は既にアカウントを持っています 存在するアカウントを追加 新しいアカウントを登録 これはドメインアドレスのようです とにかく追加 - これはチャンネルアドレスのようです + これは談話室アドレスのようです バックアップファイルを共有 Conversations のバックアップ 出来事 @@ -864,11 +869,11 @@ この操作を実行できません 公開談話室に参加… 共有アプリがこのファイルへのアクセスを許可していませんでした。 - + jabber.network ローカルサーバー ほとんどのユーザーは、公開されている XMPP エコシステム全体からより良い提案を得るために、‘jabber.network’を選択するはずです。 - チャンネル発見方法 + 談話室発見方法 アカウントを有効にしてください 通話をする 通話着信 @@ -917,7 +922,7 @@ 音声メールを録音 音声再生 音声一時中断 - 連絡先を追加、作成またはグループチャットに参加、またはチャンネルを発見する + 連絡先を追加、作成またはグループチャットに参加、または談話室を発見する %1$d人の参加者を表示 @@ -932,4 +937,4 @@ サーバーは招待をサポートしていません この機能をサポートするアクティブなアカウントがありません バックアップを開始しました。 バックアップが完了すると通知が届きます。 - + diff --git a/src/main/res/values-ml/strings.xml b/src/main/res/values-ml/strings.xml index 695df4ed5..e4e82c214 100644 --- a/src/main/res/values-ml/strings.xml +++ b/src/main/res/values-ml/strings.xml @@ -2,131 +2,285 @@ ക്രമീകരണങ്ങൾ പുതിയ സംഭാഷണം + അക്കൗണ്ടുകൾ നിയന്ത്രിക്കൂ + അക്കൗണ്ട് നിയന്ത്രിക്കൂ സംഭാഷണം അടയ്ക്കൂ + കോൺ‌ടാക്റ്റ് വിശദാംശങ്ങൾ + ഗ്രൂപ്പ് ചാറ്റ് വിശദാംശങ്ങൾ ചാനൽ വിവരങ്ങൾ അക്കൗണ്ട് ചേർക്കൂ പെര് തിരുത്തുക + കോൺ‌ടാക്റ്റ് തടയുക + കോൺ‌ടാക്റ്റ് തടഞ്ഞത് മാറ്റുക മേഖല തടയുക + പങ്കാളിയെ തടയുക ക്രമീകരണങ്ങൾ സംഭാഷണം ആരംഭിക്കൂ + കോൺ‌ടാക്റ്റ് തിരഞ്ഞെടുക്കുക + കോൺ‌ടാക്റ്റുകൾ തിരഞ്ഞെടുക്കുക + അക്കൗണ്ട് വഴി പങ്കിടുക + തടഞ്ഞവയുടെ പട്ടിക ഇപ്പോൾ 1 മിനിറ്റ് മുമ്പ് %d മിനിറ്റ് മുമ്പ് + + %d വായിക്കാത്ത സംഭാഷണം + + + %d വായിക്കാത്ത സംഭാഷണങ്ങൾ + + അയയ്ക്കുന്നു... + OpenPGP സുരക്ഷിതമാക്കിയ സന്ദേശം + വിളിപ്പേര് ഇതിനകം ഉപയോഗത്തിലാണ് + അസാധുവായ വിളിപ്പേര് അഡ്മിൻ ഉടമ + മോഡറേറ്റർ + പങ്കെടുക്കുന്നയാൾ + സന്ദർശകൻ + %s-ൽ നിന്ന് എല്ലാ കോൺ‌ടാക്റ്റുകളും തടയണോ? + കോൺ‌ടാക്റ്റ് തടഞ്ഞു തടഞ്ഞു + സെർവറിൽ രഹസ്യവാക്ക് മാറ്റുക + ഇതുമായി പങ്കിടുക… സംഭാഷണം ആരംഭിക്കുക + കോൺ‌ടാക്റ്റിനെ ക്ഷണിക്കുക ക്ഷണിക്കൂ + കോൺ‌ടാക്റ്റുകൾ + കോൺ‌ടാക്റ്റ് റദ്ദാക്കൂ + സജ്ജമാക്കൂ ചേർക്കൂ തിരുത്തുക ഇല്ലാതാക്കൂ തടയുക + തടഞ്ഞത് മാറ്റുക സംരക്ഷിക്കൂ ശരി %1$s തകർന്നു ഇപ്പോൾ അയയ്‌ക്കൂ ഒരിക്കലും ചോദിക്കരുത് + ഫയൽ ഉൾപ്പെടുത്തുക + കോൺടാക്റ്റ് ചേർക്കൂ + ചിത്രങ്ങൾ അയയ്ക്കാൻ തയ്യാറാകുന്നു + ഫയലുകൾ പങ്കിടുന്നു. കാത്തിരിക്കൂ… ചരിത്രം മായ്ക്കൂ സംഭാഷണ ചരിത്രം മായ്ക്കൂ ഫയൽ ഇല്ലാതാക്കൂ ഉപകരണം തിരഞ്ഞെടുക്കൂ സുരക്ഷിതമല്ലാത്ത സന്ദേശം അയയ്കൂ സന്ദേശം അയയ്ക്കൂ + %s-ന് (ക്ക്) സന്ദേശം അയയ്ക്കൂ + OMEMO സുരക്ഷിതമാക്കിയ സന്ദേശം അയയ്ക്കൂ + v\\OMEMO സുരക്ഷിതമാക്കിയ സന്ദേശം അയയ്ക്കൂ + OpenPGP സുരക്ഷിതമാക്കിയ സന്ദേശം അയയ്ക്കൂ + പുതിയ വിളിപ്പേര് ഉപയോഗത്തിലുള്ളതാണ് OpenKeychain + പുനരാരംഭിക്കൂ സ്ഥാപിക്കൂ + OpenKeychain ഇൻസ്റ്റാൾ ചെയ്യുക കാത്തിരിക്കുന്നു... + OpenPGP കീ ഒന്നും കണ്ടെത്തിയില്ല പൊതുവായവ ഫയലുകൾ സ്വീകരിക്കൂ + അറ്റാച്ചുമെന്റുകൾ അറിയിപ്പ് LED അറിയിപ്പ് + റിംഗ്‌ടോൺ + അറിയിപ്പ് ശബ്‌ദം + ഇൻകമിംഗ് കോളുകൾക്കുള്ള റിംഗ്‌ടോൺ വിപുലമായ ഒരിക്കലും ക്രാഷ് റിപ്പോർട്ടുകൾ അയയ്‌ക്കരുത് സന്ദേശങ്ങൾ ഉറപ്പാക്കൂ UI സ്വീകരിക്കുക + ഒരു പിശക് സംഭവിച്ചു നിങ്ങളുടെ അക്കൗണ്ട് + സാന്നിധ്യ അപ്‌ഡേറ്റുകൾ അയയ്‌ക്കുക + ചിത്രം തിരഞ്ഞെടുക്കൂ ഫോട്ടോ എടുക്കൂ + നിങ്ങൾ തിരഞ്ഞെടുത്ത ഫയൽ ഒരു ചിത്രം അല്ല ഫയൽ കണ്ടില്ല + അജ്ഞാതം + ഓൺലൈൻ + ഓഫ്‌ലൈൻ + സെർവർ കണ്ടെത്തിയില്ല + ഉപയോക്തൃനാമം ഇതിനകം നിലവിലുണ്ട് + രജിസ്ട്രേഷൻ പൂർത്തിയായി + സുരക്ഷിതമല്ലാത്ത OTR OpenPGP OMEMO അക്കൗണ്ട് ഇല്ലാതാക്കൂ + അവതാർ പ്രസിദ്ധീകരിക്കൂ + OpenPGP പബ്ലിക് കീ പ്രസിദ്ധീകരിക്കുക + OpenPGP പബ്ലിക് കീ നീക്കം ചെയ്യുക നിങ്ങള്ക്ക് ഉറപ്പാണോ? + ശബ്‌ദം റെക്കോർഡുചെയ്യൂ XMPP വിലാസം XMPP വിലാസം തടയുക + username@example.com + രഹസ്യവാക്ക് + ഈ XMPP വിലാസ‍ം അസാധുവാണ് + മെമ്മറി തീർന്നു. ചിത്രം വളരെ വലുതാണ് + സെർവർ വിവരം XEP-0313: MAM ലഭ്യമാണ് ലഭ്യമല്ല അവസാനം കണ്ടത് ഇപ്പോൾ - അവസാനമായി കണ്ടത് ഒരു മിനിറ്റ് മുമ്പ് + അവസാനം കണ്ടത് ഒരു മിനിറ്റ് മുമ്പ് + അവസാനം കണ്ടത് %d മിനിറ്റ് മുമ്പ് + അവസാനം കണ്ടത് ഒരു മണിക്കൂർ മുമ്പ് + അവസാനം കണ്ടത് %d മണിക്കൂർ മുമ്പ് + അവസാനം കണ്ടത് ഒരു ദിവസം മുമ്പ് + അവസാനം കണ്ടത് %d ദിവസം മുമ്പ് + OMEMO വിരലടയാളം + v\\OMEMO വിരലടയാളം + മറ്റ് ഉപകരണങ്ങൾ + കീകൾ ലഭ്യമാക്കുന്നു... ചെയ്തു + ഡീക്രിപ്റ്റ് ചെയ്യുക + അടയാളകുറിപ്പുകൾ തിരയുക + കോൺ‌ടാക്റ്റ് നൽകുക + കോൺ‌ടാക്റ്റ് ഇല്ലാതാക്കൂ + കോൺ‌ടാക്റ്റ് വിവരങ്ങൾ കാണിക്കൂ + കോൺ‌ടാക്റ്റ് തടയുക + കോൺ‌ടാക്റ്റ് തടഞ്ഞത് മാറ്റുക സൃഷ്ടിക്കൂ തിരഞ്ഞെടുക്കൂ + കോൺ‌ടാക്റ്റ് ഇതിനകം നിലവിലുണ്ട് ചേരുക ഗ്രൂപ്പ് ചാറ്റ് നശിപ്പിക്കൂ ചാനൽ നശിപ്പിക്കൂ വിഷയം ഗ്രൂപ്പ് ചാറ്റിൽ ചേരുന്നു... + തിരികെ ചേർക്കൂ %s ഇത് വരെ വായിച്ചിട്ടുണ്ട് %s ഇത് വരെ വായിച്ചിട്ടുണ്ട് എല്ലാവരും ഇത് വരെ വായിച്ചിട്ടുണ്ട് + പ്രസിദ്ധീകരിക്കൂ + പ്രസിദ്ധീകരിക്കുന്നു... + ഈ അക്കൗണ്ട് ഇതിനകം നിലവിലുണ്ട് അടുത്തത് + സെഷൻ സ്ഥാപിച്ചു ഒഴിവാക്കൂ + ഗ്രൂപ്പ് ചാറ്റിന് രഹസ്യവാക്ക് ആവശ്യമാണ് + രഹസ്യവാക്ക് നൽകുക + ഇപ്പോൾ അഭ്യർത്ഥിക്കുക ഒഴിവാക്കൂ + സുരക്ഷ + വിദഗ്ദ്ധ ക്രമീകരണങ്ങൾ + %s-നെ കുറിച്ച് + ആരംഭ സമയം മറ്റുള്ളവ + നിങ്ങളെ ഗ്രൂപ്പ് ചാറ്റിൽ നിന്ന് പുറത്താക്കി %s അക്കൗണ്ട് ഉപയോഗിക്കുന്നു + %s-ന്റെ വലുപ്പം പരിശോധിക്കൂ + സന്ദേശ ഓപ്ഷനുകൾ + യഥാർത്ഥ URL പകർത്തുക വീണ്ടും അയയ്ക്കൂ ഫയൽ URL + ക്ലിപ്പ്ബോർഡിലേക്ക് URL പകർത്തി + ക്ലിപ്പ്ബോർഡിലേക്ക് XMPP വിലസം പകർത്തി + വെബ് വിലാസം ഉറപ്പാക്കൂ വീണ്ടും ശ്രമിക്കുക + ബാക്കപ്പ് സൃഷ്ടിക്കൂ + ബാക്കപ്പ് ഫയലുകൾ സൃഷ്ടിക്കുന്നു ഫയൽ തിരഞ്ഞെടുക്കൂ %s ഇല്ലാതാക്കൂ ഫയൽ %s തുറക്കൂ + ഫയൽ പങ്കിടാൻ തയ്യാറാകുന്നു ഫയൽ ഇല്ലാതാക്കി + അറിയിപ്പുകൾ പ്രാപ്തമാക്കൂ അക്കൗണ്ട് അവതാർ + ഉപകരണങ്ങൾ മായ്ക്കൂ + എന്തോ കുഴപ്പം സംഭവിച്ചു + സെർവറിൽ നിന്ന് ചരിത്രം ലഭ്യമാക്കുന്നു പുതുക്കുന്നു... + രഹസ്യവാക്ക് മാറ്റി + രഹസ്യവാക്ക് മാറ്റുക + നിലവിലെ രഹസ്യവാക്ക് + പുതിയ രഹസ്യവാക്ക് + ഓഫ്‌ലൈൻ അംഗം വിപുലമായ മോഡ് + ഗ്രൂപ്പ് ചാറ്റിൽ നിന്ന് മാറ്റുക + ചാനലിൽ നിന്ന് നീക്കം ചെയ്യുക + ഗ്രൂപ്പ് ചാറ്റിൽ നിന്ന് നിരോധിക്കൂ ചാനലിൽ നിന്ന് നിരോധിക്കൂ ഇപ്പോൾ നിരോധിക്കൂ + സ്വകാര്യ, അംഗങ്ങൾ മാത്രം + മറുപടി + വായിച്ചതായി കാണിക്കൂ + എന്റെർ കീ അയയ്ക്കും + ശബ്ദം + വീഡിയോ ചിത്രം + %s അയയ്ക്കുന്നു + %s ടൈപ്പുചെയ്യുന്നു… റദ്ദാക്കൂ + സമീപകാലത്ത് ഉപയോഗിച്ചത് + കോൺ‌ടാക്റ്റുകൾ തിരയുക + സ്വകാര്യ സന്ദേശം അയയ്‌ക്കൂ ഉപയോക്തൃനാമം ഉപയോക്തൃനാമം + ഡൗൺലോഡ് പരാജയപ്പെട്ടു: സെർവർ കണ്ടെത്തിയില്ല + ഡൗൺലോഡ് പരാജയപ്പെട്ടു: ഫയൽ കണ്ടെത്തിയില്ല + തകർന്നു ലഭ്യത xmpp.example.com + CAPTCHA നിർബന്ധമാണ് %d സന്ദേശം %d സന്ദേശങ്ങൾ കൂടുതൽ സന്ദേശങ്ങൾ ലഭ്യമാക്കൂ + എല്ലാ സന്ദേശങ്ങളും അറിയിക്കൂ എപ്പോഴും വലിയ ചിത്രങ്ങൾ മാത്രം + സന്ദേശം തിരുത്തുക + സമ്മതിച്ച് തുടരുക അക്കൗണ്ട് സൃഷ്ടിക്കൂ + എന്റെ സ്വന്തം ദാതാവിനെ ഉപയോഗിക്കുക നിങ്ങളുടെ ഉപയോക്തൃനാമം തിരഞ്ഞെടുക്കൂ + ഓൺലൈൻ + ലഭ്യമല്ല + തിരക്കിലാണ് വീണ്ടും ക്ഷണിക്കൂ ഞാൻ അനുവദിക്കൂ + മുഴുവൻ മേഖലയും തടയുക + ഇപ്പോൾ സജീവം വെബ്സൈറ്റ് തുറക്കൂ ഇന്ന് ഇന്നലെ + സന്ദേശം ഒരിക്കൽ പങ്കിടുക + സന്ദേശങ്ങൾ തിരയുക GIF + പേര് സന്ദേശങ്ങൾ + കോളുകൾ സന്ദേശങ്ങൾ + നിശബ്‌ദ സന്ദേശങ്ങൾ പങ്കെടുക്കുന്നവർ + ഒരു രാജ്യം തിരഞ്ഞെടുക്കൂ ഫോൺ നമ്പർ നിങ്ങളുടെ ഫോൺ നമ്പർ ഉറപ്പാക്കൂ + നിങ്ങളുടെ ഫോൺ നമ്പർ നൽകുക. %s ഉറപ്പാക്കൂ SMS വീണ്ടും അയയ്ക്കൂ SMS വീണ്ടും അയയ്ക്കൂ (%s) അതെ ഉറപ്പാക്കുന്നു... + SMS അഭ്യർത്ഥിക്കുന്നു… + നിങ്ങളുടെ പേര് + നിങ്ങളുടെ പേര് നൽകുക അക്കൗണ്ട് തിരഞ്ഞെടുക്കൂ XMPP വിലാസം നൽകുക XMPP വിലാസം diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 7a7ba0b50..8c18ddc86 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -987,4 +987,4 @@ 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. - + diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index d48230dc1..68296a762 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -150,6 +150,7 @@ Arquivo não encontrado Ocorreu um erro genérico de E/S. Você tem espaço de armazenamento suficiente no seu aparelho? O aplicativo que você usou para selecionar esta imagem não nos forneceu permissões suficientes para ler o arquivo.\n\nUtilize um gerenciador de arquivos diferente para selecionar a imagem. + O app que você usou para compartilhar esse arquivo não forneceu permissões suficientes. Desconhecido Temporariamente desabilitado Conectado @@ -164,6 +165,7 @@ O registro não é suportado pelo servidor Token de registro inválido Não foi possível efetuar a negociação TLS + Domínio não verificável Violação de política Servidor incompatível Erro de fluxo @@ -960,4 +962,5 @@ O servidor não suporta a criação de convites 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 5b2f051be..d9e5901d7 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -973,4 +973,4 @@ 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ă. - + diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 629c45ac7..673a7944c 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -985,4 +985,5 @@ Невозможно разобрать приглашение Сервер не поддерживает создание приглашений Ни один активный аккаунт не поддерживает эту функцию + Резервное копирование было начато. Вы получите уведомление, как только оно будет завершено. diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml index 4a8edee89..623bb6a94 100644 --- a/src/main/res/values-sr/strings.xml +++ b/src/main/res/values-sr/strings.xml @@ -74,6 +74,8 @@ Одблокирај Сачувај У реду + %1$s је принудно заустављен + Слањем извештаја рада путем вашег ИксМПП налога помажете даљем развоју %1$s. Пошаљи одмах Не питај више Не могу да се повежем са налогом @@ -99,6 +101,7 @@ Пошаљи ОМЕМО шифровану поруку Пошаљи v\\ОМЕМО шифровану поруку Пошаљи ОпенПГП шифровану поруку + Нови надимак је у употреби Пошаљи нешифровано Шифровање није успело. Можда немате одговарајући лични кључ. Отворени кључарник @@ -119,12 +122,17 @@ ЛЕД светло Трептање ЛЕД светла кад стигне нова порука Звук + Звук обавештења + Звук обавештења нових порука + Мелодија долазног позива Период одгоде Напредно Никад не шаљи извештаје о паду Потврди поруке Обзнаните контактима када примите и прочитате њихове поруке Сучеље + Отворени кључарник је направио грешку. + Лош кључ за шифровање. Прихвати Десила се грешка Грешка @@ -136,6 +144,7 @@ Фотографиши Унапред дозволи захтев за претплатом Изабрани фајл није слика + Не могу преобратити датотеку фотографије Фајл није нађен Општа У/И грешка. Можда вам је нестало простора у складишту? Непознато @@ -149,10 +158,14 @@ Регистрација није успела Корисничко име је већ у употреби Регистрација завршена + Овај сервер не подржава регистрацију + Неисправан регистрациони токен ТЛС преговарање није успело + Непроверљив домен Нарушавање полисе Некомпатибилан сервер Грешка тока + Грешка при отварању тока Нешифровано ОТР ОпенПГП @@ -163,11 +176,16 @@ Објави ОпенПГП јавни кључ Уклони ОпенПГП кључ Желите ли заиста да уклоните ваш ОпенПГП кључ из ваше објаве присутности?\nВаши контакти више неће моћи да вам шаљу ОпенПГП шифроване поруке. + ОпенПГП кључ је објављен. Укључи налог Да ли сте сигурни? Сними глас + ИксМПП адреса + Блокирај ИксМПП адресу korisnickoime@primer.com Лозинка + Ово је неисправна ИксМПП адреса + Недовољно меморије. Фотографија је превелика Желите ли да додате %s у ваш именик? Подаци о серверу XEP-0313: МАМ @@ -176,6 +194,7 @@ XEP-0191: наредба блокирања XEP-0237: верзионисање ростера XEP-0198: менаџмент тока + XEP-0215: Проналажење спољњих сервиса XEP-0163: PEP (аватари/ОМЕМО) XEP-0363: ХТТП отпремање фајлова XEP-0357: „push“ @@ -183,17 +202,25 @@ недоступан Недостају објаве јавног кључа виђен/а мало пре + виђен/а пре минут виђен/а пре %d минута + виђен/а пре сат времена виђен/а пре %d сати + виђен/а јуче виђен/а пре %d дана + Шифрована порука. Инсталирајте Отворени кључарник да је дешифрујете. + Пронаћене су нове ОпенПГП шифроване поруке ИД ОпенПГП кључа ОМЕМО отисак v\\ОМЕМО отисак + ОМЕМО отисак (порекло поруке) + v\\ОМЕМО отисак (порекло поруке) Остали уређаји Поуздај се у ОМЕМО отиске Добављам кључеве… Готово Дешифруј + Обележивачи Тражи Унеси контакт Обриши контакт @@ -204,8 +231,14 @@ Изабери Контакт већ постоји Придружи се + channel@conference.example.com/nick + channel@conference.example.com Сачувај као обележивач Обриши обележивач + Уклони групно ћаскање + Уклони канал + Не могу уклонити групно ћаскање + Не могу уклонити канал Уреди предмет групног ћаскања Тема Улазим у групно ћаскање… @@ -214,18 +247,23 @@ Додај га %s је прочитао довде %s је прочитао/ла довде + %1$s + %2$d других су прочитали довде Сви су прочитали довде Објави + Тапните аватар да изаберете слику из галерије Објављујем… Сервер је одбио вашу објаву + Не могу преобратити вашу фотографију Не могох да сачувам аватар на диск (или притисните дуго да вратите подразумевани) + Ваш сервер не подржава објаву аватара шапну за %s Пошаљи личну поруку за %s Повежи Овај налог већ постоји Следеће + Сесија успостављена Прескочи Искључи обавештења Укључи @@ -238,18 +276,24 @@ Дозвољава вашим контактима да ретроактивно уређују њихове поруке Поставке за стручњаке Будите пажљиви са овим + О %s Тихи сати Време почетка Време завршетка Укључи тихе сате Обавештења ће бити ућуткана за време тихих сати Остало - Забрањени сте на овом групном ћаскању + Синхронизуј са обележивачима + Аутоматски се придружите групним ћаскањима по поставци обележивача + ОМЕМО отисак копиран на клипборд + Забрањен вам је приступ овом групном ћаскању Ово групно ћаскање је само за чланове + Ограничење ресурса Шутнути сте из овог групног ћаскања Групно ћаскање је угашено Више нисте у овом групном ћаскању преко налога %s + код домаћина %s Проверавам %s на ХТТП домаћину Нисте повезани. Покушајте поново касније Провери величину %s @@ -261,6 +305,8 @@ Пошаљи поново УРЛ фајла УРЛ је копиран на клипборд + ИксМПП адреса копирана на клипборд + Порука грешке копирана на клипборд веб адреса Очитај 2Д бар-кôд Прикажи 2Д бар-кôд @@ -268,7 +314,16 @@ Детаљи налога Потврди Покушај поново + Сервис у првом плану Спречава оперативни систем да прекине вашу везу + Направите резерву + Резерва ће бити складиштена у %s + Правим резерву + Ваша резерва је направљена + Резерве су складиштене у %s + Учитавам резерву + Ваша резерва је учитана + Не заборавите да омогућите налог Изабери фајл Примам %1$s (%2$d%% завршено) Преузми %s @@ -276,12 +331,20 @@ фајл Отвори %s шаљем (%1$d%% завршено) + Припремам датотеку за пренос %s понуђен за преузимање Прекини пренос + не могу поделити датотеку + пренос датотеке је прекинут + Датотека је обрисана + Нема апликације за отварање датотеке + Нема апликације за отварање везе + Нема апликације за приказ контакта Динамичке ознаке Приказ ознака испод контаката Укључи обавештења Сервер групног ћаскања није нађен + Не могу направити групно ћаскање Аватар налога Копирај ОМЕМО отисак на клипборд Поново генериши ОМЕМО кључ @@ -295,6 +358,7 @@ Промени лозинку Текућа лозинка Нова лозинка + Лозинка не може бити празна Укључи све налоге Искључи све налоге Изврши радњу са @@ -303,14 +367,25 @@ Изгнаник Члан Напредни режим + Одобри админ. привилегије + Укини админ. привилегије Одобри админ. привилегије Одобри админ. привилегије + Одобри власничке привилегије + Укини власничке привилегије Уклони из групног ћаскања + Уклони из канала Не могох да изменим припадност за %s - Забрани за групно ћаскање + Забрани приступ групном ћаскању + Забрани приступ каналу + Покушавате да уклоните %s из јавног канала. Ово се за стално постиже једино забраном приступа кориснику. Забрани одмах Не могох да изменим улогу за %s + Поставке приватног групног ћаскања + Поставке јавног канала Лична, само чланови + Начините ИксМПП адресу видљиву свима + Начините канал модерисаним Не учествујете Опције групног ћаскања измењене! Не могу да изменим опције групног ћаскања @@ -341,8 +416,11 @@ Обзнаните контактима када им куцате поруке Пошаљи локацију Прикажи локацију + Нема апликације за приказ локације Локација Преписка затворена + Напустили сте групно ћаскање + Напустили сте јавни канал Не поуздај се у системска сертификациона тела Сви сертификати морају ручно да се одобре Уклони сертификате @@ -356,12 +434,15 @@ %d сертификата обрисана %d сертификата обрисано + Замени дугме за слање брзом радњом Брза радња Ниједна Недавно коришћена Изаберите брзу радњу Тражи контакте + Претрага обележивача Пошаљи личну поруку + %1$s је напустио/ла групно ћаскање Корисничко име Корисничко име Ово није исправно корисничко име @@ -371,16 +452,28 @@ Преузимање није успело: не могох да упишем фајл Тор мрежа недоступна Неуспех свезивања + Сервер није одговоран за овај домен Оштећен Доступност + Увек када је уређај закљчан + Прикажи ме одсутним када је уређај закљчан + Заузет у нечујном режиму + Прикажи ме заузетум у нечујном режиму Вибрација је нечујни режим + Прикажи ме заузетум у режиму вибрације Проширене поставке повезивања Приказ домаћина и порта у поставкама налога xmpp.primer.com + Пријавите се сертификатом + Не могу прочитати сертификат Поставке архивисања Серверске поставке архивисања Добављам поставке архивисања, сачекајте… + Не могу да добавим поставке архивисања + КЕПЧА је обавезна Унесите текст са слике изнад + Неповерљив ланац сертификата + ИксМПП адреса се не слаже са сертификатом Обнови сертификат Грешка добављања ОМЕМО кључа! Оверен ОМЕМО кључ помоћу сертификата! @@ -390,6 +483,7 @@ Тунеловање свих веза кроз Тор мрежу. Захтева Орбот Име домаћина Порт + Серверска или .onion адреса Ово није исправан број порта Ово није исправно име домаћина %1$d од %2$d налога повезано @@ -399,11 +493,20 @@ %d порука Учитај још порука + Датотека подељена са %s + Слика подељена са %s + Слике подељене са %s + Текст подељен са %s + Дозволите да %1$s приступи спољној меморији + Дозволите да %1$s приступи камери Синхронизуј са контактима Обавештења за све поруке + Обавести само када ме помињу Обавештења искључена Обавештења паузирана + Компресија слике увек + Само велике слике Оптимизација батерије је укључена Искључи Назначена површина је превелика @@ -412,10 +515,14 @@ Исправи поруку Пошаљи исправљену поруку Искључили сте овај налог + Нема апликације за дељење ресурса Подели везу помоћу… + Сложи се и настави + Ваша цела ИксМПП адреса ће бити: %s Направи налог Користићу сопствени провајдер Одредите ваше корисничко име + Ручно мењај доступност Порука стања Слободан за ћаскање На вези @@ -433,11 +540,17 @@ Кратак Средњи Дуг + Објави употребу + Обзнаните контакте кад користите Конверзацију Приватност Тема Избор палете боја + Аутопатски + Светла + Тамна Зелена позадина Зелена позадина за примљене поруке + Не могу да се повежем са Отвореним кључарником Овај уређај више није у употреби Рачунар Мобилни телефон @@ -445,6 +558,7 @@ Веб прегледач Конзола Захтевано је плаћање + Дозволите приступ интернету Ја Контакт пита за претплату на ажурирање присутности Дозволи diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml index 9a5839f98..deebac8f8 100644 --- a/src/main/res/values-vi/strings.xml +++ b/src/main/res/values-vi/strings.xml @@ -1,9 +1,13 @@ Cài đặt - Hội thoại mới + Cuộc hội thoại mới Quản lý tài khoản + Quản lý tài khoản + Đóng cuộc hội thoại Thông tin liên hệ + Chi tiết cuộc trò chuyện nhóm + Chi tiết kênh Thêm tài khoản Chỉnh sửa tên Thêm vào danh bạ @@ -12,31 +16,47 @@ Bỏ chặn liên hệ Chặn miền Bỏ chặn miền + Chặn thành viên + Bỏ chặn thành viên Quản lý tài khoản Cài đặt Chia sẻ qua Conversation Khởi chạy Conversation + Chọn liên hệ + Chọn liên hệ + Chia sẻ qua tài khoản Danh sách chặn mới đây 1 phút trước %d phút trước + + %d cuộc hội thoại chưa đọc + + đang gửi... Đang giải mã tin nhắn. Xin chờ... Tin nhắn mã hoá bằng OpenPGP Biệt danh đã được sử dụng + Biệt danh không hợp lệ Quản trị viên Chủ nhân Điều phối viên Thành viên Khách + Bạn có muốn xoá %s khỏi danh sách liên hệ của bạn không? Các cuộc hội thoại với liên hệ này sẽ không bị xoá. Bạn có muốn chặn %s gửi tin nhắn cho bạn? Bạn có muốn bỏ chặn %s và cho phép họ gửi tin nhắn cho bạn? Chặn tất cả liên hệ từ %s? Bỏ chặn tất cả liên hệ từ %s? Đã chặn liên hệ + Đã chặn + Bạn có muốn xoá dấu trang %s không? Các cuộc hội thoại với dấu trang này sẽ không bị xoá. Đăng ký tài khoản mới trên máy chủ Đổi mật k trên máy chủ Chia sẻ với... + Bắt đầu cuộc hội thoại + Mời liên hệ + Mời Danh bạ Liên hệ Huỷ @@ -48,30 +68,47 @@ Bỏ chặn Lưu OK + %1$s đã đột ngột dừng + Việc sử dụng tài khoản XMPP của bạn để gửi báo cáo hoạt động sẽ giúp sự phát triển liên tục của %1$s. Gửi ngay Đừng hỏi lại nữa + Không thể kết nối đến tài khoản + Không thể kết nối đến nhiều tài khoản + Nhấn để quản lý các tài khoản của bạn Đính kèm tập tin + Thêm liên hệ bị thiếu này vào danh sách liên hệ? Thêm liên hệ thất bại khi chuyển + Đang chuẩn bị sẵn sàng để gửi hình ảnh + Đang chuẩn bị sẵn sàng để gửi các hình ảnh Đang chia sẻ các tập tin. Xin chờ... Xoá lịch sử Xoá lịch sử hội thoại + Bạn có muốn xoá tất cả tin nhắn trong cuộc hội thoại này không?\n\nCảnh báo: Việc này sẽ không ảnh hưởng đến các tin nhắn được lưu trữ trên các thiết bị hoặc máy chủ khác. + Xoá tệp + Bạn có chắc bạn muốn xoá tệp này không?\n\nCảnh báo: Việc này sẽ không xoá các bản sao được lưu trữ trên các thiết bị hoặc máy chủ khác của tệp này. + Đóng cuộc hội thoại này sau đó Chọn thiết bị Gửi tin nhắn không mã hoá + Gửi tin nhắn Gửi tin nhắn đến %s Gửi tin nhắn mã hoá OMEMO Gửi tin nhắn mã hoá v\\OMEMO Gửi tin nhắn mã hoá OpenPGP + Biệt danh mới đang được sử dụng Gửi dạng không mã hoá Giải mã thất bại. Có lẽ bạn không có đúng khoá cá nhân. OpenKeychain + OpenKeychain để mã hoá và giải mã các tin nhắn và quản lý các mã khoá công khi của bạn.

Nó được cấp phép dưới GPLv3 và có sẵn trên F-Droid và Google Play.

(Vui lòng khởi động lại %1$s sau đó.)]]>
Khởi chạy lại Cài đặt Xin cài đặt OpenKeychain đang đề xuất... đang chờ... Không tìm thấy khoá OpenPGP + Không thể mã hoá tin nhắn của bạn vì liên hệ của bạn không thông báo mã khoá công khai của họ.\n\nVui lòng yêu cầu liên hệ của bạn thiết lập OpenPGP. Không tìm thấy các khoá OpenPGP + Không thể mã hoá tin nhắn của bạn vì các liên hệ của bạn không thông báo mã khoá công khai của họ.\n\nVui lòng yêu cầu họ thiết lập OpenPGP. Tổng quan Chấp thuận các tập tin Tự động chấp thuận các tập tin nhỏ hơn... @@ -82,14 +119,22 @@ Thông báo đèn LED Chớp đèn thông báo khi có tin nhắn mới Âm báo + Âm thanh thông báo + Âm thanh thông báo cho các tin nhắn mới + Nhạc chuông cho các cuộc gọi đến Thời gian gia hạn thông báo + Khoảng thời gian mà các thông báo được giữ im lặng sau khi phát hiện hoạt động trên một trong những thiết bị khác. Nâng cao Không bao giờ gửi báo cáo dừng chạy + 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 UI + OpenKeychain đã có lỗi. + Mã khoá mã hoá bị lỗi. Chấp thuận Đã có lỗi xảy ra + Lỗi Tài khoản của bạn Gửi cập nhật hiện diện Nhận cập nhật hiện diện @@ -98,8 +143,11 @@ Chụp hình Ưu tiên trao quyền yêu cầu đăng ký Tập tin bạn chọn không phải là hình ảnh + Không thể chuyển đổi tệp hình ảnh Không tìm thấy tập tin Lỗi I/O tổng quát. Có lẽ đã hết dung lượng lưu trữ? + Ứng dụng mà bạn dùng để chọn hình ảnh này không cung cấp đủ quyền để đọc tệp.\n\nHãy sử dụng trình quản lý tệp khác để chọn hình ảnh + Ứng dụng bạn dùng để chia sẻ tệp này không cung cấp đủ quyền. Không rõ Tạm thời tắt Trực tuyến @@ -111,9 +159,14 @@ Đăng ký thất bại Tên người dùng đã được sử dụng Đăng ký hoàn tất + Việc đăng ký không được máy chủ hỗ trợ + Mã đăng ký không hợp lệ + Thương lượng TLS thất bại + Miền không thể xác minh được Vi phạm chính sách Máy chủ không tương thích Lỗi truyền phát + Lỗi khi mở luồng truyền Không mã hoá OTR OpenPGP @@ -122,11 +175,19 @@ Tạm thời tắt Đăng ảnh đại diện Đăng khoá công cộng OpenPGP + Xoá mã khoá OpenPGP công khai + Bạn có chắc bạn muốn xoá mã khoá OpenPGP công khai của bạn khỏi sự thông báo có mặt của bạn không?\nCác liên hệ của bạn sẽ không thể gửi các tin nhắn được mã hoá bằng OpenPGP cho bạn nữa. + Đã xuất bản mã khoá OpenPGP công khai. Bật tài khoản Bạn chắc chứ? + Việc xoá tài khoản sẽ xoá toàn bộ lịch sử cuộc hội thoại của bạn Ghi âm + Địa chỉ XMPP + Chặn địa chỉ XMPP username@example.com Mật khẩu + Đây không phải là địa chỉ XMPP hợp lệ + Hết bộ nhớ. Hình ảnh quá lớn Bạn có muốn thêm %s vào danh bạ? Thông tin máy chủ XEP-0313: MAM @@ -135,6 +196,7 @@ XEP-0191: Blocking Command XEP-0237: Phiên bản hoá danh sách bạn bè XEP-0198: Stream Management + XEP-0215: Khám phá dịch vụ ngoài XEP-0163: PEP (Avatars / OMEMO) XEP-0363: HTTP File Upload XEP-0357: Push @@ -142,19 +204,28 @@ không sẵn sàng Thông báo khoá công cộng bị thất lạc thấy lần cuối vừa đây + đã xem một phút trước thấy lần cuối %d phút trước + đã xem một tiếng trước thấy lần cuối %d tiếng trước + đã xem một ngày trước thấy lần cuối %d ngày trước + Tin nhắn được mã hoá. Vui lòng cài đặt OpenKeychain để giải mã nó. + Đã tìm thấy các tin nhắn được mã hoá bằng OpenPGP mới ID khoá OpenPGP Dấu vân tay OMEMO Dấu vân tay v\\OMEMO + Mã vân tay OMEMO (nguồn gốc tin nhắn) + v\\Mã vân tay OMEMO (nguồn gốc tin nhắn) Các thiết bị khác Tin tưởng các dấu vân tay OMEMO Đang nhận khoá... Xong Giải mã + Dấu trang Tìm kiếm Nhập liên hệ + Xoá liên hệ Xem chi tiết liên hệ Chặn liên hệ Bỏ chặn liên hệ @@ -162,63 +233,132 @@ Chọn Đã có liên hệ này rồi Tham gia + channel@conference.example.com/nick + channel@conference.example.com Lưu thành đánh dấu Xoá đánh dấu + Phá huỷ cuộc trò chuyện nhóm + Phá huỷ kênh + Bạn có chắc bạn muốn phá huỷ cuộc trò chuyện nhóm này không?\n\nCảnh báo: Cuộc trò chuyện nhóm này sẽ bị xoá hoàn toàn trên máy chủ. + Bạn có chắc bạn muốn phá huỷ kênh công khai này không?\n\nCảnh báo: Kênh này sẽ bị xoá hoàn toàn trên máy chủ. + Không thể phá huỷ cuộc trò chuyện nhóm + Không thể phá huỷ kênh + Chỉnh sửa chủ đề cuộc trò chuyện nhóm + Chủ đề + Đang tham gia cuộc trò chuyện nhóm... Rời khỏi Liên hệ đã thêm bạn vào danh bạ Thêm họ vào %s đã đọc đến điểm này + %s đã đọc cho đến lúc này + %1$s +%2$d người khác đã đọc cho đến lúc này + Mọi người đã đọc cho đến lúc này Đăng + Nhấn ảnh đại diện để chọn ảnh từ thư viện Đang đăng... Máy chủ đã từ chối đăng tải của bạn + Không thể chuyển đổi hình ảnh Không thể lưu ảnh đại diện vào ổ đĩa (Hoặc nhấn giữ để chuyển về mặc định) + Máy chủ của bạn không hỗ trợ việc công khai ảnh đại diện đã thì thầm đến %s Gửi tin nhắn riêng tư đến %s Kết nối Đã có tài khoản này rồi Tiếp theo + Đã thiết lập phiên làm việc Bỏ qua Tắt thông báo Bật + Cuộc trò chuyện nhóm yêu cầu mật khẩu Nhập mật khẩu + Vui lòng yêu cầu cập nhật sự có mặt từ liên hệ của bạn trước tiên.\n\nViệc này sẽ được sử dụng để xác định ứng dụng trò chuyện mà liên hệ của bạn đang dùng. Yêu cầu ngay Bỏ qua + Cảnh báo: Việc gửi cái này mà không có cập nhật sự có mặt chung có thể sẽ gây ra các vấn đề không mong đợi.\n\nHãy đi đến \"Chi tiếi liên hệ\" để xác minh đăng ký sự có mặt của bạn. + Bảo mật + Cho phép việc sửa tin nhắn + Cho phép các liên hệ của bạn chỉnh sửa cảc tin nhắn của họ + Cài đặt chuyên gia Xin hãy cẩn trọng với chúng + Giới thiệu về %s Giờ yên lặng Thời gian bắt đầu Thời gian kết thúc Bật giờ yên lặng Thông báo sẽ được tắt trong giờ yên lặng Khác + Đồng bộ hoá bằng dấu trang + Tự động tham gia các cuộc trò chuyện nhóm nếu dấu trang bảo thế + Đã sao chép mã vân tay OMEMO vào bộ nhớ tạm + Bạn bị cấm khỏi cuộc trò chuyện nhóm này + Cuộc trò chuyện nhóm này chỉ dành cho thành viên + Tài nguyên bị hạn chế + Bạn đã bị đá ra khỏi cuộc trò chuyện nhóm này + Cuộc trò chuyện nhóm bị ngừng hoạt động + Bạn không còn ở trong cuộc trò chuyện nhóm này nữa đang dùng tài khoản %s + được lưu trữ trên %s Đang kiểm tra %s trên máy chủ HTTTP Bạn chưa kết nối mạng. Xin thử lại sau Kiểm tra kích cỡ %s + Kiểm tra %1$s kích cỡ trên %2$s Tuỳ chọn tin nhắn + Trích dẫn + Dán làm trích dẫn Sao chép URL gốc Gửi lại URL tập tin + Đã sao chép URL vào bộ nhớ tạm + Đã sao chép địa chỉ XMPP vào bộ nhớ tạm + Đã sao chép thông báo lỗi vào bộ nhớ tạm + địa chỉ web + Quét mã vạch 2D + Hiện mã vạch 2D Quét danh sách chặn Chi tiết tài khoản Xác nhận Thử lại + Dịch vụ ở trước Ngăn hệ điều hành ngắt kết nối của bạn + Tạo bản sao lưu + Các tệp sao lưu sẽ được lưu trữ trong %s + Đang tạo các tệp sao lưu + Đã tạo bản sao lưu + Đã lưu trữ các tệp sao lưu trong %s + Đang khôi phục bản sao lưu + Đã khôi phục bản sao lưu + Đừng quên bật tài khoản. Chọn tập tin Đang nhận %1$s (đã hoàn tất %2$d%%) Tải về %s + Xoá %s tập tin Mở %s đang gửi (đã hoàn tất %1$d%%) + Đang chuẩn bị sẵn sàng để chia sẻ tệp Đã đề xuất tải về %s Huỷ chuyển tập tin + không thể chia sẻ tệp + đã huỷ truyền tệp + Đã xoá tệp + Không tìm thấy ứng dụng nào để mở tệp + Không tìm thấy ứng dụng nào để mở liên kết + Không tìm thấy ứng dụng nào để xem liên hệ + Thẻ năng động Hiện nhãn chỉ đọc bên dưới các liên hệ Bật thông báo + Không tìm thấy máy chủ trò chuyện nhóm nào + Không thể tạo cuộc trò chuyện nhóm Ảnh đại diện tài khoản Sao chép dấu vân tay OMEMO vào clipboard Tạo lại khoá OMEMO Xoá các thiết bị + Bạn có chắc bạn muốn xoá tất cả thiết bị khác khỏi thông báo OMEMO không? Lần sau khi các thiết bị của bạn kết nối, chúng sẽ tự thông báo lại, nhưng có thể sẽ không nhận các tin nhắn được gửi trong lúc đó. + Không có mã khoá dùng được nào có sẵn cho liên hệ này.\nKhông thể lấy mã khoá mới từ máy chủ. Có lẽ có gì đó sai với máy chủ của liên hệ? + Không có mã khoá dùng được nào có sẵn cho liên hệ này.\nHãy chắc chắn là cả hai có đăng ký sự có mặt. + Có gì đó sai đã xảy ra Đang nhận lịch sử từ máy chủ Không còn lịch sử nào trên máy chủ Đang cập nhật... @@ -227,6 +367,7 @@ Đổi mật khẩu Mật khẩu hiện tại Mật khẩu mới + Mật khẩu không thể trống Bật toàn bộ tài khoản Tắt toàn bộ tài khoản Thực hiện thao tác với @@ -235,16 +376,36 @@ Kẻ bị ruồng bỏ Thành viên Chế độ nâng cao + Cấp đặc quyền thành viên + Thu hồi đặc quyền thành viên Trao quyền quản trị Huỷ quyền quản trị + Cấp đặc quyền chủ sở hữu + Thu hồi đặc quyền chủ sở hữu + Xoá khỏi cuộc trò chuyện nhóm + Xoá khỏi kênh Không thể đổi mối quan hệ của %s + Cấm khỏi cuộc trò chuyện nhóm + Cấm khỏi kênh + Bạn đang cố xoá %s khỏi một kênh công khai. Cách duy nhất để làm thế là cấm người dùng đó mãi mãi. Cấm ngay Không thể đổi phận sự của %s + Thiết lâp cuộc trò chuyện nhóm riêng tư + Thiết lập kênh công khai Riêng, chỉ dành cho thành viên + Làm cho các địa chỉ XMPP có thể được bất kỳ ai nhìn thấy + Làm cho kênh được kiểm duyệt Hiện bạn chưa tham gia + Đã sửa đổi tuỳ chọn cuộc trò chuyện nhóm! + Không thể sửa đổi tuỳ chọn cuộc trò chuyện nhóm Chưa từng Cho đến thông báo tiếp theo + Báo lại + Trả lời + Đánh dấu là đã đọc + Đầu vào Bấm Enter để gửi + Sử dụng phím Enter để gửi tin nhắn. Bạn luôn có thể sử dụng Ctrl+Enter để gửi tin nhắn, kể cả khi tuỳ chọn này bị tắt. Hiện nút Enter Đổi nút biểu tượng cảm xúc thành nút Enter âm thanh @@ -259,11 +420,17 @@ Ẩn ngoại tuyến %s đang gõ... %s đã ngừng gõ + %s đang gõ... + %s đã ngừng gõ Thông báo đang gõ + Để cho các liên hệ của bạn biết khi bạn đang viết tin nhắn cho họ Gửi vị trí Hiện vị trí + Không tìm thấy ứng dụng nào để hiển thị vị trí Vị trí Đã đóng cuộc hội thoại + Đã rời khỏi cuộc trò chuyện nhóm riêng tư + Đã rời khỏi kênh công khai Đừng tin các CA hệ thống Tất cả chứng nhận phải được phê duyệt thủ công Xoá các chứng nhận @@ -275,41 +442,97 @@ Đã xoá %d chứng nhận + Thay thế nút \"Gửi\" bằng hành động nhanh Thao tác nhanh Không có Dùng gần đây nhất Chọn thao tác nhanh + Tìm kiếm liên hệ + Tìm kiếm dấu trang Gửi tin nhắn cá nhân + %1$s đã rời khỏi cuộc trò chuyện nhóm Tên người dùng Tên người dùng Đây không phải là tên người dùng hợp lệ Tải xuống thất bại: Không thấy máy chủ Tải xuống thất bại: Không thấy tập tin Tải xuống thất bại: Không thể kết nối đến máy chủ + Tải xuống thất bại: Không thể ghi tệp Mạng Tor chưa sẵn sàng + Gắn kết thất bại + Máy chủ không chịu trách nhiệm cho miền này Bị hỏng + Tính khả dụng + Vắng mặt khi thiết bị bị khoá + Hiện là Vắng mặt khi thiết bị bị khoá + Bận ở chế độ im lặng + Hiện là Bận khi thiết bị ở chế độ im lặng + Coi chế độ rung như chế độ im lặng + Hiện là Bận khi thiết bị ở chế độ rung + Cài đặt kết nối mở rộng + Hiện tên máy chủ và cài đặt cổng khi thiết lập tài khoản + xmpp.example.com + Đăng nhập bằng chứng chỉ + Không thể xử lý chứng chỉ + Cài đặt lưu trữ + Cài đặt lưu trữ ở phía máy chủ + Đang lấy cài đặt lưu trữ. Vui lòng đợi... + Không thể lấy cài đặt lưu trữ + Yêu cầu CAPTCHA + Nhập văn bản trong hình ảnh ở trên + Chuỗi chứng chỉ không được tin tưởng + Địa chỉ XMPP không khớp với chứng chỉ Gia hạn chứng nhận Lỗi nhập khoá OMEMO! Khoá OMEMO đã xác minh với chứng nhận! Thiết bị không hỗ trợ chọn lựa các chứng chỉ của máy trạm! + Kết nối Kết nối đến Tor Chuyển toàn bộ kết nối thông qua mạng Tor. Cần có Orbot Tên máy chủ Cổng + Địa chỉ máy chủ hoặc .onion Đây không phải là số cổng hợp lệ Đây không phải là tên máy chủ hợp lệ %1$d trên %2$d tài khoản đã kết nối %dv tin nhắn + Tải thêm tin nhắn + Đã chia sẻ tệp với %s + Đã chia sẻ hình ảnh với %s + Đã 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 Đồ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ạ.]]>
Thông báo tất cả tin nhắn + Chỉ thông báo khi được nhắc đến Đã tắt thông báo Đã dừng thông báo + Nén hình ảnh + Gợi ý: Sử dụng \'Chọn tệp\' thay vì \'Chọn ảnh\' để gửi từng hình ảnh không nén riêng biệt mà không tính đến cài đặt này. Luôn luôn + Chỉ các hình ảnh lớn Đã bật tối ưu pin + Thiết bị của bạn đang sử dụng tối ưu hoá pin sâu cho %1$s, điều này có thể dẫn đến thông báo bị trì hoãn hay thậm chí là mất tin nhắn.\nChúng tôi khuyên bạn tắt tối ưu hoá pin. + Thiết bị của bạn đang sử dụng tối ưu hoá pin sâu cho %1$s, điều này có thể dẫn đến thông báo bị trì hoãn hay thậm chí là mất tin nhắn.\nBây giờ bạn sẽ được hỏi để tắt tối ưu hoá pin. Tắt Khu vực chọn quá lớn + (Không có tài khoản đã kích hoạt) + Trường này là bắt buộc + Sửa tin nhắn + Gửi tin nhắn đã sửa + Bạn đã xác minh mã kiểm tra của người này một cách bảo mật để xác nhận sự tin tưởng. Bằng cách chọn \"Xong\" bạn chỉ đang xác nhận rằng %s ở trong cuộc trò chuyện nhóm này. + Bạn đã tắt tài khoản này + 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... + Đồng ý và tiếp tục + Địa chỉ XMPP đầy đủ của bạn sẽ là: %s + Tạo tài khoản Trực tuyến Tắt Đã chép tin nhắn vào clipboard diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 764daf0ea..d9d17896f 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -147,6 +147,7 @@ 未找到文件 常规I/O错误。可能是存储空间不足? 您用来选择图片的程序没有给予读取权限。\n\n </small>尝试其他文件管理器选择图片</small>。 + 你用来共享此文件的应用程序没有提供足够的权限。 未知 暂时不可用 在线 @@ -161,6 +162,7 @@ 服务器不支持注册 无效的注册令牌 TLS协商失败 + 域名不可验证 违反政策 服务器不兼容 流错误 @@ -947,4 +949,5 @@ 服务器不支持生成邀请 没有活跃帐户支持此功能 已启动备份。一旦完成,你会收到通知。 + 无法启用视频
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 6b5774639..c836a224c 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -163,6 +163,7 @@ Registration not supported by server Invalid registration token TLS negotiation failed + Domain not verifiable Policy violation Incompatible server Stream error diff --git a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java index 05b076424..f977758f2 100644 --- a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java +++ b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java @@ -48,6 +48,7 @@ import eu.siacs.conversations.crypto.sasl.Plain; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Entry; +import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.utils.AccountUtils; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.PhoneNumberUtilWrapper; @@ -260,7 +261,7 @@ public class QuickConversationsService extends AbstractQuickConversationsService } private void setHeader(HttpURLConnection connection) { - connection.setRequestProperty("User-Agent", service.getIqGenerator().getUserAgent()); + connection.setRequestProperty("User-Agent", HttpConnectionManager.getUserAgent()); connection.setRequestProperty("Installation-Id", getInstallationId()); connection.setRequestProperty("Accept-Language", Locale.getDefault().getLanguage()); } diff --git a/src/quicksy/res/values-vi/strings.xml b/src/quicksy/res/values-vi/strings.xml new file mode 100644 index 000000000..99f454d24 --- /dev/null +++ b/src/quicksy/res/values-vi/strings.xml @@ -0,0 +1,12 @@ + + + Khoảng thời gian Quicksy giữ yên lặng sau khi xem hoạt động trên một thiết bị khác + Bằng việc gửi báo cáo hoạt động, bạn đang hỗ trợ sự phát triển liên tục của Quicksy + Để cho tất cả liên hệ của bạn biết khi bạn sử dụng Quicksy + Để 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 Quicksy vào danh sách các ứng dụng được bảo vệ. + Ảnh hồ sơ Quicksy + Quicksy không có sẵn ở quốc gia của bạn. + Không thể xác minh danh tính máy chủ. + Lỗi bảo mật không xác định. + Hết thời gian chờ khi kết nối đến máy chủ. +