1

I created a virtual assistant app in Android Studio and it working fine until I exit the app window and the process stops. I want to make the app run in the background always so when it get's the wake word anytime it will respond. I tried using a Service but I couldn't make it work. Can you help me please?

This is my code:

package com.eylon.jarvis;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;

import java.util.ArrayList;
import java.util.Locale;

import ai.picovoice.porcupine.Porcupine;
import ai.picovoice.porcupine.PorcupineActivationException;
import ai.picovoice.porcupine.PorcupineActivationLimitException;
import ai.picovoice.porcupine.PorcupineActivationRefusedException;
import ai.picovoice.porcupine.PorcupineActivationThrottledException;
import ai.picovoice.porcupine.PorcupineException;
import ai.picovoice.porcupine.PorcupineInvalidArgumentException;
import ai.picovoice.porcupine.PorcupineManager;
import ai.picovoice.porcupine.PorcupineManagerCallback;

enum AppState {
    STOPPED,
    WAKEWORD,
    STT
}

public class MainActivity extends AppCompatActivity {

    private static final String ACCESS_KEY = "Oc8ZOSkVtJHWKhVW3iGMedHDSCSXn6P4vQtrQBl8hNLXwLmxLhs2AA==";

    private PorcupineManager porcupineManager = null;

    TextView textView;
    ToggleButton button;

    private SpeechRecognizer speechRecognizer;
    private Intent speechRecognizerIntent;
    private AppState currentState;

    private void displayError(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }

