mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-28 17:37:39 +01:00
Merge branch 'master' into develop
This commit is contained in:
commit
c9bc3df543
@ -3,7 +3,7 @@
|
|||||||
[]()
|
[]()
|
||||||
[](https://ktlint.github.io/)
|
[](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
|
## Download
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.moire.ultrasonic.api.subsonic
|
package org.moire.ultrasonic.api.subsonic
|
||||||
|
|
||||||
import okhttp3.mockwebserver.MockResponse
|
import okhttp3.mockwebserver.MockResponse
|
||||||
|
import okhttp3.mockwebserver.MockWebServer
|
||||||
import okio.Okio
|
import okio.Okio
|
||||||
import org.amshove.kluent.`should be`
|
import org.amshove.kluent.`should be`
|
||||||
import org.amshove.kluent.`should contain`
|
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.response.SubsonicResponse
|
||||||
import org.moire.ultrasonic.api.subsonic.rules.MockWebServerRule
|
import org.moire.ultrasonic.api.subsonic.rules.MockWebServerRule
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
import java.io.InputStream
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
@ -24,16 +26,25 @@ val dateFormat by lazy(LazyThreadSafetyMode.NONE, {
|
|||||||
})
|
})
|
||||||
|
|
||||||
fun MockWebServerRule.enqueueResponse(resourceName: String) {
|
fun MockWebServerRule.enqueueResponse(resourceName: String) {
|
||||||
this.mockWebServer.enqueue(MockResponse()
|
mockWebServer.enqueueResponse(resourceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MockWebServer.enqueueResponse(resourceName: String) {
|
||||||
|
enqueue(MockResponse()
|
||||||
.setBody(loadJsonResponse(resourceName))
|
.setBody(loadJsonResponse(resourceName))
|
||||||
.setHeader("Content-Type", "application/json;charset=UTF-8"))
|
.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)))
|
val source = Okio.buffer(Okio.source(javaClass.classLoader.getResourceAsStream(name)))
|
||||||
return source.readString(Charset.forName("UTF-8"))
|
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 <T> assertResponseSuccessful(response: Response<T>) {
|
fun <T> assertResponseSuccessful(response: Response<T>) {
|
||||||
response.isSuccessful `should be` true
|
response.isSuccessful `should be` true
|
||||||
response.body() `should not be` null
|
response.body() `should not be` null
|
||||||
|
@ -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)
|
||||||
|
}
|
@ -12,7 +12,7 @@ class MockWebServerRule : TestRule {
|
|||||||
val mockWebServer = MockWebServer()
|
val mockWebServer = MockWebServer()
|
||||||
|
|
||||||
override fun apply(base: Statement?, description: Description?): Statement {
|
override fun apply(base: Statement?, description: Description?): Statement {
|
||||||
val ruleStatement = object : Statement() {
|
return object : Statement() {
|
||||||
override fun evaluate() {
|
override fun evaluate() {
|
||||||
try {
|
try {
|
||||||
mockWebServer.start()
|
mockWebServer.start()
|
||||||
@ -22,6 +22,5 @@ class MockWebServerRule : TestRule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ruleStatement
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
subsonic-api/src/integrationTest/resources/self-signed.p12
Normal file
BIN
subsonic-api/src/integrationTest/resources/self-signed.p12
Normal file
Binary file not shown.
84
subsonic-api/src/integrationTest/resources/self-signed.pem
Normal file
84
subsonic-api/src/integrationTest/resources/self-signed.pem
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
@ -17,7 +17,11 @@ import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
|
|||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.jackson.JacksonConverterFactory
|
import retrofit2.converter.jackson.JacksonConverterFactory
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||||
|
import javax.net.ssl.SSLContext
|
||||||
|
import javax.net.ssl.X509TrustManager
|
||||||
|
|
||||||
private const val READ_TIMEOUT = 60_000L
|
private const val READ_TIMEOUT = 60_000L
|
||||||
|
|
||||||
@ -36,6 +40,7 @@ class SubsonicAPIClient(baseUrl: String,
|
|||||||
password: String,
|
password: String,
|
||||||
minimalProtocolVersion: SubsonicAPIVersions,
|
minimalProtocolVersion: SubsonicAPIVersions,
|
||||||
clientID: String,
|
clientID: String,
|
||||||
|
allowSelfSignedCertificate: Boolean = false,
|
||||||
debug: Boolean = false) {
|
debug: Boolean = false) {
|
||||||
private val versionInterceptor = VersionInterceptor(minimalProtocolVersion) {
|
private val versionInterceptor = VersionInterceptor(minimalProtocolVersion) {
|
||||||
protocolVersion = it
|
protocolVersion = it
|
||||||
@ -56,6 +61,7 @@ class SubsonicAPIClient(baseUrl: String,
|
|||||||
|
|
||||||
private val okHttpClient = OkHttpClient.Builder()
|
private val okHttpClient = OkHttpClient.Builder()
|
||||||
.readTimeout(READ_TIMEOUT, MILLISECONDS)
|
.readTimeout(READ_TIMEOUT, MILLISECONDS)
|
||||||
|
.apply { if (allowSelfSignedCertificate) allowSelfSignedCertificates() }
|
||||||
.addInterceptor { chain ->
|
.addInterceptor { chain ->
|
||||||
// Adds default request params
|
// Adds default request params
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
@ -165,4 +171,20 @@ class SubsonicAPIClient(baseUrl: String,
|
|||||||
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
|
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
|
||||||
this.addInterceptor(loggingInterceptor)
|
this.addInterceptor(loggingInterceptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("TrustAllX509TrustManager")
|
||||||
|
private fun OkHttpClient.Builder.allowSelfSignedCertificates() {
|
||||||
|
val trustManager = object : X509TrustManager {
|
||||||
|
override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) {}
|
||||||
|
override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) {}
|
||||||
|
override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
val sslContext = SSLContext.getInstance("SSL")
|
||||||
|
sslContext.init(null, arrayOf(trustManager), SecureRandom())
|
||||||
|
|
||||||
|
sslSocketFactory(sslContext.socketFactory, trustManager)
|
||||||
|
|
||||||
|
hostnameVerifier { _, _ -> true }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ apply from: "../gradle_scripts/code_quality.gradle"
|
|||||||
advancedVersioning {
|
advancedVersioning {
|
||||||
nameOptions {
|
nameOptions {
|
||||||
versionMajor 2
|
versionMajor 2
|
||||||
versionMinor 0
|
versionMinor 2
|
||||||
versionPatch 0
|
versionPatch 0
|
||||||
}
|
}
|
||||||
codeOptions {
|
codeOptions {
|
||||||
|
@ -37,6 +37,7 @@ public class ServerSettingsFragment extends PreferenceFragment
|
|||||||
private EditTextPreference serverPasswordPref;
|
private EditTextPreference serverPasswordPref;
|
||||||
private CheckBoxPreference equalizerPref;
|
private CheckBoxPreference equalizerPref;
|
||||||
private CheckBoxPreference jukeboxPref;
|
private CheckBoxPreference jukeboxPref;
|
||||||
|
private CheckBoxPreference allowSelfSignedCertificatePref;
|
||||||
private Preference removeServerPref;
|
private Preference removeServerPref;
|
||||||
private Preference testConnectionPref;
|
private Preference testConnectionPref;
|
||||||
|
|
||||||
@ -74,6 +75,8 @@ public class ServerSettingsFragment extends PreferenceFragment
|
|||||||
jukeboxPref = (CheckBoxPreference) findPreference(getString(R.string.jukebox_is_default));
|
jukeboxPref = (CheckBoxPreference) findPreference(getString(R.string.jukebox_is_default));
|
||||||
removeServerPref = findPreference(getString(R.string.settings_server_remove_server));
|
removeServerPref = findPreference(getString(R.string.settings_server_remove_server));
|
||||||
testConnectionPref = findPreference(getString(R.string.settings_test_connection_title));
|
testConnectionPref = findPreference(getString(R.string.settings_test_connection_title));
|
||||||
|
allowSelfSignedCertificatePref = (CheckBoxPreference) findPreference(
|
||||||
|
getString(R.string.settings_allow_self_signed_certificate));
|
||||||
|
|
||||||
setupPreferencesValues();
|
setupPreferencesValues();
|
||||||
setupPreferencesListeners();
|
setupPreferencesListeners();
|
||||||
@ -132,6 +135,11 @@ public class ServerSettingsFragment extends PreferenceFragment
|
|||||||
.putBoolean(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + serverId, (Boolean) newValue)
|
.putBoolean(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + serverId, (Boolean) newValue)
|
||||||
.apply();
|
.apply();
|
||||||
return true;
|
return true;
|
||||||
|
} else if (preference == allowSelfSignedCertificatePref) {
|
||||||
|
sharedPreferences.edit()
|
||||||
|
.putBoolean(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId, (Boolean) newValue)
|
||||||
|
.apply();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -164,6 +172,9 @@ public class ServerSettingsFragment extends PreferenceFragment
|
|||||||
|
|
||||||
jukeboxPref.setChecked(sharedPreferences
|
jukeboxPref.setChecked(sharedPreferences
|
||||||
.getBoolean(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + serverId, false));
|
.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() {
|
private void updatePassword() {
|
||||||
@ -201,6 +212,7 @@ public class ServerSettingsFragment extends PreferenceFragment
|
|||||||
serverPasswordPref.setOnPreferenceChangeListener(this);
|
serverPasswordPref.setOnPreferenceChangeListener(this);
|
||||||
equalizerPref.setOnPreferenceChangeListener(this);
|
equalizerPref.setOnPreferenceChangeListener(this);
|
||||||
jukeboxPref.setOnPreferenceChangeListener(this);
|
jukeboxPref.setOnPreferenceChangeListener(this);
|
||||||
|
allowSelfSignedCertificatePref.setOnPreferenceChangeListener(this);
|
||||||
|
|
||||||
removeServerPref.setOnPreferenceClickListener(this);
|
removeServerPref.setOnPreferenceClickListener(this);
|
||||||
testConnectionPref.setOnPreferenceClickListener(this);
|
testConnectionPref.setOnPreferenceClickListener(this);
|
||||||
@ -262,6 +274,7 @@ public class ServerSettingsFragment extends PreferenceFragment
|
|||||||
.remove(Constants.PREFERENCES_KEY_PASSWORD + serverId)
|
.remove(Constants.PREFERENCES_KEY_PASSWORD + serverId)
|
||||||
.remove(Constants.PREFERENCES_KEY_SERVER_ENABLED + serverId)
|
.remove(Constants.PREFERENCES_KEY_SERVER_ENABLED + serverId)
|
||||||
.remove(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + serverId)
|
.remove(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + serverId)
|
||||||
|
.remove(Constants.PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE + serverId)
|
||||||
.apply();
|
.apply();
|
||||||
|
|
||||||
if (serverId < activeServers) {
|
if (serverId < activeServers) {
|
||||||
|
@ -84,6 +84,8 @@ public class MusicServiceFactory {
|
|||||||
String serverUrl = preferences.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
|
String serverUrl = preferences.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
|
||||||
String username = preferences.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
|
String username = preferences.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
|
||||||
String password = preferences.getString(Constants.PREFERENCES_KEY_PASSWORD + 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 ||
|
if (serverUrl == null ||
|
||||||
username == null ||
|
username == null ||
|
||||||
@ -91,11 +93,11 @@ public class MusicServiceFactory {
|
|||||||
Log.i("MusicServiceFactory", "Server credentials is not available");
|
Log.i("MusicServiceFactory", "Server credentials is not available");
|
||||||
return new SubsonicAPIClient("http://localhost", "", "",
|
return new SubsonicAPIClient("http://localhost", "", "",
|
||||||
SubsonicAPIVersions.fromApiVersion(Constants.REST_PROTOCOL_VERSION),
|
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,
|
return new SubsonicAPIClient(serverUrl, username, password,
|
||||||
SubsonicAPIVersions.fromApiVersion(Constants.REST_PROTOCOL_VERSION),
|
SubsonicAPIVersions.fromApiVersion(Constants.REST_PROTOCOL_VERSION),
|
||||||
Constants.REST_CLIENT_ID, BuildConfig.DEBUG);
|
Constants.REST_CLIENT_ID, allowSelfSignedCertificate, BuildConfig.DEBUG);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,11 +72,11 @@ public final class Constants
|
|||||||
public static final String PREFERENCES_KEY_SERVER_URL = "serverUrl";
|
public static final String PREFERENCES_KEY_SERVER_URL = "serverUrl";
|
||||||
public static final String PREFERENCES_KEY_SERVERS_KEY = "serversKey";
|
public static final String PREFERENCES_KEY_SERVERS_KEY = "serversKey";
|
||||||
public static final String PREFERENCES_KEY_ADD_SERVER = "addServer";
|
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_ACTIVE_SERVERS = "activeServers";
|
||||||
public static final String PREFERENCES_KEY_MUSIC_FOLDER_ID = "musicFolderId";
|
public static final String PREFERENCES_KEY_MUSIC_FOLDER_ID = "musicFolderId";
|
||||||
public static final String PREFERENCES_KEY_USERNAME = "username";
|
public static final String PREFERENCES_KEY_USERNAME = "username";
|
||||||
public static final String PREFERENCES_KEY_PASSWORD = "password";
|
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_INSTALL_TIME = "installTime";
|
||||||
public static final String PREFERENCES_KEY_THEME = "theme";
|
public static final String PREFERENCES_KEY_THEME = "theme";
|
||||||
public static final String PREFERENCES_KEY_DISPLAY_BITRATE_WITH_ARTIST = "displayBitrateWithArtist";
|
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_GAPLESS_PLAYBACK = "gaplessPlayback";
|
||||||
public static final String PREFERENCES_KEY_PLAYBACK_CONTROL_SETTINGS = "playbackControlSettings";
|
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_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_DOWNLOAD_TRANSITION = "transitionToDownloadOnPlay";
|
||||||
public static final String PREFERENCES_KEY_INCREMENT_TIME = "incrementTime";
|
public static final String PREFERENCES_KEY_INCREMENT_TIME = "incrementTime";
|
||||||
public static final String PREFERENCES_KEY_ID3_TAGS = "useId3Tags";
|
public static final String PREFERENCES_KEY_ID3_TAGS = "useId3Tags";
|
||||||
|
@ -300,6 +300,7 @@
|
|||||||
<string name="settings.theme_dark">Oscuro</string>
|
<string name="settings.theme_dark">Oscuro</string>
|
||||||
<string name="settings.theme_light">Claro</string>
|
<string name="settings.theme_light">Claro</string>
|
||||||
<string name="settings.theme_title">Tema</string>
|
<string name="settings.theme_title">Tema</string>
|
||||||
|
<string name="settings.title.allow_self_signed_certificate">Permir certificado HTTPS autofirmado</string>
|
||||||
<string name="settings.use_folder_for_album_artist">Usar carpetas para el nombre del artista</string>
|
<string name="settings.use_folder_for_album_artist">Usar carpetas para el nombre del artista</string>
|
||||||
<string name="settings.use_folder_for_album_artist_summary">Se asume que la carpeta en el nivel mal alto es el nombre del artista del álbum</string>
|
<string name="settings.use_folder_for_album_artist_summary">Se asume que la carpeta en el nivel mal alto es el nombre del artista del álbum</string>
|
||||||
<string name="settings.use_id3">Navegar usando las etiquetas ID3</string>
|
<string name="settings.use_id3">Navegar usando las etiquetas ID3</string>
|
||||||
|
@ -300,6 +300,7 @@
|
|||||||
<string name="settings.theme_dark">Sombre</string>
|
<string name="settings.theme_dark">Sombre</string>
|
||||||
<string name="settings.theme_light">Clair</string>
|
<string name="settings.theme_light">Clair</string>
|
||||||
<string name="settings.theme_title">Thème</string>
|
<string name="settings.theme_title">Thème</string>
|
||||||
|
<string name="settings.title.allow_self_signed_certificate">Autoriser le certificat HTTPS auto-signé</string>
|
||||||
<string name="settings.use_folder_for_album_artist">Utilisez des dossiers pour les noms d\'artistes</string>
|
<string name="settings.use_folder_for_album_artist">Utilisez des dossiers pour les noms d\'artistes</string>
|
||||||
<string name="settings.use_folder_for_album_artist_summary">Dossier de niveau supérieur devient le nom de l\'artiste de l\'album</string>
|
<string name="settings.use_folder_for_album_artist_summary">Dossier de niveau supérieur devient le nom de l\'artiste de l\'album</string>
|
||||||
<string name="settings.use_id3">Naviguer en utilisant ID3 Tags</string>
|
<string name="settings.use_id3">Naviguer en utilisant ID3 Tags</string>
|
||||||
|
@ -300,6 +300,7 @@
|
|||||||
<string name="settings.theme_dark">Sötét</string>
|
<string name="settings.theme_dark">Sötét</string>
|
||||||
<string name="settings.theme_light">Világos</string>
|
<string name="settings.theme_light">Világos</string>
|
||||||
<string name="settings.theme_title">Téma</string>
|
<string name="settings.theme_title">Téma</string>
|
||||||
|
<string name="settings.title.allow_self_signed_certificate">Engedélyezze az önaláírt HTTPS tanúsítványt</string>
|
||||||
<string name="settings.use_folder_for_album_artist">Mappanevek használata az előadók neveként</string>
|
<string name="settings.use_folder_for_album_artist">Mappanevek használata az előadók neveként</string>
|
||||||
<string name="settings.use_folder_for_album_artist_summary">Feltételezi, hogy a legfelső szintű mappa az előadó neve.</string>
|
<string name="settings.use_folder_for_album_artist_summary">Feltételezi, hogy a legfelső szintű mappa az előadó neve.</string>
|
||||||
<string name="settings.use_id3">Böngészés ID3 Tag használatával</string>
|
<string name="settings.use_id3">Böngészés ID3 Tag használatával</string>
|
||||||
|
@ -303,6 +303,7 @@
|
|||||||
<string name="settings.theme_dark">Escuro</string>
|
<string name="settings.theme_dark">Escuro</string>
|
||||||
<string name="settings.theme_light">Claro</string>
|
<string name="settings.theme_light">Claro</string>
|
||||||
<string name="settings.theme_title">Tema</string>
|
<string name="settings.theme_title">Tema</string>
|
||||||
|
<string name="settings.title.allow_self_signed_certificate">Permitir o certificado HTTPS auto-assinado</string>
|
||||||
<string name="settings.use_folder_for_album_artist">Pasta para Nome do Artista</string>
|
<string name="settings.use_folder_for_album_artist">Pasta para Nome do Artista</string>
|
||||||
<string name="settings.use_folder_for_album_artist_summary">Assume que a pasta mais acima é o nome do artista</string>
|
<string name="settings.use_folder_for_album_artist_summary">Assume que a pasta mais acima é o nome do artista</string>
|
||||||
<string name="settings.use_id3">Navegar Usando Etiquetas ID3</string>
|
<string name="settings.use_id3">Navegar Usando Etiquetas ID3</string>
|
||||||
|
@ -303,6 +303,7 @@
|
|||||||
<string name="settings.theme_dark">Escuro</string>
|
<string name="settings.theme_dark">Escuro</string>
|
||||||
<string name="settings.theme_light">Claro</string>
|
<string name="settings.theme_light">Claro</string>
|
||||||
<string name="settings.theme_title">Tema</string>
|
<string name="settings.theme_title">Tema</string>
|
||||||
|
<string name="settings.title.allow_self_signed_certificate">Permitir o certificado HTTPS auto-assinado</string>
|
||||||
<string name="settings.use_folder_for_album_artist">Pasta para Nome do Artista</string>
|
<string name="settings.use_folder_for_album_artist">Pasta para Nome do Artista</string>
|
||||||
<string name="settings.use_folder_for_album_artist_summary">Assume que a pasta mais acima é o nome do artista</string>
|
<string name="settings.use_folder_for_album_artist_summary">Assume que a pasta mais acima é o nome do artista</string>
|
||||||
<string name="settings.use_id3">Navegar Usando Etiquetas ID3</string>
|
<string name="settings.use_id3">Navegar Usando Etiquetas ID3</string>
|
||||||
|
@ -146,6 +146,7 @@
|
|||||||
<string name="select_genre.empty">No genres found</string>
|
<string name="select_genre.empty">No genres found</string>
|
||||||
<string name="select_playlist.empty">No saved playlists on server</string>
|
<string name="select_playlist.empty">No saved playlists on server</string>
|
||||||
<string name="service.connecting">Contacting server, please wait.</string>
|
<string name="service.connecting">Contacting server, please wait.</string>
|
||||||
|
<string name="settings.allow_self_signed_certificate" translatable="false">allowSelfSignedCertificate</string>
|
||||||
<string name="settings.appearance_title">Appearance</string>
|
<string name="settings.appearance_title">Appearance</string>
|
||||||
<string name="settings.buffer_length">Buffer Length</string>
|
<string name="settings.buffer_length">Buffer Length</string>
|
||||||
<string name="settings.buffer_length_0">Disabled</string>
|
<string name="settings.buffer_length_0">Disabled</string>
|
||||||
@ -303,6 +304,7 @@
|
|||||||
<string name="settings.theme_dark">Dark</string>
|
<string name="settings.theme_dark">Dark</string>
|
||||||
<string name="settings.theme_light">Light</string>
|
<string name="settings.theme_light">Light</string>
|
||||||
<string name="settings.theme_title">Theme</string>
|
<string name="settings.theme_title">Theme</string>
|
||||||
|
<string name="settings.title.allow_self_signed_certificate">Allow self-signed HTTPS certificate</string>
|
||||||
<string name="settings.use_folder_for_album_artist">Use Folders For Artist Name</string>
|
<string name="settings.use_folder_for_album_artist">Use Folders For Artist Name</string>
|
||||||
<string name="settings.use_folder_for_album_artist_summary">Assume top-level folder is the name of the album artist</string>
|
<string name="settings.use_folder_for_album_artist_summary">Assume top-level folder is the name of the album artist</string>
|
||||||
<string name="settings.use_id3">Browse Using ID3 Tags</string>
|
<string name="settings.use_id3">Browse Using ID3 Tags</string>
|
||||||
|
@ -38,6 +38,12 @@
|
|||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:title="@string/equalizer.enabled"
|
android:title="@string/equalizer.enabled"
|
||||||
/>
|
/>
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="@string/settings.allow_self_signed_certificate"
|
||||||
|
android:persistent="false"
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:title="@string/settings.title.allow_self_signed_certificate"
|
||||||
|
/>
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="@string/jukebox.is_default"
|
android:key="@string/jukebox.is_default"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
#Tue Dec 26 22:52:35 CET 2017
|
#Tue Dec 26 22:52:35 CET 2017
|
||||||
AI_VERSION_CODE=59
|
AI_VERSION_CODE=60
|
||||||
|
Loading…
x
Reference in New Issue
Block a user