1 parent
15ab99c740
commit
2b89bbd959
6 files changed
+76
-150
No files matched your search
@@ -1,36 +1,23 @@
|
|||||||
package io.github.transui.android;
|
package io.github.transui.android;
|
||||||
|
|
||||||
final class AppConfig {
|
final class AppConfig {
|
||||||
final String remoteEndpoint;
|
final String endpoint;
|
||||||
final String remoteModel;
|
final String model;
|
||||||
final String remoteKey;
|
final String apiKey;
|
||||||
final String localEndpoint;
|
|
||||||
final String localModel;
|
|
||||||
final String localKey;
|
|
||||||
final String prompt;
|
final String prompt;
|
||||||
final boolean remoteFirst;
|
|
||||||
|
|
||||||
AppConfig(
|
AppConfig(
|
||||||
String remoteEndpoint,
|
String endpoint,
|
||||||
String remoteModel,
|
String model,
|
||||||
String remoteKey,
|
String apiKey,
|
||||||
String localEndpoint,
|
String prompt) {
|
||||||
String localModel,
|
this.endpoint = endpoint;
|
||||||
String localKey,
|
this.model = model;
|
||||||
String prompt,
|
this.apiKey = apiKey;
|
||||||
boolean remoteFirst) {
|
|
||||||
this.remoteEndpoint = remoteEndpoint;
|
|
||||||
this.remoteModel = remoteModel;
|
|
||||||
this.remoteKey = remoteKey;
|
|
||||||
this.localEndpoint = localEndpoint;
|
|
||||||
this.localModel = localModel;
|
|
||||||
this.localKey = localKey;
|
|
||||||
this.prompt = prompt;
|
this.prompt = prompt;
|
||||||
this.remoteFirst = remoteFirst;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasUsableEndpoint() {
|
boolean hasUsableEndpoint() {
|
||||||
if (remoteFirst && !remoteEndpoint.isEmpty()) return true;
|
return !endpoint.isEmpty();
|
||||||
return !localEndpoint.isEmpty();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,34 +12,38 @@ final class ConfigRepository {
|
|||||||
|
|
||||||
ConfigRepository(Context context) {
|
ConfigRepository(Context context) {
|
||||||
preferences = context.getApplicationContext().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
preferences = context.getApplicationContext().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||||
|
removeObsoleteLocalSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig load() {
|
AppConfig load() {
|
||||||
String remoteKey = decryptOrEmpty(preferences.getString("remote_key", ""));
|
|
||||||
String localKey = decryptOrEmpty(preferences.getString("local_key", ""));
|
|
||||||
return new AppConfig(
|
return new AppConfig(
|
||||||
preferences.getString("remote_endpoint", ""),
|
preferences.getString("remote_endpoint", ""),
|
||||||
preferences.getString("remote_model", ""),
|
preferences.getString("remote_model", ""),
|
||||||
remoteKey,
|
decryptOrEmpty(preferences.getString("remote_key", "")),
|
||||||
preferences.getString("local_endpoint", "http://localhost:8080"),
|
preferences.getString("prompt", ""));
|
||||||
preferences.getString("local_model", ""),
|
|
||||||
localKey,
|
|
||||||
preferences.getString("prompt", ""),
|
|
||||||
preferences.getBoolean("remote_first", true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void save(AppConfig config) throws GeneralSecurityException, IOException {
|
void save(AppConfig config) throws GeneralSecurityException, IOException {
|
||||||
String remoteKey = SecretStore.encrypt(config.remoteKey);
|
|
||||||
String localKey = SecretStore.encrypt(config.localKey);
|
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putString("remote_endpoint", config.remoteEndpoint)
|
.putString("remote_endpoint", config.endpoint)
|
||||||
.putString("remote_model", config.remoteModel)
|
.putString("remote_model", config.model)
|
||||||
.putString("remote_key", remoteKey)
|
.putString("remote_key", SecretStore.encrypt(config.apiKey))
|
||||||
.putString("local_endpoint", config.localEndpoint)
|
|
||||||
.putString("local_model", config.localModel)
|
|
||||||
.putString("local_key", localKey)
|
|
||||||
.putString("prompt", config.prompt)
|
.putString("prompt", config.prompt)
|
||||||
.putBoolean("remote_first", config.remoteFirst)
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeObsoleteLocalSettings() {
|
||||||
|
if (!preferences.contains("local_endpoint")
|
||||||
|
&& !preferences.contains("local_model")
|
||||||
|
&& !preferences.contains("local_key")
|
||||||
|
&& !preferences.contains("remote_first")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
preferences.edit()
|
||||||
|
.remove("local_endpoint")
|
||||||
|
.remove("local_model")
|
||||||
|
.remove("local_key")
|
||||||
|
.remove("remote_first")
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import android.os.Bundle;
|
|||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Switch;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -20,14 +19,12 @@ import java.security.GeneralSecurityException;
|
|||||||
public final class MainActivity extends Activity {
|
public final class MainActivity extends Activity {
|
||||||
private static final int MICROPHONE_REQUEST = 1;
|
private static final int MICROPHONE_REQUEST = 1;
|
||||||
|
|
||||||
private EditText remoteEndpoint;
|
private static final String ACTION_LANGUAGE_SETTINGS = "android.settings.LANGUAGE_SETTINGS";
|
||||||
private EditText remoteModel;
|
|
||||||
private EditText remoteKey;
|
private EditText endpoint;
|
||||||
private EditText localEndpoint;
|
private EditText model;
|
||||||
private EditText localModel;
|
private EditText apiKey;
|
||||||
private EditText localKey;
|
|
||||||
private EditText prompt;
|
private EditText prompt;
|
||||||
private Switch remoteFirst;
|
|
||||||
private TextView providerStatus;
|
private TextView providerStatus;
|
||||||
private Button microphone;
|
private Button microphone;
|
||||||
private ConfigRepository repository;
|
private ConfigRepository repository;
|
||||||
@@ -42,9 +39,7 @@ public final class MainActivity extends Activity {
|
|||||||
|
|
||||||
findViewById(R.id.save).setOnClickListener(view -> saveConfig());
|
findViewById(R.id.save).setOnClickListener(view -> saveConfig());
|
||||||
microphone.setOnClickListener(view -> requestMicrophone());
|
microphone.setOnClickListener(view -> requestMicrophone());
|
||||||
findViewById(R.id.choose_provider).setOnClickListener(view -> {
|
findViewById(R.id.language_settings).setOnClickListener(view -> openLanguageSettings());
|
||||||
if (saveConfig()) openVoiceInputSettings();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -60,39 +55,27 @@ public final class MainActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void bindViews() {
|
private void bindViews() {
|
||||||
remoteEndpoint = findViewById(R.id.remote_endpoint);
|
endpoint = findViewById(R.id.endpoint);
|
||||||
remoteModel = findViewById(R.id.remote_model);
|
model = findViewById(R.id.model);
|
||||||
remoteKey = findViewById(R.id.remote_key);
|
apiKey = findViewById(R.id.api_key);
|
||||||
localEndpoint = findViewById(R.id.local_endpoint);
|
|
||||||
localModel = findViewById(R.id.local_model);
|
|
||||||
localKey = findViewById(R.id.local_key);
|
|
||||||
prompt = findViewById(R.id.prompt);
|
prompt = findViewById(R.id.prompt);
|
||||||
remoteFirst = findViewById(R.id.remote_first);
|
|
||||||
providerStatus = findViewById(R.id.provider_status);
|
providerStatus = findViewById(R.id.provider_status);
|
||||||
microphone = findViewById(R.id.microphone);
|
microphone = findViewById(R.id.microphone);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showConfig(AppConfig config) {
|
private void showConfig(AppConfig config) {
|
||||||
remoteEndpoint.setText(config.remoteEndpoint);
|
endpoint.setText(config.endpoint);
|
||||||
remoteModel.setText(config.remoteModel);
|
model.setText(config.model);
|
||||||
remoteKey.setText(config.remoteKey);
|
apiKey.setText(config.apiKey);
|
||||||
localEndpoint.setText(config.localEndpoint);
|
|
||||||
localModel.setText(config.localModel);
|
|
||||||
localKey.setText(config.localKey);
|
|
||||||
prompt.setText(config.prompt);
|
prompt.setText(config.prompt);
|
||||||
remoteFirst.setChecked(config.remoteFirst);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean saveConfig() {
|
private boolean saveConfig() {
|
||||||
AppConfig config = new AppConfig(
|
AppConfig config = new AppConfig(
|
||||||
value(remoteEndpoint).trim(),
|
value(endpoint).trim(),
|
||||||
value(remoteModel).trim(),
|
value(model).trim(),
|
||||||
value(remoteKey).trim(),
|
value(apiKey).trim(),
|
||||||
value(localEndpoint).trim(),
|
value(prompt));
|
||||||
value(localModel).trim(),
|
|
||||||
value(localKey).trim(),
|
|
||||||
value(prompt),
|
|
||||||
remoteFirst.isChecked());
|
|
||||||
try {
|
try {
|
||||||
repository.save(config);
|
repository.save(config);
|
||||||
Toast.makeText(this, R.string.settings_saved, Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, R.string.settings_saved, Toast.LENGTH_SHORT).show();
|
||||||
@@ -109,11 +92,17 @@ public final class MainActivity extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openVoiceInputSettings() {
|
private void openLanguageSettings() {
|
||||||
try {
|
try {
|
||||||
startActivity(new Intent(Settings.ACTION_VOICE_INPUT_SETTINGS));
|
startActivity(new Intent(ACTION_LANGUAGE_SETTINGS));
|
||||||
} catch (ActivityNotFoundException error) {
|
return;
|
||||||
Toast.makeText(this, R.string.voice_settings_unavailable, Toast.LENGTH_LONG).show();
|
} catch (ActivityNotFoundException ignored) {
|
||||||
|
// Older Android builds expose the combined Languages and input page here.
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS));
|
||||||
|
} catch (ActivityNotFoundException ignored) {
|
||||||
|
Toast.makeText(this, R.string.language_settings_unavailable, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,25 +20,10 @@ final class TranscriptionClient {
|
|||||||
private volatile HttpURLConnection activeConnection;
|
private volatile HttpURLConnection activeConnection;
|
||||||
|
|
||||||
Result transcribe(AppConfig config, byte[] wav) {
|
Result transcribe(AppConfig config, byte[] wav) {
|
||||||
Result remoteResult = null;
|
if (config.endpoint.isEmpty()) {
|
||||||
if (config.remoteFirst && !config.remoteEndpoint.isEmpty()) {
|
return Result.error("No transcription endpoint is configured", SpeechRecognizer.ERROR_CLIENT);
|
||||||
remoteResult = request(
|
|
||||||
config.remoteEndpoint, config.remoteModel, config.remoteKey, config.prompt, wav);
|
|
||||||
if (remoteResult.text != null) return remoteResult;
|
|
||||||
}
|
}
|
||||||
|
return request(config.endpoint, config.model, config.apiKey, config.prompt, wav);
|
||||||
if (!config.localEndpoint.isEmpty()) {
|
|
||||||
Result localResult = request(
|
|
||||||
config.localEndpoint, config.localModel, config.localKey, config.prompt, wav);
|
|
||||||
if (localResult.text != null || remoteResult == null) return localResult;
|
|
||||||
return Result.error(
|
|
||||||
remoteResult.error + "; local: " + localResult.error,
|
|
||||||
localResult.errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return remoteResult != null
|
|
||||||
? remoteResult
|
|
||||||
: Result.error("No transcription endpoint is configured", SpeechRecognizer.ERROR_CLIENT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void cancel() {
|
void cancel() {
|
||||||
|
|||||||
@@ -48,51 +48,26 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/SectionLabel"
|
style="@style/SectionLabel"
|
||||||
android:text="@string/remote" />
|
android:text="@string/server" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/remote_endpoint"
|
android:id="@+id/endpoint"
|
||||||
style="@style/Field"
|
style="@style/Field"
|
||||||
android:hint="@string/remote_endpoint"
|
android:hint="@string/endpoint"
|
||||||
android:importantForAutofill="no"
|
android:importantForAutofill="no"
|
||||||
android:inputType="textUri" />
|
android:inputType="textUri" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/remote_model"
|
android:id="@+id/model"
|
||||||
style="@style/Field"
|
style="@style/Field"
|
||||||
android:hint="@string/remote_model"
|
android:hint="@string/model"
|
||||||
android:importantForAutofill="no"
|
android:importantForAutofill="no"
|
||||||
android:inputType="text" />
|
android:inputType="text" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/remote_key"
|
android:id="@+id/api_key"
|
||||||
style="@style/Field"
|
style="@style/Field"
|
||||||
android:hint="@string/remote_api_key"
|
android:hint="@string/api_key"
|
||||||
android:importantForAutofill="no"
|
|
||||||
android:inputType="textPassword" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/SectionLabel"
|
|
||||||
android:text="@string/local" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/local_endpoint"
|
|
||||||
style="@style/Field"
|
|
||||||
android:hint="@string/local_endpoint"
|
|
||||||
android:importantForAutofill="no"
|
|
||||||
android:inputType="textUri" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/local_model"
|
|
||||||
style="@style/Field"
|
|
||||||
android:hint="@string/local_model"
|
|
||||||
android:importantForAutofill="no"
|
|
||||||
android:inputType="text" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/local_key"
|
|
||||||
style="@style/Field"
|
|
||||||
android:hint="@string/local_api_key"
|
|
||||||
android:importantForAutofill="no"
|
android:importantForAutofill="no"
|
||||||
android:inputType="textPassword" />
|
android:inputType="textPassword" />
|
||||||
|
|
||||||
@@ -109,15 +84,6 @@
|
|||||||
android:maxLines="3"
|
android:maxLines="3"
|
||||||
android:singleLine="false" />
|
android:singleLine="false" />
|
||||||
|
|
||||||
<Switch
|
|
||||||
android:id="@+id/remote_first"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="14dp"
|
|
||||||
android:checked="true"
|
|
||||||
android:text="@string/remote_first"
|
|
||||||
android:textColor="@color/ink" />
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/save"
|
android:id="@+id/save"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -133,10 +99,10 @@
|
|||||||
android:text="@string/allow_microphone" />
|
android:text="@string/allow_microphone" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/choose_provider"
|
android:id="@+id/language_settings"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:text="@string/choose_provider" />
|
android:text="@string/open_language_settings" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -5,22 +5,17 @@
|
|||||||
<string name="title">Transui voice input</string>
|
<string name="title">Transui voice input</string>
|
||||||
<string name="status_selected">Selected as voice input</string>
|
<string name="status_selected">Selected as voice input</string>
|
||||||
<string name="status_not_selected">Select Transui as voice input</string>
|
<string name="status_not_selected">Select Transui as voice input</string>
|
||||||
<string name="remote">REMOTE</string>
|
<string name="server">SERVER</string>
|
||||||
<string name="local">LOCAL</string>
|
|
||||||
<string name="request">REQUEST</string>
|
<string name="request">REQUEST</string>
|
||||||
<string name="remote_endpoint">Remote endpoint</string>
|
<string name="endpoint">Transcription endpoint</string>
|
||||||
<string name="remote_model">Remote model</string>
|
<string name="model">Model</string>
|
||||||
<string name="remote_api_key">Remote API key</string>
|
<string name="api_key">API key</string>
|
||||||
<string name="local_endpoint">Local endpoint</string>
|
|
||||||
<string name="local_model">Local model</string>
|
|
||||||
<string name="local_api_key">Local API key</string>
|
|
||||||
<string name="prompt">Prompt</string>
|
<string name="prompt">Prompt</string>
|
||||||
<string name="remote_first">Remote first, local fallback</string>
|
|
||||||
<string name="save">Save settings</string>
|
<string name="save">Save settings</string>
|
||||||
<string name="choose_provider">Choose voice input</string>
|
<string name="open_language_settings">Open language settings</string>
|
||||||
<string name="allow_microphone">Allow microphone</string>
|
<string name="allow_microphone">Allow microphone</string>
|
||||||
<string name="microphone_allowed">Microphone allowed</string>
|
<string name="microphone_allowed">Microphone allowed</string>
|
||||||
<string name="settings_saved">Settings saved</string>
|
<string name="settings_saved">Settings saved</string>
|
||||||
<string name="settings_save_failed">Could not save API keys</string>
|
<string name="settings_save_failed">Could not save API keys</string>
|
||||||
<string name="voice_settings_unavailable">Voice input settings are unavailable</string>
|
<string name="language_settings_unavailable">Language settings are unavailable</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in new issue
Block a user