diff --git a/README.md b/README.md index 601d29fa..b1d44331 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,15 @@ [![Codecov branch](https://img.shields.io/codecov/c/github/ultrasonic/ultrasonic/develop.svg)]() [![ktlint](https://img.shields.io/badge/code%20style-%E2%9D%A4-FF4081.svg)](https://ktlint.github.io/) -Ultrasonic is free, open-source [Subsonic](http://www.subsonic.org/) [API](http://www.subsonic.org/pages/api.jsp) compatible music streaming Android client. +Ultrasonic is free and open-source music streaming Android client for [Subsonic](http://www.subsonic.org/) [API](http://www.subsonic.org/pages/api.jsp) (version 1.7.0 or higher) compatible servers. ## Download -App is available to download at following stores: +App is available to download at following stores: [Get it on Google Play](https://play.google.com/store/apps/details?id=org.moire.ultrasonic) [Get it on F-Droid](https://f-droid.org/packages/org.moire.ultrasonic/) - + ## Bugs and issue First, see if your issue haven’t been yet reported [here](https://github.com/ultrasonic/ultrasonic/issues), @@ -23,6 +23,6 @@ See [CONTRIBUTING](CONTRIBUTING.md). ## License -This software is licensed under the terms of the GNU General Public License version 3 (GPLv3). +This software is licensed under the terms of the GNU General Public License version 3 (GPLv3). Full text of the license is available in the [LICENSE](LICENSE) file and [online](https://opensource.org/licenses/gpl-3.0.html). diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/CommonFunctions.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/CommonFunctions.kt index b44a3b03..f01b2af5 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/CommonFunctions.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/CommonFunctions.kt @@ -1,6 +1,7 @@ package org.moire.ultrasonic.api.subsonic import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import okio.Okio import org.amshove.kluent.`should be` import org.amshove.kluent.`should contain` @@ -8,6 +9,7 @@ import org.amshove.kluent.`should not be` import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse import org.moire.ultrasonic.api.subsonic.rules.MockWebServerRule import retrofit2.Response +import java.io.InputStream import java.nio.charset.Charset import java.text.SimpleDateFormat import java.util.Calendar @@ -24,16 +26,25 @@ val dateFormat by lazy(LazyThreadSafetyMode.NONE, { }) fun MockWebServerRule.enqueueResponse(resourceName: String) { - this.mockWebServer.enqueue(MockResponse() + mockWebServer.enqueueResponse(resourceName) +} + +fun MockWebServer.enqueueResponse(resourceName: String) { + enqueue(MockResponse() .setBody(loadJsonResponse(resourceName)) .setHeader("Content-Type", "application/json;charset=UTF-8")) } -fun MockWebServerRule.loadJsonResponse(name: String): String { +fun Any.loadJsonResponse(name: String): String { val source = Okio.buffer(Okio.source(javaClass.classLoader.getResourceAsStream(name))) return source.readString(Charset.forName("UTF-8")) } +fun Any.loadResourceStream(name: String): InputStream { + val source = Okio.buffer(Okio.source(javaClass.classLoader.getResourceAsStream(name))) + return source.inputStream() +} + fun assertResponseSuccessful(response: Response) { response.isSuccessful `should be` true response.body() `should not be` null diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt new file mode 100644 index 00000000..06ba4668 --- /dev/null +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt @@ -0,0 +1,94 @@ +package org.moire.ultrasonic.api.subsonic + +import okhttp3.mockwebserver.MockWebServer +import org.amshove.kluent.`should throw` +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.io.InputStream +import java.net.InetAddress +import java.security.KeyStore +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import javax.net.ssl.KeyManagerFactory +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLHandshakeException +import javax.net.ssl.TrustManagerFactory + +private const val PORT = 8443 +private const val HOST = "localhost" + +/** + * Integration test to check [SubsonicAPIClient] interaction with different SSL scenarios. + */ +class SubsonicApiSSLTest { + private val mockWebServer = MockWebServer() + + @Before + fun setUp() { + val sslContext = createSSLContext(loadResourceStream("self-signed.pem"), + loadResourceStream("self-signed.p12"), "") + mockWebServer.useHttps(sslContext.socketFactory, false) + mockWebServer.start(InetAddress.getByName(HOST), PORT) + } + + @After + fun tearDown() { + mockWebServer.shutdown() + } + + private fun createSSLContext(certificatePemStream: InputStream, + certificatePkcs12Stream: InputStream, + password: String): SSLContext { + var cert: X509Certificate? = null + val trustStore = KeyStore.getInstance(KeyStore.getDefaultType()) + trustStore.load(null) + + certificatePemStream.use { + cert = (CertificateFactory.getInstance("X.509") + .generateCertificate(certificatePemStream)) as X509Certificate + } + val alias = cert?.subjectX500Principal?.name ?: + throw IllegalStateException("Failed to load certificate") + trustStore.setCertificateEntry(alias, cert) + + val tmf = TrustManagerFactory.getInstance("X509") + tmf.init(trustStore) + val trustManagers = tmf.trustManagers + val sslContext = SSLContext.getInstance("TLS") + + val ks = KeyStore.getInstance("PKCS12") + ks.load(certificatePkcs12Stream, password.toCharArray()) + val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) + kmf.init(ks, password.toCharArray()) + + sslContext.init(kmf.keyManagers, trustManagers, null) + return sslContext + } + + @Test + fun `Should fail request if self-signed certificate support is disabled`() { + val client = createSubsonicClient(false) + mockWebServer.enqueueResponse("ping_ok.json") + + val fail = { + client.api.ping().execute() + } + + fail `should throw` SSLHandshakeException::class + } + + @Test + fun `Should pass request if self-signed certificate support is enabled`() { + val client = createSubsonicClient(true) + mockWebServer.enqueueResponse("ping_ok.json") + + val response = client.api.ping().execute() + + assertResponseSuccessful(response) + } + + private fun createSubsonicClient(allowSelfSignedCertificate: Boolean) = SubsonicAPIClient( + "https://$HOST:$PORT/", USERNAME, PASSWORD, CLIENT_VERSION, CLIENT_ID, + allowSelfSignedCertificate = allowSelfSignedCertificate) +} diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/rules/MockWebServerRule.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/rules/MockWebServerRule.kt index 0be7a746..8b208642 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/rules/MockWebServerRule.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/rules/MockWebServerRule.kt @@ -12,7 +12,7 @@ class MockWebServerRule : TestRule { val mockWebServer = MockWebServer() override fun apply(base: Statement?, description: Description?): Statement { - val ruleStatement = object : Statement() { + return object : Statement() { override fun evaluate() { try { mockWebServer.start() @@ -22,6 +22,5 @@ class MockWebServerRule : TestRule { } } } - return ruleStatement } } diff --git a/subsonic-api/src/integrationTest/resources/self-signed.p12 b/subsonic-api/src/integrationTest/resources/self-signed.p12 new file mode 100644 index 00000000..3f2000d3 Binary files /dev/null and b/subsonic-api/src/integrationTest/resources/self-signed.p12 differ diff --git a/subsonic-api/src/integrationTest/resources/self-signed.pem b/subsonic-api/src/integrationTest/resources/self-signed.pem new file mode 100644 index 00000000..ee0b8d14 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/self-signed.pem @@ -0,0 +1,84 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEA6eq9gNJFxdTuO/EQoiEd9b7JwdApmg6ds7hPM1wyS8w9rL8F +BZBkFv0sl+mf+HqtQkCyfvcmF3zu8gi6BnF1HdErEYALN7fIYwAREn4K/3rDjKeb +WLsMdWBN5Q5s4CsAz34GtchA8X7wcvFG3hY4Q+/dWHoSJP5ag6Cd228ExUrgSmnl +CvPmHxAOGzhygI8PuLitK4cP40gkEyIz385JnJ0VOAmZ/E08O05qS8zama/U4Adq +XPYSrR8FHfhzO8MIug3uYf4USl/eI+8vV7JzU9CzwfHN7REBlcVPNitTDliye7Ak +yoM/1ZSoqZli7EjS0FGx4Ez5onh8f1zdKyJZqNgkYc+olv2wTQT0q1iqjaAxsnh2 +JisO+kzo+8zks5O0KHlLBRUgkrZQX0C2RFuCCXjmkyRu9ZKZEDar2aB5QbOsH1J9 +W6INWNqWkQoqtj40B4MncOTTlyTGPKOFjhD3p2ihI3uymfbhLdSOF2thxoNFLKkJ ++YnsK+7TdvpNh+9EnE6SRT2i2jFxmPaSRXvsT2vm874qBs1+flRyvW7xU1aVjZvm +IdkKqi4oRD+Ee95mTLihXmdMQmdvDlsj3Ad07zLt31w+xUtkIHlQQHdXm2ZolXGz +xvtN3aJ6rnEHqk0yyE77MoR1wnhmWRWGEOADDRmbOy5X83Bqk0q3D6HmHEsCAwEA +AQKCAgBJIx8rRxOPvnrafQ4RUz911bhpg/dt9sHyLl99FIeZUXu7JmKgkbvpwDEQ +MnjVDS5c97OXpRjg4SwouvfHCfRvZTYNG7bmLe1Wnu+3k3dG2BCKSuF0hc9oZ7sT +MkZydJ+lQKdCcSF1IJZ3qd7Zk6L2Aup3Pnur22dbnn2c3YJlWXr1aVS27vl1nuR6 +OFT8wz5MKFnksS8ThjvZS6ligbJcaHT492+RBmkdte/gUWXMBcEOZuMnu7ytKnTE +ISmOdvWkjrSJKRMZCg5/t8papi4O98MskbksNVQEixOwQS2P38W2jKWEODNeSUPO ++2mFrWNUxSZTll27IebzP4rbcLsNSbS1LFN/Xe+R2DZ91J4+OaIBOkMCiqofpfXN +UZ2hwRiQrT/WL325XLKZtf5kUqRiQgFmtfbkq64ZEuMrLJiG/brrxnbsOobREg+v +SN2lZz/bcCdguj97TQ3Oe74hVZRzId/tcr7KtujRMWWzsnWT5a1H/vmVQYVBQg4p +iB4cTQMR2heNwkvMGEOBw59m2gIDlnsg50V6IKYWfhY4WnvqalQkZj+S/Ki9YBP/ +DjHSzEG0pJLyt9GZejcKy4JyEsC+Bmt+LwxqMlPJqvbvYkvI4O3ef8+8tNNika0q +gDvH03iP55uPCUugDD+9IOBv0HgmjG9jilYwNyAomFTsHo10KQKCAQEA+EfPPhQf +4E3bkebuLz1ZfqVAYqzNW/nvY9AdWcQPFE6PB1y7ABUczhw1wPfG0l+DnVHTzA95 +iUglAUtgOP87ELfimeoJLnwfzDPAJdwBLjwjvtOEK+nFHtgRd7+7OyZvu3rymgKr +iIzjjga18UUMxRPcDAYsh9DfsXnKCwCXgvM7Ei5BC+gvlgNRBwPuREqT0vua+hYH +jCPFFKsJgCuooXGXbnGHifIiWZxntqJ2+TXIrvV7xia6UYJd+bvdxUFxtdgNbGOg +3L7EuMJ7si8EoJqVtLi9oYXwTEzNQeeP5QULzgGmpXyF36WUck4k0ktMK0vCDfAX +aNFRIgIvZxMSLwKCAQEA8TCamfeQ3ts+V5OkNhs5gTT09iNWqgTPV2t1ZqKEp9id +jsJIV29r1Qod+QcVvbKiXNDeYu9MoTMs4ssNwKHsEpUrYCBzOmwipQ7hudTE97cw +3ORWC6D6SEfmtqDEe+sR17aL/844y3XIiuGEx/Ek/qF71kB6U4tD8+WhaZbehMmh +IxYykjueBpDTGM6JrC7v4CWpHiZlDI4YIiyxcA/UCEa/EJV1Cd+5IYziXd64kqCo +I8//7d0qsCkE2mz7tR3hK6j1X6anqjoEIJg70NmkRN1V28LZBQZ9EjgSakcRqJOj +sKS1ebg+CPU5BHJYNYv9SolkcO2n3cQ2vxtXGPXcpQKCAQAdycfUo+d7Kvw4EiPr +qQmuxzblX+Q3r9IIALU0yvAgOJiygm6xQNc2522PnGrPXMRWwLWPmx+y1+QQtrFx +xTWZ+OYIH2tAl4XdIyxfnnjJyk9jms8V0bNj0vqtimR1YVQwgzzOO5nHBVhb9vQn +YWh50Lsq+ianmOjtyzXxgf2rqXEh6kjFm/Lxpa44EEGrEeOQgb2DWddH+hawNyEp +rpNJ424OwzJG27VBWSGcaPurRMeyLiPOj2D1XJXX27Fs9EAnWCesJHvtYDoMDNF4 +fGmqt0FU8IFX+tDs5p4N1TGPgb571fjfjAQn5B7eY//I913JKAq9T1wPqGV6lhaH +4GLjAoIBAQCLdxduwIC83PoHmg/yWXu/AuhDC9wpI+7hFfolBwS+KbuxuRYruPoZ +jmgWf8pKjujj0sNFYiplbDogSloBcaAYfrk+NIVs2uqNlzVfR3E97GgM0twOjV8s +PKdkI0J6hUsj+SKrIIwm2kzEQfONyhsiQi5hjZcuh/EbL0VO0TaKgizzJPrJJEAU +e9oVFhj1v45lhmFsVbdIs0GxQTa5He31ezMwW7v5oaxjghvDO+5umwee7b+Hw8PT +aWStCSfjawuxO1nnnW6GOFX6owyzj6Y1S+dB1EG5bi8UQegkHERRvk2A7z0gzTDR +7TqzH4tyKyij2R6DTmkrCzK8/wo2HLUhAoIBAAyYiOI4BHTExzilGO9gcmPeXVgl +E2QYfeFOgEg4BrbV+qdm+kXFXmO1hzUk/3ucEc/QAZMj1guRWeTtR57i1rJ2QifN +dUcTh86HtRA1li4G4gCXLwwhBFlBQH9hyhNJ8dYWx/RMqSYU2YBFsJ2q3CckBAy5 +fNYKvqdH9usPQ9uy8pdHucDYxTdmpbsKFJ00JyUsuh+eV66ajwbuKc74khmMjeEq +ZtWznnZvv9ujYng0/xHMVyNMFB9uFBC0K6UrIdUsU8bP7W50xarzsRx85ueN+F/e +Y5vFLcWQgoC9Nmi7Zt/95JXTzXyysSvQz8uDd//98x6Ud7CmR9xYdBtTay0= +-----END RSA PRIVATE KEY----- diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt index 15fc5fae..db769ccc 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt @@ -17,7 +17,11 @@ import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse import retrofit2.Response import retrofit2.Retrofit import retrofit2.converter.jackson.JacksonConverterFactory +import java.security.SecureRandom +import java.security.cert.X509Certificate import java.util.concurrent.TimeUnit.MILLISECONDS +import javax.net.ssl.SSLContext +import javax.net.ssl.X509TrustManager private const val READ_TIMEOUT = 60_000L @@ -36,6 +40,7 @@ class SubsonicAPIClient(baseUrl: String, password: String, minimalProtocolVersion: SubsonicAPIVersions, clientID: String, + allowSelfSignedCertificate: Boolean = false, debug: Boolean = false) { private val versionInterceptor = VersionInterceptor(minimalProtocolVersion) { protocolVersion = it @@ -56,6 +61,7 @@ class SubsonicAPIClient(baseUrl: String, private val okHttpClient = OkHttpClient.Builder() .readTimeout(READ_TIMEOUT, MILLISECONDS) + .apply { if (allowSelfSignedCertificate) allowSelfSignedCertificates() } .addInterceptor { chain -> // Adds default request params val originalRequest = chain.request() @@ -165,4 +171,20 @@ class SubsonicAPIClient(baseUrl: String, loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY this.addInterceptor(loggingInterceptor) } + + @SuppressWarnings("TrustAllX509TrustManager") + private fun OkHttpClient.Builder.allowSelfSignedCertificates() { + val trustManager = object : X509TrustManager { + override fun checkClientTrusted(p0: Array?, p1: String?) {} + override fun checkServerTrusted(p0: Array?, p1: String?) {} + override fun getAcceptedIssuers(): Array = emptyArray() + } + + val sslContext = SSLContext.getInstance("SSL") + sslContext.init(null, arrayOf(trustManager), SecureRandom()) + + sslSocketFactory(sslContext.socketFactory, trustManager) + + hostnameVerifier { _, _ -> true } + } } diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index b2d2e8cd..85d16484 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -7,7 +7,7 @@ apply from: "../gradle_scripts/code_quality.gradle" advancedVersioning { nameOptions { versionMajor 2 - versionMinor 0 + versionMinor 2 versionPatch 0 } codeOptions { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java index 6c8e4d9e..b9fcb529 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/ServerSettingsFragment.java @@ -37,6 +37,7 @@ public class ServerSettingsFragment extends PreferenceFragment private EditTextPreference serverPasswordPref; private CheckBoxPreference equalizerPref; private CheckBoxPreference jukeboxPref; + private CheckBoxPreference allowSelfSignedCertificatePref; private Preference removeServerPref; private Preference testConnectionPref; @@ -74,6 +75,8 @@ public class ServerSettingsFragment extends PreferenceFragment jukeboxPref = (CheckBoxPreference) findPreference(getString(R.string.jukebox_is_default)); removeServerPref = findPreference(getString(R.string.settings_server_remove_server)); testConnectionPref = findPreference(getString(R.string.settings_test_connection_title)); + allowSelfSignedCertificatePref = (CheckBoxPreference) findPreference( + getString(R.string.settings_allow_self_signed_certificate)); setupPreferencesValues(); setupPreferencesListeners(); @@ -132,6 +135,11 @@ public class ServerSettingsFragment extends PreferenceFragment .putBoolean(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + serverId, (Boolean) newValue) .apply(); return true; + } else if (preference == allowSelfSignedCertificatePref) { + sharedPreferences.edit() + .putBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId, (Boolean) newValue) + .apply(); + return true; } return false; } @@ -164,6 +172,9 @@ public class ServerSettingsFragment extends PreferenceFragment jukeboxPref.setChecked(sharedPreferences .getBoolean(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + serverId, false)); + + allowSelfSignedCertificatePref.setChecked(sharedPreferences + .getBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId, false)); } private void updatePassword() { @@ -201,6 +212,7 @@ public class ServerSettingsFragment extends PreferenceFragment serverPasswordPref.setOnPreferenceChangeListener(this); equalizerPref.setOnPreferenceChangeListener(this); jukeboxPref.setOnPreferenceChangeListener(this); + allowSelfSignedCertificatePref.setOnPreferenceChangeListener(this); removeServerPref.setOnPreferenceClickListener(this); testConnectionPref.setOnPreferenceClickListener(this); @@ -262,6 +274,7 @@ public class ServerSettingsFragment extends PreferenceFragment .remove(Constants.PREFERENCES_KEY_PASSWORD + serverId) .remove(Constants.PREFERENCES_KEY_SERVER_ENABLED + serverId) .remove(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + serverId) + .remove(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId) .apply(); if (serverId < activeServers) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java index 3fa24033..e274412a 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicServiceFactory.java @@ -84,6 +84,8 @@ public class MusicServiceFactory { String serverUrl = preferences.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null); String username = preferences.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null); String password = preferences.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null); + boolean allowSelfSignedCertificate = preferences + .getBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + instance, false); if (serverUrl == null || username == null || @@ -91,11 +93,11 @@ public class MusicServiceFactory { Log.i("MusicServiceFactory", "Server credentials is not available"); return new SubsonicAPIClient("http://localhost", "", "", SubsonicAPIVersions.fromApiVersion(Constants.REST_PROTOCOL_VERSION), - Constants.REST_CLIENT_ID, BuildConfig.DEBUG); + Constants.REST_CLIENT_ID, allowSelfSignedCertificate, BuildConfig.DEBUG); } return new SubsonicAPIClient(serverUrl, username, password, SubsonicAPIVersions.fromApiVersion(Constants.REST_PROTOCOL_VERSION), - Constants.REST_CLIENT_ID, BuildConfig.DEBUG); + Constants.REST_CLIENT_ID, allowSelfSignedCertificate, BuildConfig.DEBUG); } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java index 19c4a199..a3aab7e5 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java @@ -72,11 +72,11 @@ public final class Constants public static final String PREFERENCES_KEY_SERVER_URL = "serverUrl"; public static final String PREFERENCES_KEY_SERVERS_KEY = "serversKey"; public static final String PREFERENCES_KEY_ADD_SERVER = "addServer"; - public static final String PREFERENCES_KEY_REMOVE_SERVER = "removeServer"; public static final String PREFERENCES_KEY_ACTIVE_SERVERS = "activeServers"; public static final String PREFERENCES_KEY_MUSIC_FOLDER_ID = "musicFolderId"; public static final String PREFERENCES_KEY_USERNAME = "username"; public static final String PREFERENCES_KEY_PASSWORD = "password"; + public static final String PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE = "allowSSCertificate"; public static final String PREFERENCES_KEY_INSTALL_TIME = "installTime"; public static final String PREFERENCES_KEY_THEME = "theme"; public static final String PREFERENCES_KEY_DISPLAY_BITRATE_WITH_ARTIST = "displayBitrateWithArtist"; @@ -109,7 +109,6 @@ public final class Constants public static final String PREFERENCES_KEY_GAPLESS_PLAYBACK = "gaplessPlayback"; public static final String PREFERENCES_KEY_PLAYBACK_CONTROL_SETTINGS = "playbackControlSettings"; public static final String PREFERENCES_KEY_CLEAR_SEARCH_HISTORY = "clearSearchHistory"; - public static final String PREFERENCES_KEY_TEST_CONNECTION = "testConnection"; public static final String PREFERENCES_KEY_DOWNLOAD_TRANSITION = "transitionToDownloadOnPlay"; public static final String PREFERENCES_KEY_INCREMENT_TIME = "incrementTime"; public static final String PREFERENCES_KEY_ID3_TAGS = "useId3Tags"; diff --git a/ultrasonic/src/main/res/values-es/strings.xml b/ultrasonic/src/main/res/values-es/strings.xml index 7a04ad5b..e96ec80f 100644 --- a/ultrasonic/src/main/res/values-es/strings.xml +++ b/ultrasonic/src/main/res/values-es/strings.xml @@ -300,6 +300,7 @@ Oscuro Claro Tema + Permir certificado HTTPS autofirmado Usar carpetas para el nombre del artista Se asume que la carpeta en el nivel mal alto es el nombre del artista del álbum Navegar usando las etiquetas ID3 diff --git a/ultrasonic/src/main/res/values-fr/strings.xml b/ultrasonic/src/main/res/values-fr/strings.xml index 698ba92d..eeb7d42b 100644 --- a/ultrasonic/src/main/res/values-fr/strings.xml +++ b/ultrasonic/src/main/res/values-fr/strings.xml @@ -300,6 +300,7 @@ Sombre Clair Thème + Autoriser le certificat HTTPS auto-signé Utilisez des dossiers pour les noms d\'artistes Dossier de niveau supérieur devient le nom de l\'artiste de l\'album Naviguer en utilisant ID3 Tags diff --git a/ultrasonic/src/main/res/values-hu/strings.xml b/ultrasonic/src/main/res/values-hu/strings.xml index 4a92588b..6389458c 100644 --- a/ultrasonic/src/main/res/values-hu/strings.xml +++ b/ultrasonic/src/main/res/values-hu/strings.xml @@ -300,6 +300,7 @@ Sötét Világos Téma + Engedélyezze az önaláírt HTTPS tanúsítványt Mappanevek használata az előadók neveként Feltételezi, hogy a legfelső szintű mappa az előadó neve. Böngészés ID3 Tag használatával diff --git a/ultrasonic/src/main/res/values-pt-rBR/strings.xml b/ultrasonic/src/main/res/values-pt-rBR/strings.xml index 552061ce..839c8643 100644 --- a/ultrasonic/src/main/res/values-pt-rBR/strings.xml +++ b/ultrasonic/src/main/res/values-pt-rBR/strings.xml @@ -303,6 +303,7 @@ Escuro Claro Tema + Permitir o certificado HTTPS auto-assinado Pasta para Nome do Artista Assume que a pasta mais acima é o nome do artista Navegar Usando Etiquetas ID3 diff --git a/ultrasonic/src/main/res/values-pt/strings.xml b/ultrasonic/src/main/res/values-pt/strings.xml index 64d0035c..0cf5ac22 100644 --- a/ultrasonic/src/main/res/values-pt/strings.xml +++ b/ultrasonic/src/main/res/values-pt/strings.xml @@ -303,6 +303,7 @@ Escuro Claro Tema + Permitir o certificado HTTPS auto-assinado Pasta para Nome do Artista Assume que a pasta mais acima é o nome do artista Navegar Usando Etiquetas ID3 diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index ccbba6c9..7ed490c8 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -146,6 +146,7 @@ No genres found No saved playlists on server Contacting server, please wait. + allowSelfSignedCertificate Appearance Buffer Length Disabled @@ -303,6 +304,7 @@ Dark Light Theme + Allow self-signed HTTPS certificate Use Folders For Artist Name Assume top-level folder is the name of the album artist Browse Using ID3 Tags diff --git a/ultrasonic/src/main/res/xml/server_settings.xml b/ultrasonic/src/main/res/xml/server_settings.xml index cc107299..3f755b58 100644 --- a/ultrasonic/src/main/res/xml/server_settings.xml +++ b/ultrasonic/src/main/res/xml/server_settings.xml @@ -38,6 +38,12 @@ android:defaultValue="true" android:title="@string/equalizer.enabled" /> +