From 1333534988d7bcbd9e6c3f17e409946b9d9b2192 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Mon, 25 Dec 2017 12:25:54 +0100 Subject: [PATCH 1/4] Allow to use self-signed certificates. By default OkHttpClient will not allow self-signed certificates, but some of app users use them. This is disabled by default, should be enabled explicitly. It also allows any CN in self-signed certificate. Signed-off-by: Yahor Berdnikau --- .../api/subsonic/CommonFunctions.kt | 15 ++- .../api/subsonic/SubsonicApiSSLTest.kt | 94 ++++++++++++++++++ .../api/subsonic/rules/MockWebServerRule.kt | 3 +- .../integrationTest/resources/self-signed.p12 | Bin 0 -> 4237 bytes .../integrationTest/resources/self-signed.pem | 84 ++++++++++++++++ .../api/subsonic/SubsonicAPIClient.kt | 22 ++++ 6 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSSLTest.kt create mode 100644 subsonic-api/src/integrationTest/resources/self-signed.p12 create mode 100644 subsonic-api/src/integrationTest/resources/self-signed.pem 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 0000000000000000000000000000000000000000..3f2000d33e8f174c60d14f8239f855218b162cf4 GIT binary patch literal 4237 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 literal 0 HcmV?d00001 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----- +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 +-----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 } + } } From 592ab16b94d30d080f8a3074ea97dc5285d6b2fc Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Mon, 25 Dec 2017 13:04:27 +0100 Subject: [PATCH 2/4] Add setting to server config to allow self-signed certificates. It is disabled by default. Signed-off-by: Yahor Berdnikau --- .../ultrasonic/fragment/ServerSettingsFragment.java | 13 +++++++++++++ .../ultrasonic/service/MusicServiceFactory.java | 6 ++++-- .../java/org/moire/ultrasonic/util/Constants.java | 3 +-- ultrasonic/src/main/res/values-es/strings.xml | 1 + ultrasonic/src/main/res/values-fr/strings.xml | 1 + ultrasonic/src/main/res/values-hu/strings.xml | 1 + ultrasonic/src/main/res/values-pt-rBR/strings.xml | 1 + ultrasonic/src/main/res/values-pt/strings.xml | 1 + ultrasonic/src/main/res/values/strings.xml | 2 ++ ultrasonic/src/main/res/xml/server_settings.xml | 6 ++++++ 10 files changed, 31 insertions(+), 4 deletions(-) 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..499ba26c 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 + Permita el 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 f9255200..643fad6a 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 5ce59f3a..2767ebec 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" /> + Date: Tue, 26 Dec 2017 19:17:53 +0100 Subject: [PATCH 3/4] Fix spanish translation --- ultrasonic/src/main/res/values-es/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultrasonic/src/main/res/values-es/strings.xml b/ultrasonic/src/main/res/values-es/strings.xml index 499ba26c..e96ec80f 100644 --- a/ultrasonic/src/main/res/values-es/strings.xml +++ b/ultrasonic/src/main/res/values-es/strings.xml @@ -300,7 +300,7 @@ Oscuro Claro Tema - Permita el certificado HTTPS autofirmado + 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 From 6b6c8a045fbaaae04eb493420a31df8fc6012a77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=81scar=20Garci=CC=81a=20Amor?= Date: Tue, 26 Dec 2017 19:35:25 +0100 Subject: [PATCH 4/4] Updated versionCode and versionName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Óscar García Amor --- ultrasonic/src/main/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultrasonic/src/main/AndroidManifest.xml b/ultrasonic/src/main/AndroidManifest.xml index d24a6d8d..f9c69f40 100644 --- a/ultrasonic/src/main/AndroidManifest.xml +++ b/ultrasonic/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ + a:versionCode="60" + a:versionName="2.1.0">