mirror of
https://github.com/andreytkachenko/LocalSTT.git
synced 2024-11-22 09:26:23 +04:00
Added SpeechActivity to handle incoming RecognitionIntents
This commit is contained in:
parent
920f3468b2
commit
c3755cde41
@ -8,8 +8,82 @@
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@drawable/ic_service_trigger"
|
||||
android:label="@string/app_name" >
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="standard"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:name=".SpeechActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.speech.action.RECOGNIZE_SPEECH" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.speech.action.WEB_SEARCH" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- input/output: Nothing -->
|
||||
<!-- Samsung Galaxy SII launches VOICE_COMMAND when HOME is double-clicked -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VOICE_COMMAND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
Action ASSIST (API 16)
|
||||
Input: EXTRA_ASSIST_PACKAGE, EXTRA_ASSIST_CONTEXT. Output: nothing.
|
||||
|
||||
* "Search assistant" on CM10.2, which can be mapped to various buttons.
|
||||
* Long press on the HOME button on Nexus 5X.
|
||||
* Upper physical button on Huawei Watch 2.
|
||||
|
||||
The default ASSIST app is user-configurable on the phone but not on Wear,
|
||||
i.e. on the phone the user can choose which app is started, e.g. when long-pressing
|
||||
on the HOME button, and the filter priority plays no role. On Wear the choice
|
||||
is based only on the priority.
|
||||
We set it to lower than default to let the other guy win. This is probably
|
||||
specific to Huawei Watch 2 with its weird setup,
|
||||
where the upper button launches ASSIST (and this cannot be
|
||||
changed) and the lower button can open any app (other than Google Assist).
|
||||
-->
|
||||
<intent-filter android:priority="-10">
|
||||
<action android:name="android.intent.action.ASSIST" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<!-- input/output: Nothing -->
|
||||
<!-- API 3 -->
|
||||
<!-- "Voice search" on CM10.2, which can be mapped to various buttons -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH_LONG_PRESS" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- input/output: Nothing -->
|
||||
<intent-filter>
|
||||
<action android:name="android.speech.action.VOICE_SEARCH_HANDS_FREE" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<!-- TODO: future work
|
||||
<intent-filter>
|
||||
<action android:name="android.provider.MediaStore.RECORD_SOUND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
-->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".VoskRecognitionService"
|
||||
android:icon="@drawable/ic_service_trigger"
|
||||
|
248
app/src/main/java/cat/oreilly/localstt/SpeechActivity.java
Normal file
248
app/src/main/java/cat/oreilly/localstt/SpeechActivity.java
Normal file
@ -0,0 +1,248 @@
|
||||
package cat.oreilly.localstt;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.os.Message;
|
||||
import android.speech.RecognitionListener;
|
||||
import android.speech.RecognizerIntent;
|
||||
import android.speech.SpeechRecognizer;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.List;
|
||||
|
||||
public class SpeechActivity extends AppCompatActivity {
|
||||
protected static final String TAG = SpeechActivity.class.getSimpleName();
|
||||
|
||||
private static final String MSG = "MSG";
|
||||
private static final int MSG_TOAST = 1;
|
||||
private static final int MSG_RESULT_ERROR = 2;
|
||||
public static final Integer RecordAudioRequestCode = 1;
|
||||
private SpeechRecognizer speechRecognizer;
|
||||
private EditText editText;
|
||||
|
||||
protected static class SimpleMessageHandler extends Handler {
|
||||
private final WeakReference<SpeechActivity> mRef;
|
||||
|
||||
private SimpleMessageHandler(SpeechActivity c) {
|
||||
mRef = new WeakReference<>(c);
|
||||
}
|
||||
|
||||
public void handleMessage(Message msg) {
|
||||
SpeechActivity outerClass = mRef.get();
|
||||
if (outerClass != null) {
|
||||
Bundle b = msg.getData();
|
||||
String msgAsString = b.getString(MSG);
|
||||
switch (msg.what) {
|
||||
case MSG_TOAST:
|
||||
outerClass.toast(msgAsString);
|
||||
break;
|
||||
case MSG_RESULT_ERROR:
|
||||
outerClass.showError(msgAsString);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static Message createMessage(int type, String str) {
|
||||
Bundle b = new Bundle();
|
||||
b.putString(MSG, str);
|
||||
Message msg = Message.obtain();
|
||||
msg.what = type;
|
||||
msg.setData(b);
|
||||
return msg;
|
||||
}
|
||||
|
||||
protected void toast(String message) {
|
||||
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
void showError(String msg) {
|
||||
editText.setText(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.speech_activity);
|
||||
if (ContextCompat.checkSelfPermission(this,
|
||||
Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
|
||||
checkPermission();
|
||||
}
|
||||
|
||||
editText = findViewById(R.id.text);
|
||||
speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
|
||||
|
||||
speechRecognizer.setRecognitionListener(new RecognitionListener() {
|
||||
@Override
|
||||
public void onReadyForSpeech(Bundle bundle) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeginningOfSpeech() {
|
||||
editText.setText("");
|
||||
editText.setHint("Listening...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRmsChanged(float v) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferReceived(byte[] bytes) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEndOfSpeech() {
|
||||
speechRecognizer.stopListening();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int i) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResults(Bundle bundle) {
|
||||
Log.i(TAG, "onResults");
|
||||
ArrayList<String> results = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
|
||||
Log.i(TAG, results.get(0));
|
||||
editText.setText(results.get(0));
|
||||
returnResults(results);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPartialResults(Bundle bundle) {
|
||||
Log.i(TAG, "onPartialResults");
|
||||
ArrayList<String> data = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
|
||||
Log.i(TAG, data.get(0));
|
||||
editText.setText(data.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(int i, Bundle bundle) {
|
||||
Log.d(TAG, bundle.toString());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
Log.i(TAG, "onStart");
|
||||
final Intent speechRecognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
|
||||
speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
|
||||
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
|
||||
speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());
|
||||
speechRecognizer.startListening(speechRecognizerIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
Log.i(TAG, "onDestroy");
|
||||
speechRecognizer.destroy();
|
||||
}
|
||||
|
||||
private void checkPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.RECORD_AUDIO },
|
||||
RecordAudioRequestCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||
@NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == RecordAudioRequestCode && grantResults.length > 0) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
|
||||
Toast.makeText(this, "Permission Granted", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void returnResults(ArrayList<String> results) {
|
||||
Handler handler = new SimpleMessageHandler(this);
|
||||
|
||||
Intent incomingIntent = getIntent();
|
||||
Log.d(TAG, incomingIntent.toString());
|
||||
Bundle extras = incomingIntent.getExtras();
|
||||
if (extras == null) {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, extras.toString());
|
||||
PendingIntent pendingIntent = getPendingIntent(extras);
|
||||
if (pendingIntent == null) {
|
||||
Log.d(TAG, "No pending intent, setting result intent.");
|
||||
setResultIntent(handler, results);
|
||||
} else {
|
||||
Log.d(TAG, pendingIntent.toString());
|
||||
|
||||
Bundle bundle = extras.getBundle(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE);
|
||||
if (bundle == null) {
|
||||
bundle = new Bundle();
|
||||
}
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.putExtras(bundle);
|
||||
// This is for Google Maps, YouTube, ...
|
||||
// intent.putExtra(SearchManager.QUERY, result);
|
||||
|
||||
// Display a toast with the transcription.
|
||||
handler.sendMessage(
|
||||
createMessage(MSG_TOAST, String.format(getString(R.string.toastForwardedMatches), results.get(0))));
|
||||
try {
|
||||
Log.d(TAG, "Sending result via pendingIntent");
|
||||
pendingIntent.send(this, AppCompatActivity.RESULT_OK, intent);
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
handler.sendMessage(createMessage(MSG_TOAST, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
private void setResultIntent(final Handler handler, List<String> matches) {
|
||||
Intent intent = new Intent();
|
||||
intent.putStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS, new ArrayList<>(matches));
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
}
|
||||
|
||||
private PendingIntent getPendingIntent(Bundle extras) {
|
||||
Parcelable extraResultsPendingIntentAsParceable = extras
|
||||
.getParcelable(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT);
|
||||
if (extraResultsPendingIntentAsParceable != null) {
|
||||
// PendingIntent.readPendingIntentOrNullFromParcel(mExtraResultsPendingIntent);
|
||||
if (extraResultsPendingIntentAsParceable instanceof PendingIntent) {
|
||||
return (PendingIntent) extraResultsPendingIntentAsParceable;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
24
app/src/main/res/layout/speech_activity.xml
Normal file
24
app/src/main/res/layout/speech_activity.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout 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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".SpeechActivity">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_height="wrap_content">
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="15dp"
|
||||
android:padding="10dp"
|
||||
android:hint="Loading..."
|
||||
android:id="@+id/text"
|
||||
android:layout_centerInParent="true"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
6
app/src/main/res/values/colors.xml
Normal file
6
app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#6200EE</color>
|
||||
<color name="colorPrimaryDark">#3700B3</color>
|
||||
<color name="colorAccent">#03DAC5</color>
|
||||
</resources>
|
@ -4,5 +4,6 @@
|
||||
<string name="app_name">LocalSTT</string>
|
||||
<string name="vosk_recognition_service">Kaldi/Vosk Recognizer</string>
|
||||
<string name="deepspeech_recognition_service">Deepspeech Recognizer</string>
|
||||
<string name="toastForwardedMatches">Recognized: %1$s</string>
|
||||
|
||||
</resources>
|
||||
|
11
app/src/main/res/values/styles.xml
Normal file
11
app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
3
gradle.properties
Normal file
3
gradle.properties
Normal file
@ -0,0 +1,3 @@
|
||||
android.enableD8=true
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
Loading…
Reference in New Issue
Block a user