diff options
Diffstat (limited to 'core')
14 files changed, 476 insertions, 149 deletions
diff --git a/core/build.gradle b/core/build.gradle index b3c614059..ee5a33ce5 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -101,6 +101,9 @@ dependencies { System.out.println("core: free build hack, skipping some dependencies") } + // bundle conscrypt with free builds + freeImplementation "org.conscrypt:conscrypt-android:$conscryptVersion" + testImplementation "org.awaitility:awaitility:$awaitilityVersion" testImplementation 'junit:junit:4.13' testImplementation 'org.mockito:mockito-core:1.10.19' diff --git a/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java b/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java index ecb9d68df..04d74f2a2 100644 --- a/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java +++ b/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java @@ -1,6 +1,8 @@ package de.danoeh.antennapod.core; import android.content.Context; +import java.security.Security; +import org.conscrypt.Conscrypt; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; @@ -53,6 +55,7 @@ public class ClientConfig { } private static void installSslProvider(Context context) { - // ProviderInstaller is a closed-source Google library + // Insert bundled conscrypt as highest security provider (overrides OS version). + Security.insertProviderAt(Conscrypt.newProvider(), 1); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java index 889018c45..a01b3cb52 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java @@ -1,39 +1,16 @@ package de.danoeh.antennapod.core.service.download; import android.os.Build; -import androidx.annotation.NonNull; import android.text.TextUtils; import android.util.Log; - -import de.danoeh.antennapod.core.service.BasicAuthorizationInterceptor; - -import java.io.File; -import java.io.IOException; -import java.net.CookieManager; -import java.net.CookiePolicy; -import java.net.HttpURLConnection; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.Socket; -import java.net.SocketAddress; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - +import androidx.annotation.NonNull; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.BasicAuthorizationInterceptor; import de.danoeh.antennapod.core.service.UserAgentInterceptor; +import de.danoeh.antennapod.core.ssl.BackportTrustManager; +import de.danoeh.antennapod.core.ssl.NoV1SslSocketFactory; import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.util.Flavors; import okhttp3.Cache; import okhttp3.CipherSuite; import okhttp3.ConnectionSpec; @@ -45,6 +22,19 @@ import okhttp3.Request; import okhttp3.Response; import okhttp3.internal.http.StatusLine; +import javax.net.ssl.X509TrustManager; +import java.io.File; +import java.net.CookieManager; +import java.net.CookiePolicy; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + /** * Provides access to a HttpClient singleton. */ @@ -149,13 +139,20 @@ public class AntennapodHttpClient { }); } } - if (Build.VERSION.SDK_INT < 21) { - builder.sslSocketFactory(new CustomSslSocketFactory(), trustManager()); + + if (Flavors.FLAVOR == Flavors.FREE) { + // The Free flavor bundles a modern conscrypt (security provider), so CustomSslSocketFactory + // is only used to make sure that modern protocols (TLSv1.3 and TLSv1.2) are enabled and + // that old, deprecated, protocols (like SSLv3, TLSv1.0 and TLSv1.1) are disabled. + X509TrustManager trustManager = BackportTrustManager.create(); + builder.sslSocketFactory(new NoV1SslSocketFactory(trustManager), trustManager); + } else if (Build.VERSION.SDK_INT < 21) { + X509TrustManager trustManager = BackportTrustManager.create(); + builder.sslSocketFactory(new NoV1SslSocketFactory(trustManager), trustManager); // workaround for Android 4.x for certain web sites. // see: https://github.com/square/okhttp/issues/4053#issuecomment-402579554 - List<CipherSuite> cipherSuites = new ArrayList<>( - ConnectionSpec.MODERN_TLS.cipherSuites()); + List<CipherSuite> cipherSuites = new ArrayList<>(ConnectionSpec.MODERN_TLS.cipherSuites()); cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA); cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA); @@ -168,101 +165,7 @@ public class AntennapodHttpClient { return builder; } - /** - * Closes expired connections. This method should be called by the using class once has finished its work with - * the HTTP client. - */ - public static synchronized void cleanup() { - if (httpClient != null) { - // does nothing at the moment - } - } - - private static X509TrustManager trustManager() { - try { - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init((KeyStore) null); - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new IllegalStateException("Unexpected default trust managers:" - + Arrays.toString(trustManagers)); - } - return (X509TrustManager) trustManagers[0]; - } catch (Exception e) { - Log.e(TAG, Log.getStackTraceString(e)); - return null; - } - } - public static void setCacheDirectory(File cacheDirectory) { AntennapodHttpClient.cacheDirectory = cacheDirectory; } - - private static class CustomSslSocketFactory extends SSLSocketFactory { - - private SSLSocketFactory factory; - - public CustomSslSocketFactory() { - try { - SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); - sslContext.init(null, null, null); - factory= sslContext.getSocketFactory(); - } catch(GeneralSecurityException e) { - e.printStackTrace(); - } - } - - @Override - public String[] getDefaultCipherSuites() { - return factory.getDefaultCipherSuites(); - } - - @Override - public String[] getSupportedCipherSuites() { - return factory.getSupportedCipherSuites(); - } - - public Socket createSocket() throws IOException { - SSLSocket result = (SSLSocket) factory.createSocket(); - configureSocket(result); - return result; - } - - public Socket createSocket(String var1, int var2) throws IOException { - SSLSocket result = (SSLSocket) factory.createSocket(var1, var2); - configureSocket(result); - return result; - } - - public Socket createSocket(Socket var1, String var2, int var3, boolean var4) throws IOException { - SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4); - configureSocket(result); - return result; - } - - public Socket createSocket(InetAddress var1, int var2) throws IOException { - SSLSocket result = (SSLSocket) factory.createSocket(var1, var2); - configureSocket(result); - return result; - } - - public Socket createSocket(String var1, int var2, InetAddress var3, int var4) throws IOException { - SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4); - configureSocket(result); - return result; - } - - public Socket createSocket(InetAddress var1, int var2, InetAddress var3, int var4) throws IOException { - SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4); - configureSocket(result); - return result; - } - - private void configureSocket(SSLSocket s) { - s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1.1", "TLSv1" } ); - } - - } - } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java index 61608992b..65b7ed7d1 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java @@ -71,6 +71,7 @@ public class HttpDownloader extends Downloader { // set header explicitly so that okhttp doesn't do transparent gzip Log.d(TAG, "addHeader(\"Accept-Encoding\", \"identity\")"); httpReq.addHeader("Accept-Encoding", "identity"); + httpReq.cacheControl(new CacheControl.Builder().noCache().build()); // noStore breaks CDNs } if (!TextUtils.isEmpty(request.getLastModified())) { @@ -259,7 +260,6 @@ public class HttpDownloader extends Downloader { onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource()); } finally { IOUtils.closeQuietly(out); - AntennapodHttpClient.cleanup(); IOUtils.closeQuietly(responseBody); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportCaCerts.java b/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportCaCerts.java new file mode 100644 index 000000000..720d6a9d9 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportCaCerts.java @@ -0,0 +1,73 @@ +package de.danoeh.antennapod.core.ssl; + +public class BackportCaCerts { + public static final String SECTIGO_USER_TRUST = "-----BEGIN CERTIFICATE-----\n" + + "MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB\n" + + "iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl\n" + + "cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV\n" + + "BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw\n" + + "MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV\n" + + "BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU\n" + + "aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy\n" + + "dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\n" + + "AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B\n" + + "3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY\n" + + "tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/\n" + + "Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2\n" + + "VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT\n" + + "79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6\n" + + "c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT\n" + + "Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l\n" + + "c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee\n" + + "UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE\n" + + "Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd\n" + + "BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G\n" + + "A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF\n" + + "Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO\n" + + "VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3\n" + + "ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs\n" + + "8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR\n" + + "iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze\n" + + "Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ\n" + + "XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/\n" + + "qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB\n" + + "VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB\n" + + "L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG\n" + + "jjxDah2nGN59PRbxYvnKkKj9\n" + + "-----END CERTIFICATE-----\n"; + + public static final String COMODO = "-----BEGIN CERTIFICATE-----\n" + + "MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB\n" + + "hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\n" + + "A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV\n" + + "BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5\n" + + "MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT\n" + + "EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR\n" + + "Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh\n" + + "dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR\n" + + "6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X\n" + + "pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC\n" + + "9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV\n" + + "/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf\n" + + "Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z\n" + + "+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w\n" + + "qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah\n" + + "SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC\n" + + "u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf\n" + + "Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq\n" + + "crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E\n" + + "FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB\n" + + "/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl\n" + + "wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM\n" + + "4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV\n" + + "2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna\n" + + "FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ\n" + + "CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK\n" + + "boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke\n" + + "jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL\n" + + "S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb\n" + + "QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl\n" + + "0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB\n" + + "NVOFBkpdn627G190\n" + + "-----END CERTIFICATE-----"; +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportTrustManager.java b/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportTrustManager.java new file mode 100644 index 000000000..b8fe950b2 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportTrustManager.java @@ -0,0 +1,58 @@ +package de.danoeh.antennapod.core.ssl; + +import android.util.Log; + +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.io.ByteArrayInputStream; +import java.nio.charset.Charset; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateFactory; +import java.util.ArrayList; +import java.util.List; + +/** + * SSL trust manager that allows old Android systems to use modern certificates. + */ +public class BackportTrustManager { + private static final String TAG = "BackportTrustManager"; + + private static X509TrustManager getSystemTrustManager(KeyStore keystore) { + TrustManagerFactory factory; + try { + factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + factory.init(keystore); + for (TrustManager manager : factory.getTrustManagers()) { + if (manager instanceof X509TrustManager) { + return (X509TrustManager) manager; + } + } + } catch (NoSuchAlgorithmException | KeyStoreException e) { + e.printStackTrace(); + } + throw new IllegalStateException("Unexpected default trust managers"); + } + + public static X509TrustManager create() { + try { + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + keystore.load(null); // Clear + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + keystore.setCertificateEntry("BACKPORT_COMODO_ROOT_CA", cf.generateCertificate( + new ByteArrayInputStream(BackportCaCerts.COMODO.getBytes(Charset.forName("UTF-8"))))); + keystore.setCertificateEntry("SECTIGO_USER_TRUST_CA", cf.generateCertificate( + new ByteArrayInputStream(BackportCaCerts.SECTIGO_USER_TRUST.getBytes(Charset.forName("UTF-8"))))); + + List<X509TrustManager> managers = new ArrayList<>(); + managers.add(getSystemTrustManager(keystore)); + managers.add(getSystemTrustManager(null)); + return new CompositeX509TrustManager(managers); + } catch (Exception e) { + Log.e(TAG, Log.getStackTraceString(e)); + return null; + } + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/ssl/CompositeX509TrustManager.java b/core/src/main/java/de/danoeh/antennapod/core/ssl/CompositeX509TrustManager.java new file mode 100644 index 000000000..7af96a492 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/ssl/CompositeX509TrustManager.java @@ -0,0 +1,60 @@ +package de.danoeh.antennapod.core.ssl; + +import javax.net.ssl.X509TrustManager; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Represents an ordered list of {@link X509TrustManager}s with additive trust. If any one of the composed managers + * trusts a certificate chain, then it is trusted by the composite manager. + * Based on https://stackoverflow.com/a/16229909 + */ +public class CompositeX509TrustManager implements X509TrustManager { + private final List<X509TrustManager> trustManagers; + + public CompositeX509TrustManager(List<X509TrustManager> trustManagers) { + this.trustManagers = trustManagers; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + CertificateException reason = null; + for (X509TrustManager trustManager : trustManagers) { + try { + trustManager.checkClientTrusted(chain, authType); + return; // someone trusts them. success! + } catch (CertificateException e) { + // maybe someone else will trust them + reason = e; + } + } + throw reason; + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + CertificateException reason = null; + for (X509TrustManager trustManager : trustManagers) { + try { + trustManager.checkServerTrusted(chain, authType); + return; // someone trusts them. success! + } catch (CertificateException e) { + // maybe someone else will trust them + reason = e; + } + } + throw reason; + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + List<X509Certificate> certificates = new ArrayList<>(); + for (X509TrustManager trustManager : trustManagers) { + certificates.addAll(Arrays.asList(trustManager.getAcceptedIssuers())); + } + return certificates.toArray(new X509Certificate[0]); + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/ssl/NoV1SslSocketFactory.java b/core/src/main/java/de/danoeh/antennapod/core/ssl/NoV1SslSocketFactory.java new file mode 100644 index 000000000..96a42f22d --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/ssl/NoV1SslSocketFactory.java @@ -0,0 +1,100 @@ +package de.danoeh.antennapod.core.ssl; + +import de.danoeh.antennapod.core.util.Flavors; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.security.GeneralSecurityException; + +/** + * SSLSocketFactory that does not use TLS 1.0 + * This fixes issues with old Android versions that abort if the server does not know TLS 1.0 + */ +public class NoV1SslSocketFactory extends SSLSocketFactory { + private SSLSocketFactory factory; + + public NoV1SslSocketFactory(TrustManager trustManager) { + try { + SSLContext sslContext; + + if (Flavors.FLAVOR == Flavors.FREE) { + // Free flavor (bundles modern conscrypt): support for TLSv1.3 is guaranteed. + sslContext = SSLContext.getInstance("TLSv1.3"); + } else { + // Play flavor (security provider can vary): only TLSv1.2 is guaranteed. + sslContext = SSLContext.getInstance("TLSv1.2"); + } + + sslContext.init(null, new TrustManager[] {trustManager}, null); + factory = sslContext.getSocketFactory(); + } catch (GeneralSecurityException e) { + e.printStackTrace(); + } + } + + @Override + public String[] getDefaultCipherSuites() { + return factory.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return factory.getSupportedCipherSuites(); + } + + public Socket createSocket() throws IOException { + SSLSocket result = (SSLSocket) factory.createSocket(); + configureSocket(result); + return result; + } + + public Socket createSocket(String var1, int var2) throws IOException { + SSLSocket result = (SSLSocket) factory.createSocket(var1, var2); + configureSocket(result); + return result; + } + + public Socket createSocket(Socket var1, String var2, int var3, boolean var4) throws IOException { + SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4); + configureSocket(result); + return result; + } + + public Socket createSocket(InetAddress var1, int var2) throws IOException { + SSLSocket result = (SSLSocket) factory.createSocket(var1, var2); + configureSocket(result); + return result; + } + + public Socket createSocket(String var1, int var2, InetAddress var3, int var4) throws IOException { + SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4); + configureSocket(result); + return result; + } + + public Socket createSocket(InetAddress var1, int var2, InetAddress var3, int var4) throws IOException { + SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4); + configureSocket(result); + return result; + } + + private void configureSocket(SSLSocket s) { + if (Flavors.FLAVOR == Flavors.FREE) { + // Free flavor (bundles modern conscrypt): TLSv1.3 and modern cipher suites are + // guaranteed. Protocols older than TLSv1.2 are now deprecated and can be disabled. + s.setEnabledProtocols(new String[] { "TLSv1.3", "TLSv1.2" }); + } else { + // Play flavor (security provider can vary): only TLSv1.2 is guaranteed, supported + // cipher suites may vary. Old protocols might be necessary to keep things working. + + // TLS 1.0 is enabled by default on some old systems, which causes connection errors. + // This disables that. + s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1.1", "TLSv1" }); + } + } +}
\ No newline at end of file diff --git a/core/src/main/res/values-cs/strings.xml b/core/src/main/res/values-cs/strings.xml index 2aa11889d..ec6f20941 100644 --- a/core/src/main/res/values-cs/strings.xml +++ b/core/src/main/res/values-cs/strings.xml @@ -40,7 +40,7 @@ <string name="drawer_open">Otevřít menu</string> <string name="drawer_close">Zavřít menu</string> <string name="drawer_preferences">Nastavení panelu</string> - <string name="drawer_feed_order_unplayed_episodes">Řadit dle počtu</string> + <string name="drawer_feed_order_unplayed_episodes">Řadit dle čítače</string> <string name="drawer_feed_order_alphabetical">Řadit abecedně</string> <string name="drawer_feed_order_last_update">Řadit dle data zveřejnění</string> <string name="drawer_feed_order_most_played">Řadit podle počtu poslechnutých epizod</string> @@ -110,12 +110,21 @@ <item quantity="many">%d dnů po dokončení</item> <item quantity="other">%d dnů po dokončení</item> </plurals> + <plurals name="num_selected_label"> + <item quantity="one">%d vybrána</item> + <item quantity="few">%d vybrány</item> + <item quantity="many">%d vybráno</item> + <item quantity="other">%d vybráno</item> + </plurals> <string name="loading_more">Načítají se další…</string> <!--Actions on feeds--> <string name="mark_all_read_label">Označit vše jako poslechnuté</string> <string name="mark_all_read_msg">Všechny epizody označeny jako poslechnuté</string> <string name="mark_all_read_confirmation_msg">Potvrďte prosím, že chcete označit všechny vybrané epizody jako poslechnuté.</string> <string name="mark_all_read_feed_confirmation_msg">Potvrďte prosím, že chcete označit všechny epizody tohoto podcastu jako poslechnuté.</string> + <string name="remove_all_new_flags_label">Odstranit příznak „nová“ ze všech epizod</string> + <string name="removed_all_new_flags_msg">Příznak „nová“ odstraněn ze všech epizod</string> + <string name="remove_all_new_flags_confirmation_msg">Potvrďte prosím, že chcete odstranit příznak „nová“ ze všech epizod.</string> <string name="show_info_label">Informace o zdroji</string> <string name="show_feed_settings_label">Zobrazit nastavení podcastu</string> <string name="feed_info_label">Informace o podcastu</string> @@ -132,6 +141,7 @@ <string name="share_item_url_with_position_label">Sdílet URL souboru s časovou značkou</string> <string name="feed_delete_confirmation_msg">Potvrďte prosím, že chcete smazat podcast „%1$s“ a VŠECHNY jeho epizody (včetně stažených epizod).</string> <string name="feed_remover_msg">Odstraňování podcastu</string> + <string name="load_complete_feed">Aktualizovat celý podcast</string> <string name="multi_select">Výběr více položek</string> <string name="select_all_above">Vybrat všechny výše</string> <string name="select_all_below">Vybrat všechny níže</string> @@ -168,10 +178,13 @@ <item quantity="many">%d epizod staženo.</item> <item quantity="other">%d epizod staženo.</item> </plurals> + <string name="remove_new_flag_label">Odstranit příznak „nová“</string> + <string name="removed_new_flag_label">Příznak „nová“ odstraněn</string> <string name="mark_read_label">Označit jako poslechnuté</string> <string name="marked_as_read_label">Označeno jako poslechnuté</string> <string name="mark_read_no_media_label">Označit jako poslechnuté</string> <string name="marked_as_read_no_media_label">Označeno jako poslechnuté</string> + <string name="play_this_to_seek_position">Pro přeskočení na pozice musíte epizodu přehrát</string> <plurals name="marked_read_batch_label"> <item quantity="one">%d epizoda označena jako přehraná</item> <item quantity="few">%d epizody označeny jako přehrané</item> @@ -180,9 +193,27 @@ </plurals> <string name="mark_unread_label">Označit jako neposlechnuté</string> <string name="mark_unread_label_no_media">Označit jako nepřečtené</string> + <plurals name="marked_unread_batch_label"> + <item quantity="one">%d epizoda označena jako neposlechnutá</item> + <item quantity="few">%d epizody označeny jako neposlechnuté</item> + <item quantity="many">%d epizod označeno jako neposlechnuté</item> + <item quantity="other">%d epizod označeno jako neposlechnuté</item> + </plurals> <string name="add_to_queue_label">Přidat do fronty</string> <string name="added_to_queue_label">Přidáno do fronty</string> + <plurals name="added_to_queue_batch_label"> + <item quantity="one">%d epizoda přidána do fronty</item> + <item quantity="few">%d epizody přidány do fronty</item> + <item quantity="many">%d epizod přidáno do fronty</item> + <item quantity="other">%d epizod přidáno do fronty</item> + </plurals> <string name="remove_from_queue_label">Odebrat z fronty</string> + <plurals name="removed_from_queue_batch_label"> + <item quantity="one">%d epizoda odebrána z fronty</item> + <item quantity="few">%d epizody odebrány z fronty</item> + <item quantity="many">%d epizod odebráno z fronty</item> + <item quantity="other">%d epizod odebráno z fronty</item> + </plurals> <string name="add_to_favorite_label">Přidat k oblíbeným</string> <string name="added_to_favorites">Přidáno k oblíbeným</string> <string name="remove_from_favorite_label">Odebrat z obíbených</string> @@ -233,6 +264,7 @@ <string name="download_type_feed">Kanál</string> <string name="download_type_media">Soubor</string> <string name="download_request_error_dialog_message_prefix">Nastala chyba při pokusu o stažení souboru:\u0020</string> + <string name="null_value_podcast_error">Nebyl poskytnut žádný podcast, co by mohl být zobrazen.</string> <string name="authentication_notification_title">Vyžadováno ověření</string> <string name="authentication_notification_msg">Zdroj který jste vybrali vyžaduje zadání uživatelského jména a hesla</string> <string name="confirm_mobile_download_dialog_title">Potvrdit mobilní stahování</string> @@ -285,13 +317,17 @@ <!--Variable Speed--> <string name="download_plugin_label">Stáhnout modul</string> <string name="no_playback_plugin_title">Modul není nainstalován</string> + <string name="no_playback_plugin_or_sonic_msg">Pro správnou funkci proměnlivé rychlosti přehrávání je doporučeno povolit vestavěný přehrávač Sonic.</string> <string name="set_playback_speed_label">Rychlosti přehrávání</string> <string name="enable_sonic">Povolit Sonic</string> <!--Empty list labels--> <string name="no_items_header_label">Žádné epizody ve frontě</string> + <string name="no_items_label">Přidejte epizodu stažením nebo dlouhým dotykem a volbou „Přidat do fronty“.</string> <string name="no_shownotes_label">Tato epizoda neobsahuje žádné poznámky.</string> <string name="no_run_downloads_head_label">Neběží žádná stahování</string> + <string name="no_run_downloads_label">Můžete stáhnout epizody tohoto podcastu z obrazovky s jeho detaily.</string> <string name="no_comp_downloads_head_label">Žádné stažené epizody</string> + <string name="no_comp_downloads_label">Můžete stáhnout epizody tohoto podcastu z obrazovky s jeho detaily.</string> <string name="no_log_downloads_head_label">Žádné záznamy o stahování</string> <string name="no_log_downloads_label">Až proběhnou nějaká stahování, tak se záznamy o nich objeví zde.</string> <string name="no_history_head_label">Žádná historie</string> @@ -304,6 +340,7 @@ <string name="no_fav_episodes_label">Epizody si můžete přidat mezi oblíbené dlouhým dotykem.</string> <string name="no_chapters_head_label">Žádné kapitoly</string> <string name="no_chapters_label">Tato epizoda nemá žádné kapitoly.</string> + <string name="no_subscriptions_head_label">Žádené sbírky</string> <string name="no_subscriptions_label">Pro přidání podcastu do sbírky se dotkněte ikonky plus níže.</string> <!--Preferences--> <string name="storage_pref">Úložiště</string> @@ -317,6 +354,7 @@ <string name="import_export_pref">Importovat/Exportovat</string> <string name="import_export_search_keywords">zálohovat, obnovit</string> <string name="appearance">Vzhled</string> + <string name="external_elements">Externí elementy</string> <string name="interruptions">Přerušení</string> <string name="playback_control">Ovládání přehrávání</string> <string name="preference_search_hint">Vyhledávání…</string> @@ -328,6 +366,8 @@ <string name="pref_pauseOnDisconnect_sum">Při odpojení sluchátek nebo bluetooth připojení pozastavit přehrávání.</string> <string name="pref_unpauseOnHeadsetReconnect_sum">Pokračovat v přehrávání po připojení sluchátek</string> <string name="pref_unpauseOnBluetoothReconnect_sum">Pokračovat v přehrávání po připojení bluetooth</string> + <string name="pref_hardwareForwardButtonSkips_title">Tlačítko rychle vpřed přeskakuje</string> + <string name="pref_hardwareForwardButtonSkips_sum">Stisk tlačítka rychle vpřed (FF) na připojeném zařízení Bluetooth přeskočí na další epizodu místo rychlého přetočení vpřed.</string> <string name="pref_hardwarePreviousButtonRestarts_title">Tlačítko zpět restartuje</string> <string name="pref_hardwarePreviousButtonRestarts_sum">Po stlačení hardwarového tlačítka pro posun zpět místo přetočení vpřed restartovat přehrávání aktuální epizody</string> <string name="pref_followQueue_sum">Po přehrání položky z fronty přejít automaticky na další</string> @@ -337,10 +377,14 @@ <string name="pref_smart_mark_as_played_title">Chytré označování jako poslechnuté</string> <string name="pref_skip_keeps_episodes_sum">Neodstraňovat epizody při jejich přeskočení</string> <string name="pref_skip_keeps_episodes_title">Nemazat přeskočené epizody</string> + <string name="pref_favorite_keeps_episodes_sum">Nemazat epizody, které jsou mezi oblíbenými.</string> <string name="pref_favorite_keeps_episodes_title">Nemazat oblíbené epizody</string> <string name="playback_pref">Přehrávání</string> + <string name="playback_pref_sum">Ovládání tlačítky sluchátek, přeskakování, fronta</string> <string name="network_pref">Síť</string> + <string name="network_pref_sum">Četnost aktualizací, ovládání stahování, mobilní data</string> <string name="pref_autoUpdateIntervallOrTime_title">Aktualizovat interval nebo čas v průběhu dne</string> + <string name="pref_autoUpdateIntervallOrTime_sum">Zvolte čas mezi aktualizacemi nebo čas v rámci dne, kdy proběhne automatická aktualizace podcastů</string> <string name="pref_autoUpdateIntervallOrTime_message">Můžete nastavit <i>interval</i> jako třeba \"každé 2 hodiny\", nastavit specifický <i>čas v průběhu dne</i> jako \"7:00\" nebo úplně <i>vypnout</i> automatické aktualizace.\n\n<small>Mějte na paměti: Časy aktualizací nejsou přesné. Možná zaznamenáte krátká zpoždění.</small></string> <string name="pref_autoUpdateIntervallOrTime_Disable">Vypnout</string> <string name="pref_autoUpdateIntervallOrTime_Interval">Nastavit interval</string> @@ -351,17 +395,24 @@ <string name="pref_pauseOnHeadsetDisconnect_title">Sluchátka odpojena</string> <string name="pref_unpauseOnHeadsetReconnect_title">Sluchátka připojena</string> <string name="pref_unpauseOnBluetoothReconnect_title">Bluetooth připojeno</string> + <string name="pref_stream_over_download_title">Upřednostnit streamování</string> + <string name="pref_stream_over_download_sum">Zobrazit tlačítko streamovat místo tlačítka stáhnout v seznamech.</string> <string name="pref_mobileUpdate_title">Mobilní aktualizace</string> + <string name="pref_mobileUpdate_sum">Vyberte, co by mělo být povoleno přes mobilní data</string> <string name="pref_mobileUpdate_refresh">Obnovit podcast</string> + <string name="pref_mobileUpdate_images">Obrázky podcastů/epizod</string> <string name="pref_mobileUpdate_auto_download">Automatické stahování</string> + <string name="pref_mobileUpdate_episode_download">Stahování epizod</string> <string name="pref_mobileUpdate_streaming">Streamování</string> <string name="user_interface_label">Uživatelské rozhraní</string> + <string name="user_interface_sum">Vzhled, poradí ve sbírce, uzamčená obrazovka</string> <string name="pref_set_theme_title">Vybrat motiv</string> <string name="pref_nav_drawer_items_title">Změnit navigační panel</string> <string name="pref_nav_drawer_items_sum">Upravit zobrazení položek v navigačním panelu.</string> <string name="pref_nav_drawer_feed_order_title">Nastavit pořadí sbírek</string> <string name="pref_nav_drawer_feed_order_sum">Upravit pořadí vašich sbírek</string> - <string name="pref_nav_drawer_feed_counter_title">Nastavit počítadlo sbírek</string> + <string name="pref_nav_drawer_feed_counter_title">Nastavit čítač sbírek</string> + <string name="pref_nav_drawer_feed_counter_sum">Změnit informaci zobrazenou čítačem sbírek. Též ovlivňuje řazení, je-li nastaveno na „podle čítače“.</string> <string name="pref_set_theme_sum">Změnit vzhled AntennaPod.</string> <string name="pref_automatic_download_title">Automatické stahování</string> <string name="pref_automatic_download_sum">Nastavení automatického stahování epizod.</string> @@ -371,6 +422,9 @@ <string name="pref_automatic_download_on_battery_sum">Povolit automatické stahování i pokud není baterie nabíjena</string> <string name="pref_parallel_downloads_title">Paralelní stahování</string> <string name="pref_episode_cache_title">Historie epizod</string> + <string name="pref_episode_cache_summary">Celkový počet epizod stažených na zařízení. Automatické stahování se zastaví při dosažení této hodnoty.</string> + <string name="pref_episode_cover_title">Použít obrázek epizody</string> + <string name="pref_episode_cover_summary">Použít obrázek přímo z epizody, pokud je k dispozici. Není-li tato možnost zaškrtnuta, tak se vžy použije obrázek podcastu.</string> <string name="pref_theme_title_use_system">Použít systémové téma</string> <string name="pref_theme_title_light">Světlý</string> <string name="pref_theme_title_dark">Tmavý</string> @@ -387,9 +441,10 @@ <string name="pref_gpodnet_setlogin_information_sum">Změní přihlašovací údaje k vašemu gpodder.net účtu.</string> <string name="pref_gpodnet_sync_changes_title">Synchronizovat ihned</string> <string name="pref_gpodnet_sync_changes_sum">Synchronizovat odběr a změny stavu epizody s gpodder.net.</string> + <string name="pref_gpodnet_full_sync_title">Synchronizovat vše ihned</string> <string name="pref_gpodnet_full_sync_sum">Synchronizovat všechny odběry a stav epizod s gpodder.net.</string> <string name="pref_gpodnet_login_status"><![CDATA[Přihlášen jako <i>%1$s</i> z přístroje <i>%2$s</i>]]></string> - <string name="pref_gpodnet_notifications_title">Zobrazovat upozornění na chyby synchronizace</string> + <string name="pref_gpodnet_notifications_title">Zobrazovat oznámení o chybách synchronizace</string> <string name="pref_gpodnet_notifications_sum">Toto nastavení se netýká chyb přihlášení.</string> <string name="pref_playback_speed_title">Rychlosti přehrávání</string> <string name="pref_playback_speed_sum">Přizpůsobení rychlosti je dostupné pro přehrávání zvuku různými rychlostmi</string> @@ -398,16 +453,20 @@ <string name="pref_feed_skip_sum">Přeskočit úvod a závěr.</string> <string name="pref_feed_skip_ending">Přeskočit posledních</string> <string name="pref_feed_skip_intro">Přeskočit prvních</string> + <string name="pref_feed_skip_ending_toast">Přeskočeno posledních %d sekund</string> + <string name="pref_feed_skip_intro_toast">Přeskočeno prvních %d sekund</string> <string name="pref_playback_time_respects_speed_title">Upravit informace o médiu vzhledem k rychlosti přehrávání.</string> <string name="pref_playback_time_respects_speed_sum">Zobrazené délka a pozice jsou upravené vzhledem k rychlosti přehrávání</string> <string name="pref_fast_forward">Délka času posunu vpřed</string> + <string name="pref_fast_forward_sum">Upravit o kolik sekund se přeskočí dopředu při stisku tlačítka rychle vpřed (FF).</string> <string name="pref_rewind">Délka času posunu zpět</string> + <string name="pref_rewind_sum">Upravit o kolik sekund se přeskočí zpět při stisku tlačítka přetočit zpět (RW).</string> <string name="pref_gpodnet_sethostname_title">Nastavit hostname</string> <string name="pref_gpodnet_sethostname_use_default_host">Použít přednastaveného hosta</string> - <string name="pref_expandNotify_title">Vysoká priorita pro upozornění</string> + <string name="pref_expandNotify_title">Vysoká priorita pro oznámení</string> <string name="pref_expandNotify_sum">Toto obvykle přidá tlačítka ovládání přehrávání do zpráv upozornění</string> <string name="pref_persistNotify_title">Pevné ovládání přehrávání</string> - <string name="pref_persistNotify_sum">Zachovat upozornění a ovládání na obrazovce uzamčení i při pozastaveném přehrávání.</string> + <string name="pref_persistNotify_sum">Zachovat oznámení a ovládání na obrazovce uzamčení i při pozastaveném přehrávání.</string> <string name="pref_compact_notification_buttons_title">Nastavení tlačítek uzamčené obrazovky</string> <string name="pref_compact_notification_buttons_sum">Změnit tlačítka ovládání na obrazovce uzamčení. Tlačítka přehrát/pozastavit jsou vždy zobrazena.</string> <string name="pref_compact_notification_buttons_dialog_title">Vybrat maximálně %1$d položek</string> @@ -416,7 +475,9 @@ <string name="pref_lockscreen_background_sum">Nastavit pozadí uzamčené obrazovky na obrázek aktuální epizody. Jako vedlejší efekt zobrazí toto nastavení obrázek i v aplikacích třetích stran. </string> <string name="pref_showDownloadReport_title">Zobrazit report stahování</string> <string name="pref_showDownloadReport_sum">Pokud selže stahování, vygenerovat report zobrazující detaily o chybě.</string> - <string name="pref_expand_notify_unsupport_toast">Verze Androidu nižší než 4.1 nepodporují rozšířená upozornění.</string> + <string name="pref_showAutoDownloadReport_title">Zobrazovat hlášení automatického stahování</string> + <string name="pref_showAutoDownloadReport_sum">Zobrazovat oznámení o automaticky stažených epizodách.</string> + <string name="pref_expand_notify_unsupport_toast">Verze Androidu nižší než 4.1 nepodporují rozšířená oznámení.</string> <string name="pref_enqueue_location_title">Pozice přidávání do fronty</string> <string name="pref_enqueue_location_sum">Přidávat epizody na: %1$s</string> <string name="enqueue_location_back">konec</string> @@ -425,7 +486,9 @@ <string name="pref_smart_mark_as_played_disabled">Vypnuto</string> <string name="pref_image_cache_size_title">Velikost odkládací paměti obrázků</string> <string name="pref_image_cache_size_sum">Velikost diskové paměti pro obrázky.</string> + <string name="visit_user_forum">Uživatelské fórum</string> <string name="bug_report_title">Nahlásit chybu</string> + <string name="open_bug_tracker">Otevřít systém pro sledování a hlášení chyb (bug tracker)</string> <string name="export_logs">Exportovat záznamy</string> <string name="copy_to_clipboard">Zkopírovat do schránky</string> <string name="copied_to_clipboard">Zkopírováno do schránky</string> @@ -490,6 +553,7 @@ <string name="database">Databáze</string> <string name="opml">OPML</string> <string name="html">HTML</string> + <string name="html_export_summary">Ukažte své sbírky přátelům</string> <string name="opml_export_summary">Přenést sbírky do jiné podcastové aplikace</string> <string name="opml_import_summary">Importovat vaše sbírky z jiné podcastové aplikace</string> <string name="database_export_summary">Přenést sbírky, poslechnuté epizody a frontu do aplikace AntennaPod na jiném zařízení</string> @@ -582,6 +646,7 @@ <string name="choose_data_directory">Vybrat umístění dat</string> <string name="choose_data_directory_message">Vyberte prosím váš výchozí datový adresář. AntennaPod vytvoří všechny potřebné podadresáře.</string> <string name="choose_data_directory_permission_rationale">Ke změně datového adresáře je vyžadován přístup k externímu úložišti</string> + <string name="choose_data_directory_available_space">%1$s z %2$s zdarma</string> <string name="create_folder_msg">Vytvořit adresář \"%1$s\"?</string> <string name="create_folder_success">Nový adresář vytvořen</string> <string name="create_folder_error_no_write_access">Nelze zapisovat do adresáře</string> @@ -600,6 +665,9 @@ <string name="pref_restart_required">Pro aktivování změn nastavení bylo třeba restartovat aplikaci AntennaPod.</string> <!--Online feed view--> <string name="subscribe_label">Odebírat</string> + <string name="subscribing_label">Přidává se do sbírky…</string> + <string name="preview_episode">Spustit ukázku</string> + <string name="stop_preview">Zastavit ukázku</string> <!--Content descriptions for image buttons--> <string name="rewind_label">Posunout zpět</string> <string name="fast_forward_label">Posunout vpřed</string> @@ -613,6 +681,9 @@ <string name="is_favorite_label">Epizoda je označená jako oblíbená</string> <string name="drag_handle_content_description">Tahem změnit pozici této položky</string> <string name="load_next_page_label">Načíst další stranu</string> + <string name="switch_pages">Přehodit stránku</string> + <string name="position">Pozice: %1$s</string> + <string name="apply_action">Vykonat</string> <!--Feed information screen--> <string name="authentication_label">Ověření</string> <string name="authentication_descr">Změnit uživatelské jméno a heslo pro tento podcast a jeho epizody.</string> @@ -638,6 +709,7 @@ <string name="browse_gpoddernet_label">Prohledávat gpodder.net</string> <string name="discover">Objevit</string> <string name="discover_more">více »</string> + <string name="search_powered_by">Vyhledávání poskytuje %1$s</string> <string name="filter">Filtr</string> <!--Episodes apply actions--> <string name="all_label">Vše</string> @@ -716,11 +788,22 @@ <string name="cast_failed_receiver_player_error">Přijímač zaznamenal závažnou chybu</string> <string name="cast_failed_media_error_skipping">Chyba přehrávání médií. Přeskakuji...</string> <!--Notification channels--> + <string name="notification_channel_user_action">Je vyžadována činnost z vaší strany</string> + <string name="notification_channel_user_action_description">Zobrazuje se, pokud je požadována činnost z vaší strany. Například je-li potřeba zadat heslo.</string> <string name="notification_channel_downloading">Stahuji</string> + <string name="notification_channel_downloading_description">Zobrazuje se v průběhu stahování.</string> + <string name="notification_channel_playing">Přehrává se</string> + <string name="notification_channel_playing_description">Umožňuje ovládat přehrávání. Toto je to hlavní oznámení, které uvidité při přehrávání podcastu.</string> + <string name="notification_channel_error">Chyby</string> <string name="notification_channel_error_description">Zobrazuje se, když se něco nepovedlo. Například pokud selhalo stahování anebo synchronizace gpodder.</string> + <string name="notification_channel_auto_download">Automatické stahování</string> + <string name="notification_channel_episode_auto_download">Zobrazuje se po automatickém stažení epizod.</string> <!--Widget settings--> + <string name="widget_settings">Nastavení widgetu</string> + <string name="widget_create_button">Vytvořit widget</string> <string name="widget_opacity">Průhlednost</string> <!--On-Demand configuration--> + <string name="on_demand_config_setting_changed">Nastavení úspěšně aktualizováno</string> <string name="on_demand_config_stream_text"> Vypadá to, že častěji streamujete než stahujete. Chcete zobrazovat tlačítko streamovat v seznamu epizod? </string> <string name="on_demand_config_download_text">Vypadá to, že častěji stahujete než streamujete. Chcete zobrazovat tlačítko stáhnout v seznamu epizod?</string> </resources> diff --git a/core/src/main/res/values-eu/strings.xml b/core/src/main/res/values-eu/strings.xml index 6c0973739..e4b4fe4b6 100644 --- a/core/src/main/res/values-eu/strings.xml +++ b/core/src/main/res/values-eu/strings.xml @@ -509,8 +509,11 @@ <!--About screen--> <string name="about_pref">Honi buruz</string> <string name="antennapod_version">AntennaPod bertsioa</string> + <string name="contributors">Laguntzaileak</string> + <string name="contributors_summary">Denek lagun dezakete gure foroan kodea, itzulpenak edo erabiltzaileei laguntza emanez.</string> <string name="developers">Garatzaileak</string> <string name="translators">Itzultzaileak</string> + <string name="special_thanks">Esker bereziak</string> <string name="privacy_policy">Pribatutasun politika</string> <string name="licenses">Baimenak</string> <string name="licenses_summary">AntennaPod-ek beste software ezin hobeak erabiltzen ditu</string> @@ -554,6 +557,8 @@ <string name="import_select_file">Aukeratu inportatzeko fitxategia</string> <string name="import_ok">Inportazio arrakastatsua.\n\nSakatu OK, AntennaPod berrabiarazteko</string> <string name="import_no_downgrade">Datu-basea AntennaPod-en bertsio berriago batekin esportatu zen. Uneko aplikazioak ez daki nola inportatu.</string> + <string name="favorites_export_label">Gogokoak esportatu</string> + <string name="favorites_export_summary">Gorde diren gogokoak esportatu artxibatzeko</string> <!--Sleep timer--> <string name="set_sleeptimer_label">Ezarri tenporizadore bat</string> <string name="disable_sleeptimer_label">Desgaitu tenporizadorea</string> diff --git a/core/src/main/res/values-fa/strings.xml b/core/src/main/res/values-fa/strings.xml index a418d834c..0ac3b684d 100644 --- a/core/src/main/res/values-fa/strings.xml +++ b/core/src/main/res/values-fa/strings.xml @@ -85,6 +85,7 @@ <string name="auto_download_apply_to_items_message">تنظیمات جدید <i>بارگیری خودکار</i> به طور خودکار بر قسمتهای جدید اعمال خواهد شد.\nآیا میخواهید بر قسمتهایی قبلاً منتشر شده هم اعمال شود؟</string> <string name="auto_delete_label">حذف خودکار قسمت</string> <string name="feed_volume_reduction">کم کردن صدا</string> + <string name="feed_volume_reduction_summary">برای قسمتهای این خوراک، صدا کم شود: %1$s</string> <string name="feed_volume_reduction_off">خاموش</string> <string name="feed_volume_reduction_light">سبک</string> <string name="feed_volume_reduction_heavy">سنگین</string> @@ -204,6 +205,7 @@ <string name="deactivate_auto_download">غیر فعال کردن بارگیری خودکار</string> <string name="reset_position">تنظیم مجدد موقعیت پخش</string> <string name="removed_item">مورد حذف شده است</string> + <string name="no_items_selected">چیزی انتخاب نشده است</string> <!--Download messages and labels--> <string name="download_successful">موفقیتآمیز</string> <string name="download_pending">بارگیری معوق</string> @@ -324,6 +326,8 @@ <string name="storage_sum">حذف خودکار قسمت، درونریزی، برونریزی</string> <string name="project_pref">پروژه</string> <string name="queue_label">صف</string> + <string name="synchronization_pref">همگامسازی</string> + <string name="synchronization_sum">با کمک gpodder.net با دستگاههای دیگر همگام کنید</string> <string name="automation">اتوماسیون</string> <string name="download_pref_details">جزئیات</string> <string name="import_export_pref">وارد/صادر کرد</string> diff --git a/core/src/main/res/values-pl/strings.xml b/core/src/main/res/values-pl/strings.xml index 1043b58d8..f5fa5e310 100644 --- a/core/src/main/res/values-pl/strings.xml +++ b/core/src/main/res/values-pl/strings.xml @@ -323,7 +323,7 @@ <!--Empty list labels--> <string name="no_items_header_label">Brak odcinków w kolejce</string> <string name="no_items_label">Dodaj odcinek, pobierając go, lub przytrzymaj dłużej na odcinku i wybierz \"Dodaj do kolejki\".</string> - <string name="no_shownotes_label">Ten epizod nie ma notatek.</string> + <string name="no_shownotes_label">Ten odcinek nie ma notatek.</string> <string name="no_run_downloads_head_label">Brak aktywnych pobierań</string> <string name="no_run_downloads_label">Możesz pobrać odcinki z ekranu informacji o podcaście</string> <string name="no_comp_downloads_head_label">Brak pobranych odcinków</string> @@ -528,8 +528,10 @@ <!--About screen--> <string name="about_pref">O...</string> <string name="antennapod_version">Wersja AntennaPod</string> + <string name="contributors">Kontrybutorzy</string> <string name="developers">Twórcy</string> <string name="translators">Tłumacze</string> + <string name="special_thanks">Specjalne podziękowania</string> <string name="privacy_policy">Polityka prywatności</string> <string name="licenses">Licencje</string> <string name="licenses_summary">AntennaPod używa różnego świetnego oprogramowania/bibliotek</string> @@ -573,6 +575,8 @@ <string name="import_select_file">Wybierz plik do Importowania</string> <string name="import_ok">Import zakończony powodzeniem.\n\nNaciśnij OK aby zrestartować AntennaPod</string> <string name="import_no_downgrade">Baza danych została eksportowana z nowszej wersji AntennaPod. Twoja wersja nie potrafi obsłużyć tego pliku. </string> + <string name="favorites_export_label">Eksport ulubionych</string> + <string name="favorites_export_summary">Ulubione wyeksportowano do pliku</string> <!--Sleep timer--> <string name="set_sleeptimer_label">Ustaw czas do wyłączenia</string> <string name="disable_sleeptimer_label">Wyłącz wyłącznik czasowy</string> diff --git a/core/src/main/res/values-pt-rBR/strings.xml b/core/src/main/res/values-pt-rBR/strings.xml index 1d36dc272..59e3af044 100644 --- a/core/src/main/res/values-pt-rBR/strings.xml +++ b/core/src/main/res/values-pt-rBR/strings.xml @@ -384,6 +384,7 @@ <string name="pref_mobileUpdate_episode_download">Download de episódios</string> <string name="pref_mobileUpdate_streaming">Streaming</string> <string name="user_interface_label">Interface com usuário</string> + <string name="user_interface_sum">Aparência, pedidos de assinatura, tela de bloqueio</string> <string name="pref_set_theme_title">Selecionar tema</string> <string name="pref_nav_drawer_items_title">Configurar itens da Gaveta de Navegação</string> <string name="pref_nav_drawer_items_sum">Escolher quais itens irão aparecer na gaveta de navegação.</string> @@ -400,6 +401,10 @@ <string name="pref_automatic_download_on_battery_sum">Permitir download automático enquanto a bateria não está carregando</string> <string name="pref_parallel_downloads_title">Downloads paralelos</string> <string name="pref_episode_cache_title">Cache de episódios</string> + <string name="pref_episode_cache_summary">Número total de episódios baixados em cache no dispositivo. O download automático será suspenso se esse número for atingido.</string> + <string name="pref_episode_cover_title">Usar capa do episódio</string> + <string name="pref_episode_cover_summary">Use a capa específica do episódio sempre que disponível. Se desmarcado, o aplicativo sempre usará a imagem da capa do podcast.</string> + <string name="pref_theme_title_use_system">Usar tema do sistema</string> <string name="pref_theme_title_light">Claro</string> <string name="pref_theme_title_dark">Escuro</string> <string name="pref_theme_title_trueblack">Preto (preparado para AMOLED)</string> @@ -415,12 +420,22 @@ <string name="pref_gpodnet_setlogin_information_sum">Alterar informações de login da sua conta gpodder.net</string> <string name="pref_gpodnet_sync_changes_title">Sincronizar agora</string> <string name="pref_gpodnet_sync_changes_sum">Sincronizar as alterações de estado da inscrição e de episódios com o gpodder.net.</string> + <string name="pref_gpodnet_full_sync_title">Forçar sincronização completa</string> <string name="pref_gpodnet_full_sync_sum">Sincronizar os estados das inscrições e episódios com o gpodder.net.</string> <string name="pref_gpodnet_login_status"><![CDATA[Entrou como <i>%1$s</i> com o dispositivo <i>%2$s</i>]]></string> <string name="pref_gpodnet_notifications_title">Exibir notificações de erros de sincronismo</string> <string name="pref_gpodnet_notifications_sum">Essa configuração não se aplica a erros de autenticação.</string> <string name="pref_playback_speed_title">Velocidades de Reprodução</string> <string name="pref_playback_speed_sum">Personalize as velocidades variáveis de reprodução de áudio.</string> + <string name="pref_feed_playback_speed_sum">A velocidade a ser usada ao iniciar a reprodução de áudio para episódios neste podcast</string> + <string name="pref_feed_skip">Salto automático</string> + <string name="pref_feed_skip_sum">Pule as introduções e os créditos finais.</string> + <string name="pref_feed_skip_ending">Pular últimos</string> + <string name="pref_feed_skip_intro">Pular primeiros</string> + <string name="pref_feed_skip_ending_toast">Pulou últimos %d segundos</string> + <string name="pref_feed_skip_intro_toast">Pulou os primeiros %d segundos</string> + <string name="pref_playback_time_respects_speed_title">Ajuste as informações da mídia para a velocidade de reprodução</string> + <string name="pref_playback_time_respects_speed_sum">A posição exibida e a duração são adaptadas à velocidade de reprodução</string> <string name="pref_fast_forward">Tempo de avanço rápido</string> <string name="pref_fast_forward_sum">Personalize os segundos para avançar quando o botão avanço rápido for clicado</string> <string name="pref_rewind">Tempo de retroceder</string> @@ -439,15 +454,29 @@ <string name="pref_lockscreen_background_sum">Configurar o plano de fundo da tela de bloqueio para a imagem do episódio atual. Como um efeito colateral, também ira mostrar imagens de aplicativos de terceiros.</string> <string name="pref_showDownloadReport_title">Mostrar Relatório de Downloads</string> <string name="pref_showDownloadReport_sum">Se os downloads falharem, gerar um relatório que mostra os detalhes da falha.</string> + <string name="pref_showAutoDownloadReport_title">Mostrar relatório de downloads automáticos</string> + <string name="pref_showAutoDownloadReport_sum">Mostra uma notificação para episódios baixados automaticamente.</string> <string name="pref_expand_notify_unsupport_toast">Versões do Android inferiores a 4.1 não suportam notificações expansíveis</string> + <string name="pref_enqueue_location_title">Local da fila</string> + <string name="pref_enqueue_location_sum">Adicionar episódios para: %1$s</string> + <string name="enqueue_location_back">Final</string> + <string name="enqueue_location_front">Início</string> + <string name="enqueue_location_after_current">Depois do episódio atual</string> <string name="pref_smart_mark_as_played_disabled">Desabilitado</string> <string name="pref_image_cache_size_title">Tamanho da Imagem em Cache</string> <string name="pref_image_cache_size_sum">Tamanho do cache de disco para imagens.</string> + <string name="visit_user_forum">Fórum de usuários</string> + <string name="bug_report_title">Reportar um bug</string> + <string name="open_bug_tracker">Abrir registro de bugs</string> + <string name="export_logs">Exportar logs</string> + <string name="copy_to_clipboard">Copiar para clipboard</string> + <string name="copied_to_clipboard">Copiado para clipboard</string> <string name="experimental_pref">Experimental</string> <string name="pref_media_player_message">Selecione qual reprodutor de mídia usar para reproduzir os arquivos</string> <string name="pref_current_value">Valor atual: %1$s</string> <string name="pref_proxy_title">Proxy</string> <string name="pref_proxy_sum">Configurar um proxy da rede</string> + <string name="pref_faq">Perguntas mais frequentes</string> <string name="pref_no_browser_found">Nenhum navegador web encontrado.</string> <string name="pref_cast_title">Suporte ao Chromecast</string> <string name="pref_cast_message_play_flavor">Habilitar o suporte para reprodução remota de mídia em dispositivos Cast (como Chromecast, Caixa de som ou Android TV)</string> @@ -455,6 +484,8 @@ <string name="pref_enqueue_downloaded_title">Enfileirar os baixados</string> <string name="pref_enqueue_downloaded_summary">Adicionar episódios baixados à fila</string> <string name="media_player_builtin">Reprodutor próprio do Android</string> + <string name="media_player_switch_to_exoplayer">Alterar para ExoPlayer</string> + <string name="media_player_switched_to_exoplayer">Alterado para ExoPlayer</string> <string name="pref_skip_silence_title">Pular silêncio no áudio</string> <string name="pref_videoBehavior_title">Após fechar vídeo</string> <string name="pref_videoBehavior_sum">Comportamento ao parar a reprodução de vídeo</string> diff --git a/core/src/main/res/values-zh-rTW/strings.xml b/core/src/main/res/values-zh-rTW/strings.xml index 9d809f88d..0b3f88797 100644 --- a/core/src/main/res/values-zh-rTW/strings.xml +++ b/core/src/main/res/values-zh-rTW/strings.xml @@ -40,8 +40,8 @@ <string name="drawer_open">打開選單</string> <string name="drawer_close">關閉選單</string> <string name="drawer_preferences">側邊選單設定</string> - <string name="drawer_feed_order_unplayed_episodes">按數量排序</string> - <string name="drawer_feed_order_alphabetical">按字母表排序</string> + <string name="drawer_feed_order_unplayed_episodes">按計數器排序</string> + <string name="drawer_feed_order_alphabetical">按字母排序</string> <string name="drawer_feed_order_last_update">按發布日期排序</string> <string name="drawer_feed_order_most_played">按已播放的集數排序</string> <string name="drawer_feed_counter_new_unplayed">新增及未播放集數</string> @@ -319,7 +319,7 @@ <string name="synchronization_pref">同步</string> <string name="synchronization_sum">利用 gpodder.net 與其他裝置同步</string> <string name="automation">自動化</string> - <string name="download_pref_details">詳情</string> + <string name="download_pref_details">細節</string> <string name="import_export_pref">匯入/匯出</string> <string name="import_export_search_keywords">備份, 還原, backup, restore, export, import, 匯出, 匯入</string> <string name="appearance">外觀</string> @@ -351,10 +351,10 @@ <string name="playback_pref">播放</string> <string name="playback_pref_sum">耳機線控、快轉時間、待播清單</string> <string name="network_pref">網路</string> - <string name="network_pref_sum">更新週期、下載控制、行動數據</string> + <string name="network_pref_sum">更新週期、下載控制、行動網路</string> <string name="pref_autoUpdateIntervallOrTime_title">更新週期</string> <string name="pref_autoUpdateIntervallOrTime_sum">設定 Podcast 節目清單的更新週期</string> - <string name="pref_autoUpdateIntervallOrTime_message">您可以設定「每 2 小時」這類的<i>週期</i>或指定「每天早上 7 點」這種<i>每日定時</i>,也也可以<i>停用</i> 自動更新。\n\n<small>請注意:這裡的時間並非十分精準,可能會有些許延遲。</small></string> + <string name="pref_autoUpdateIntervallOrTime_message">您可以設定「每 2 小時」這類的<i>週期</i>或指定「每天早上 7 點」這種<i>每日定時</i>,也可以<i>停用</i> 自動更新。\n\n<small>請注意:這裡的時間並非十分精準,可能會有些許延遲。</small></string> <string name="pref_autoUpdateIntervallOrTime_Disable">停用</string> <string name="pref_autoUpdateIntervallOrTime_Interval">設定週期</string> <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">設定每日定時</string> @@ -366,20 +366,20 @@ <string name="pref_unpauseOnBluetoothReconnect_title">連上藍牙時繼續播放</string> <string name="pref_stream_over_download_title">偏好串流</string> <string name="pref_stream_over_download_sum">在清單中顯示串流播放按鈕取代下載鈕</string> - <string name="pref_mobileUpdate_title">行動網路更新</string> + <string name="pref_mobileUpdate_title">允許以行動網路…</string> <string name="pref_mobileUpdate_sum">選擇以行動網路連線時可以做的事</string> <string name="pref_mobileUpdate_refresh">更新 Podcast</string> - <string name="pref_mobileUpdate_images">封面</string> + <string name="pref_mobileUpdate_images">更新封面圖</string> <string name="pref_mobileUpdate_auto_download">自動下載</string> <string name="pref_mobileUpdate_episode_download">下載單集</string> <string name="pref_mobileUpdate_streaming">串流播放</string> <string name="user_interface_label">使用者介面</string> - <string name="user_interface_sum">外觀、訂閱順序、鎖定畫面</string> + <string name="user_interface_sum">外觀、訂閱排序、鎖定畫面</string> <string name="pref_set_theme_title">選擇主題</string> <string name="pref_nav_drawer_items_title">設定側邊選單</string> <string name="pref_nav_drawer_items_sum">調整側邊選單裡要顯示的項目</string> - <string name="pref_nav_drawer_feed_order_title">設定訂閱順序</string> - <string name="pref_nav_drawer_feed_order_sum">更改您的訂閱順序</string> + <string name="pref_nav_drawer_feed_order_title">設定訂閱排序方式</string> + <string name="pref_nav_drawer_feed_order_sum">更改您訂閱頻道的排序方式</string> <string name="pref_nav_drawer_feed_counter_title">設定訂閱計數器</string> <string name="pref_nav_drawer_feed_counter_sum">調整訂閱計數器中要顯示的東西,同時也會在排序方式設定為「計數」時影響排序。</string> <string name="pref_set_theme_sum">更改 AntennaPod 的外觀</string> @@ -394,12 +394,12 @@ <string name="pref_episode_cache_summary">在本機中可以暫存的集數,若達上限則將停止自動下載。</string> <string name="pref_episode_cover_title">使用單集的封面圖</string> <string name="pref_episode_cover_summary">在單集有專屬封面的情況下使用該封面圖。如果取消,則一律使用 Podcast 的封面圖。</string> - <string name="pref_theme_title_use_system">使用系統主題</string> + <string name="pref_theme_title_use_system">依據系統設定</string> <string name="pref_theme_title_light">淡色</string> <string name="pref_theme_title_dark">深色</string> <string name="pref_theme_title_trueblack">黑色 (適用 AMOLED 螢幕)</string> <string name="pref_episode_cache_unlimited">無限</string> - <string name="pref_update_interval_hours_plural">時數</string> + <string name="pref_update_interval_hours_plural">小時</string> <string name="pref_update_interval_hours_singular">小時</string> <string name="pref_update_interval_hours_manual">手動</string> <string name="pref_gpodnet_authenticate_title">登入</string> @@ -490,7 +490,7 @@ <string name="back_button_show_prompt">離開前需要確認</string> <string name="close_prompt">確定關閉 AntennaPod?</string> <string name="double_tap_toast">重按返回鍵以離開</string> - <string name="back_button_go_to_page">前往頁面...</string> + <string name="back_button_go_to_page">前往頁面…</string> <string name="back_button_go_to_page_title">選擇頁面</string> <string name="pref_delete_removes_from_queue_title">刪除時同步自待播清單中移除</string> <string name="pref_delete_removes_from_queue_sum">刪除某單集後,也自動將其從待播清單中移除</string> @@ -693,7 +693,7 @@ <string name="sort_title_a_z">標題 (A \u2192 Z)</string> <string name="sort_title_z_a">標題 (Z \u2192 A)</string> <string name="sort_date_new_old">日期 (新 \u2192 舊)</string> - <string name="sort_date_old_new">標題 (舊 \u2192 新)</string> + <string name="sort_date_old_new">日期 (舊 \u2192 新)</string> <string name="sort_duration_short_long">單集時間 (短 \u2192 長)</string> <string name="sort_duration_long_short">單集時間 (長 \u2192 短)</string> <string name="sort_a_z">A \u2192 Z</string> |