Android Custom Keyboard with SpeechRecognizer

2.4k views Asked by At

I have a fully functional custom android keyboard in which i have to add speech recognition. Here are the relevant parts of the implementation i have

public class CustomInputMethodService 
    extends InputMethodService
    implements <random stuff> {

    private SpeechRecognizer mSpeechRecognizer;
    private RecognitionListener mSpeechlistener;

    public void onCreate() {
        super.onCreate();
        mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
        mSpeechlistener = new CustomRecognitionListener();
        mSpeechRecognizer.setRecognitionListener(mSpeechlistener);
    }

    @Override
    public void onPress(int primaryCode) {
        if (primaryCode == KeyCodes.VOICE_INPUT) {
            mSpeechRecognizer.startListening(getSpeechIntent());
        }else if(..){
            ...
        }
    }

    private Intent getSpeechIntent() {
        Intent speechIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        speechIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        speechIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, this.getPackageName());
        speechIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, false);
        return speechIntent;
    }

}

The relevant method of the CustomRecognitionListener is simply:

        @Override
        public void onResults(Bundle results) {
            ArrayList<String> matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
            Log.d(TAG, "onResults: ----> " + matches.get(0));
            if(matches != null && matches.size() > 0) {
                writeText(matches.get(0));
            }
        }

This code is working just fine. The twist here is that i want a similar behaviour to what happens on google keyboard when the uset taps the microphone key:

enter image description here

This would ideally by achieved by something like:

Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
try {
    startActivityForResult(voiceIntent, Constants.RESULT_SPEECH);
} catch (ActivityNotFoundException ex) {
    DebugLog.e(TAG, "Not found excpetion onKeyDown: " + ex);
}

However, since the key listener is on and InputMethodService im not able to call startActivityForResult. What is the ideal way to accomplish this? Should i simply start a new activity without a layout and have a callback to the inputMethodService? seems messy

2

There are 2 answers

1
Kaarel On BEST ANSWER

Your screenshot shows "Google voice typing", which is an independent IME that is called by the Google Keyboard when its microphone button is pressed. So, your IME should do the same: replace itself with an IME that provides voice typing, and hope that there is a backlink to your IME once the voice typing is done.

The simplest implementation would be Switching among IME Subtypes, but you might want to have more control, e.g. start a specific IME with specific input parameters, etc. I'm not sure what is the best/standard way to achieve this extra control.

For an example of a voice typing IME you could look into (my app) Kõnele.

0
Adr3nl On

Simple implementation of the solution:

// on mic tap we call
public void startVoiceListening() {
    InputMethodManager imeManager = (InputMethodManager) getApplicationContext().getSystemService(INPUT_METHOD_SERVICE);
    String voiceExists = voiceExists(imeManager);
    if (voiceExists != null) {
        final IBinder token = getWindow().getWindow().getAttributes().token;
        imeManager.setInputMethod(token,voiceExists);
    }
}

private String voiceExists(InputMethodManager imeManager) {
    List<InputMethodInfo> list = imeManager.getInputMethodList();
    for (InputMethodInfo el : list) {
        // do something to check whatever IME we want.
        // in this case "com.google.android.googlequicksearchbox"
    }
    return null;
}

Once we no longer want to use the current IME just close it and it will fall back to the previous one