    private final PorcupineManagerCallback porcupineManagerCallback = new PorcupineManagerCallback() {
        @Override
        public void invoke(int keywordIndex) {
            runOnUiThread(() -> {
                textView.setText("");
                try {
                    // need to stop porcupine manager before speechRecognizer can start listening.
                    porcupineManager.stop();
                } catch (PorcupineException e) {
                    displayError("Failed to stop Porcupine.");
                    return;
                }

                speechRecognizer.startListening(speechRecognizerIntent);
                currentState = AppState.STT;
            });
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = findViewById(R.id.text1);
        button = findViewById(R.id.button1);

        if (!SpeechRecognizer.isRecognitionAvailable(this)) {
            displayError("Speech Recognition not available.");
        }

        // Creating the Intent of the Google speech to text and adding extra variables.
        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());
        speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, "en-US");
        speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "en-US");
        speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_ONLY_RETURN_LANGUAGE_PREFERENCE, "en-US");
        speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Start speaking");

        try {
            porcupineManager = new PorcupineManager.Builder()
                    .setAccessKey(ACCESS_KEY)
                    .setKeyword(Porcupine.BuiltInKeyword.JARVIS)
                    .setSensitivity(0.7f)
                    .build(getApplicationContext(), porcupineManagerCallback);
        } catch (PorcupineInvalidArgumentException e) {
            onPorcupineInitError(
                    String.format("%s\nEnsure your accessKey '%s' is a valid access key.", e.getMessage(), ACCESS_KEY)
            );
        } catch (PorcupineActivationException e) {
            onPorcupineInitError("AccessKey activation error");
        } catch (PorcupineActivationLimitException e) {
            onPorcupineInitError("AccessKey reached its device limit");
        } catch (PorcupineActivationRefusedException e) {
            onPorcupineInitError("AccessKey refused");
        } catch (PorcupineActivationThrottledException e) {
            onPorcupineInitError("AccessKey has been throttled");
        } catch (PorcupineException e) {
            onPorcupineInitError("Failed to initialize Porcupine " + e.getMessage());
        }

        currentState = AppState.STOPPED;
    }

    private void onPorcupineInitError(final String errorMessage) {
        runOnUiThread(() -> {
            TextView errorText = findViewById(R.id.text1);
            errorText.setText(errorMessage);

            ToggleButton recordButton = findViewById(R.id.button1);
            recordButton.setChecked(false);
            recordButton.setEnabled(false);
        });
    }

    @Override
    protected void onStop() {
        if (button.isChecked()) {
            stopService();
            button.toggle();
            speechRecognizer.destroy();
        }

        super.onStop();
    }

    private boolean hasRecordPermission() {
        return ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
                == PackageManager.PERMISSION_GRANTED;
    }

    private void requestRecordPermission() {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO},
                0);
    }

    @SuppressLint("SetTextI18n")
    private void playback(int milliSeconds) {
        speechRecognizer.stopListening();
        currentState = AppState.WAKEWORD;

        new Handler(Looper.getMainLooper()).postDelayed(() -> {
            if (currentState == AppState.WAKEWORD) {
                porcupineManager.start();
                textView.setText("Listening for " + Porcupine.BuiltInKeyword.JARVIS + " ...");
            }
        }, milliSeconds);
    }

    private void stopService() {
        if (porcupineManager != null) {
            try {
                porcupineManager.stop();
            } catch (PorcupineException e) {
                displayError("Failed to stop porcupine.");
            }
        }

        textView.setText("");
        speechRecognizer.stopListening();
        speechRecognizer.destroy();
        currentState = AppState.STOPPED;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (grantResults.length == 0 || grantResults[0] == PackageManager.PERMISSION_DENIED) {
            displayError("Microphone permission is required for this app!");
            requestRecordPermission();
        } else {
            speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
            speechRecognizer.setRecognitionListener(new SpeechListener());
            playback(0);
        }
    }

    public void process(View view) {
        if (button.isChecked()) {
            if (hasRecordPermission()) {
                speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
                speechRecognizer.setRecognitionListener(new SpeechListener());
                playback(0);
            } else {
                requestRecordPermission();
            }
        } else {
            stopService();
        }
    }

    private class SpeechListener implements RecognitionListener {
        @Override
        public void onReadyForSpeech(Bundle params) {
        }

        @Override
        public void onBeginningOfSpeech() {
        }

        @Override
        public void onRmsChanged(float rmsdB) {
        }

        @Override
        public void onBufferReceived(byte[] buffer) {
        }

        @Override
        public void onEndOfSpeech() {
        }

        @SuppressLint("SwitchIntDef")
        @Override
        public void onError(int error) {
            switch (error) {
                case SpeechRecognizer.ERROR_AUDIO:
                    displayError("Error recording audio.");
                    break;
                case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:
                    displayError("Insufficient permissions.");
                    break;
                case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:
                case SpeechRecognizer.ERROR_NETWORK:
                    displayError("Network Error.");
                    break;
                case SpeechRecognizer.ERROR_NO_MATCH:
                    if (button.isChecked()) {
                        displayError("No recognition result matched.");
                        playback(1000);
                    }
                case SpeechRecognizer.ERROR_CLIENT:
                    return;
                case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:
                    displayError("Recognition service is busy.");
                    break;
                case SpeechRecognizer.ERROR_SERVER:
                    displayError("Server Error.");
                    break;
                case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
                    displayError("No speech input.");
                    break;
                default:
                    displayError("Something wrong occurred.");
            }

            stopService();
            button.toggle();
        }

        @Override
        public void onResults(Bundle results) {
            ArrayList<String> data = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
            audioResponseSelecting(data.get(0).toLowerCase(Locale.ROOT));
            textView.setText(data.get(0));

            playback(3000);
        }

        @Override
        public void onPartialResults(Bundle partialResults) {
            ArrayList<String> data = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
            audioResponseSelecting(data.get(0).toLowerCase(Locale.ROOT));
            textView.setText(data.get(0));
        }

        @Override
        public void onEvent(int eventType, Bundle params) {
        }
    }

    // The response selecting function.
    public void audioResponseSelecting(String transcript)
    {
        if (transcript.equals(("Good morning").toLowerCase(Locale.ROOT)))
        {
            executeResponse(R.raw.good_morning);
        }
        else if (transcript.equals(("Who is your creator").toLowerCase(Locale.ROOT)))
        {
            executeResponse(R.raw.creator);
        }
    }

    // The audio file response execution function.
    public void executeResponse(final int audio)
    {
        MediaPlayer response = MediaPlayer.create(MainActivity.this, audio);
        response.start();
    }
}
AGJ
  • 23
  • 5

1 Answers1

1

By design, all SpeechRecognizer's methods "must be invoked only from the main application thread."

Main application thread is also referred to as "UI thread".

This means that SpeechRecognizer cannot run in a service.

WebViewer
  • 761
  • 7
  • 21