Create preferences

This commit is contained in:
2020-05-09 14:48:39 +02:00
parent bb0a2a94ed
commit 0850390635
13 changed files with 177 additions and 51 deletions

View File

@@ -33,6 +33,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'org.nanohttpd:nanohttpd:2.2.0' implementation 'org.nanohttpd:nanohttpd:2.2.0'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
implementation 'androidx.preference:preference:1.1.0-rc01'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

View File

@@ -13,6 +13,10 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity
android:name=".preference.PreferencesActivity"
android:label="@string/title_activity_preferences"
android:parentActivityName=".MainActivity"/>
<service <service
android:name=".service.ForegroundService" android:name=".service.ForegroundService"

View File

@@ -3,10 +3,13 @@ package io.bartek
import android.content.* import android.content.*
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Button import android.widget.Button
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import io.bartek.preference.PreferencesActivity
import io.bartek.service.ForegroundService import io.bartek.service.ForegroundService
import io.bartek.service.ServiceState import io.bartek.service.ServiceState
@@ -24,6 +27,19 @@ class MainActivity : AppCompatActivity() {
} }
} }
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId) {
R.id.open_preferences -> startActivity(Intent(this, PreferencesActivity::class.java))
}
return super.onOptionsItemSelected(item)
}
private fun updateViewAccordingToServiceState(newState: ServiceState) { private fun updateViewAccordingToServiceState(newState: ServiceState) {
controlServerButton.isEnabled = true controlServerButton.isEnabled = true
when (newState) { when (newState) {
@@ -53,8 +69,6 @@ class MainActivity : AppCompatActivity() {
super.onPause() super.onPause()
} }
fun openTTSSettings(view: View) = startActivity(Intent("com.android.settings.TTS_SETTINGS"))
fun controlServer(view: View) { fun controlServer(view: View) {
controlServerButton.isEnabled = false controlServerButton.isEnabled = false
when (ForegroundService.state) { when (ForegroundService.state) {

View File

@@ -0,0 +1,19 @@
package io.bartek.preference
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat
import io.bartek.R
class PreferencesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_preferences)
supportFragmentManager
.beginTransaction()
.replace(R.id.preferences, PreferencesFragment())
.commit()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
}

View File

@@ -0,0 +1,28 @@
package io.bartek.preference
import android.content.Intent
import android.os.Bundle
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import io.bartek.R
class PreferencesFragment : PreferenceFragmentCompat() {
private lateinit var portPreference: EditTextPreference
private lateinit var sayEndpointPreference: SwitchPreference
private lateinit var waveEndpointPreference: SwitchPreference
private lateinit var ttsEnginePreference: Preference
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
portPreference = findPreference("preference_port")!!
sayEndpointPreference = findPreference("preference_enable_say_endpoint")!!
waveEndpointPreference = findPreference("preference_enable_wave_endpoint")!!
ttsEnginePreference = findPreference("preference_tts")!!
ttsEnginePreference.setOnPreferenceClickListener {
startActivity(Intent("com.android.settings.TTS_SETTINGS"))
true
}
}
}

View File

@@ -3,24 +3,30 @@ package io.bartek.service
import android.app.* import android.app.*
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Color import android.graphics.Color
import android.os.Binder import android.os.Binder
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import androidx.preference.PreferenceManager
import io.bartek.MainActivity import io.bartek.MainActivity
import io.bartek.R import io.bartek.R
import io.bartek.web.TTSServer import io.bartek.web.TTSServer
import java.lang.Integer.parseInt
class ForegroundService : Service() { class ForegroundService : Service() {
private var port: Int = 8080 private lateinit var preferences: SharedPreferences
private var wakeLock: PowerManager.WakeLock? = null private var wakeLock: PowerManager.WakeLock? = null
private var isServiceStarted = false private var isServiceStarted = false
private var ttsServer: TTSServer? = null private var ttsServer: TTSServer? = null
private val port: Int
get() = parseInt(preferences.getString("preference_port", "8080")!!)
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
preferences = PreferenceManager.getDefaultSharedPreferences(this)
startForeground(1, createNotification()) startForeground(1, createNotification())
} }
@@ -68,10 +74,7 @@ class ForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.let { intent?.let {
when(it.action) { when(it.action) {
START -> { START -> startService()
port = it.getIntExtra(PORT, port)
startService()
}
STOP -> stopService() STOP -> stopService()
} }
} }

View File

@@ -3,11 +3,10 @@ package io.bartek.web
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.speech.tts.TextToSpeech import android.speech.tts.TextToSpeech
import android.widget.Toast
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import fi.iki.elonen.NanoHTTPD import fi.iki.elonen.NanoHTTPD
import fi.iki.elonen.NanoHTTPD.Response.Status.* import fi.iki.elonen.NanoHTTPD.Response.Status.*
import io.bartek.R
import io.bartek.service.ServiceState import io.bartek.service.ServiceState
import io.bartek.tts.TTS import io.bartek.tts.TTS
import org.json.JSONObject import org.json.JSONObject
@@ -18,11 +17,19 @@ private data class TTSRequestData(val text: String, val language: Locale)
class TTSServer(port: Int, private val context: Context) : NanoHTTPD(port), class TTSServer(port: Int, private val context: Context) : NanoHTTPD(port),
TextToSpeech.OnInitListener { TextToSpeech.OnInitListener {
private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
private val tts = TTS(context, this) private val tts = TTS(context, this)
override fun serve(session: IHTTPSession?): Response { override fun serve(session: IHTTPSession?): Response {
try { try {
return tryToServe(session) session?.let {
return when(it.uri) {
"/wave" -> wave(it)
else -> throw ResponseException(NOT_FOUND, "")
}
}
throw ResponseException(BAD_REQUEST, "")
} catch (e: ResponseException) { } catch (e: ResponseException) {
throw e throw e
} catch (e: Exception) { } catch (e: Exception) {
@@ -30,18 +37,8 @@ class TTSServer(port: Int, private val context: Context) : NanoHTTPD(port),
} }
} }
private fun tryToServe(session: IHTTPSession?): Response { private fun wave(session: IHTTPSession): Response {
val (text, language) = getRequestData(validateRequest(session)) if(!preferences.getBoolean("preference_enable_wave_endpoint", true)) {
val (stream, size) = tts.performTTS(text, language)
return newFixedLengthResponse(OK, "audio/x-wav", stream, size)
}
private fun validateRequest(session: IHTTPSession?): IHTTPSession {
if (session == null) {
throw ResponseException(BAD_REQUEST, "")
}
if (session.uri != "/") {
throw ResponseException(NOT_FOUND, "") throw ResponseException(NOT_FOUND, "")
} }
@@ -53,8 +50,9 @@ class TTSServer(port: Int, private val context: Context) : NanoHTTPD(port),
throw ResponseException(BAD_REQUEST, "") throw ResponseException(BAD_REQUEST, "")
} }
val (text, language) = getRequestData(session)
return session val (stream, size) = tts.performTTS(text, language)
return newFixedLengthResponse(OK, "audio/x-wav", stream, size)
} }
private fun getRequestData(session: IHTTPSession): TTSRequestData { private fun getRequestData(session: IHTTPSession): TTSRequestData {

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z"/>
</vector>

View File

@@ -1,30 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".MainActivity" android:orientation="vertical"
android:orientation="vertical"> tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/control_server_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:onClick="controlServer"
android:text="@string/main_activity_run" />
</LinearLayout>
<Button <Button
android:id="@+id/ttsSettings" android:id="@+id/control_server_button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:onClick="openTTSSettings" android:layout_weight="2"
android:text="@string/main_activity_tts_settings" /> android:onClick="controlServer"
</LinearLayout> android:text="@string/main_activity_run" />
</LinearLayout>

View File

@@ -0,0 +1,9 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/preferences"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/open_preferences" android:icon="@drawable/ic_settings" app:showAsAction="always" android:title="Preferences" />
</menu>

View File

@@ -1,12 +1,23 @@
<resources> <resources>
<string name="app_name">TTS Server</string> <string name="app_name">TTS Server</string>
<string name="main_activity_run">Run</string>
<string name="main_activity_stop">Stop</string>
<string name="service_notification_category_name">HTTP Server</string> <string name="service_notification_category_name">HTTP Server</string>
<string name="service_notification_category_description">The fixed notification keeping the HTTP server alive</string> <string name="service_notification_category_description">The fixed notification keeping the HTTP server alive</string>
<string name="service_notification_title">Server is running</string> <string name="service_notification_title">Server is running</string>
<string name="service_notification_text">The HTTP server is listening on port %1$d</string> <string name="service_notification_text">The HTTP server is listening on port %1$d</string>
<string name="main_activity_server_port">Server port</string> <string name="title_activity_preferences">Settings</string>
<string name="main_activity_run">Run</string> <string name="preference_port_summary">The desired port on which HTTP server is intended to listening</string>
<string name="main_activity_stop">Stop</string> <string name="preference_port_title">Server port</string>
<string name="main_activity_tts_settings">TTS Settings</string> <string name="preference_enable_say_endpoint_summary">Allow HTTP clients to use /say endpoint which enables them to say message using builtin speakers or external ones connected to the device</string>
<string name="preference_enable_say_endpoint_title">Enable /say endpoint</string>
<string name="preference_enable_wave_endpoint_summary">Allow HTTP clients to use /wave endpoint which enables them to retrieve TTS message as wave file</string>
<string name="preference_enable_wave_endpoint_title">Enable /wave endpoint</string>
<string name="preference_tts_summary">Go to platform\'s TTS engine settings and adjust its parameters</string>
<string name="preference_tts_title">TTS engine settings</string>
<string name="preference_category_tts">TTS engine</string>
<string name="preference_category_server">Server</string>
</resources> </resources>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory android:title="@string/preference_category_server" app:iconSpaceReserved="false">
<EditTextPreference
android:defaultValue="8080"
android:inputType="number"
android:key="preference_port"
android:summary="@string/preference_port_summary"
android:title="@string/preference_port_title"
app:iconSpaceReserved="false" />
<SwitchPreference
android:defaultValue="true"
android:key="preference_enable_say_endpoint"
android:summary="@string/preference_enable_say_endpoint_summary"
android:title="@string/preference_enable_say_endpoint_title"
app:iconSpaceReserved="false" />
<SwitchPreference
android:defaultValue="true"
android:key="preference_enable_wave_endpoint"
android:summary="@string/preference_enable_wave_endpoint_summary"
android:title="@string/preference_enable_wave_endpoint_title"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/preference_category_tts" app:iconSpaceReserved="false">
<Preference
android:key="preference_tts"
android:summary="@string/preference_tts_summary"
android:title="@string/preference_tts_title"
app:iconSpaceReserved="false" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>