Android Open Source - K6nele Voice Ime Service






From Project

Back to project page K6nele.

License

The source code is released under:

Apache License

If you think the Android project K6nele listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package ee.ioc.phon.android.speak;
//from  w w w .  j  a v  a 2  s . c  om
import android.annotation.TargetApi;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.SpannableString;
import android.view.View;
import android.view.Window;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class VoiceImeService extends InputMethodService {

    private InputMethodManager mInputMethodManager;

    private VoiceImeView mInputView;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("onCreate");
        mInputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
    }

    /**
     * This is called at configuration change. We just kill the running session.
     * TODO: better handle configuration changes
     */
    @Override
    public void onInitializeInterface() {
        Log.i("onInitializeInterface");
        closeSession();
    }

    @Override
    public View onCreateInputView() {
        Log.i("onCreateInputView");
        mInputView = (VoiceImeView) getLayoutInflater().inflate(R.layout.voice_ime_view, null);
        return mInputView;
    }

    /**
     * We check the type of editor control and if we probably cannot handle it (email addresses,
     * dates) or do not want to (passwords) then we hand the editing over to an other keyboard.
     */
    @Override
    public void onStartInput(EditorInfo attribute, boolean restarting) {
        super.onStartInput(attribute, restarting);
        Log.i("onStartInput: " + attribute.inputType + "/" + attribute.imeOptions + "/" + restarting);

        switch (attribute.inputType & InputType.TYPE_MASK_CLASS) {
            case InputType.TYPE_CLASS_NUMBER:
                Log.i("onStartInput: NUMBER");
                break;
            case InputType.TYPE_CLASS_DATETIME:
                Log.i("onStartInput: DATETIME");
                switchIme(false);
                break;
            case InputType.TYPE_CLASS_PHONE:
                Log.i("onStartInput: PHONE");
                switchIme(false);
                break;
            case InputType.TYPE_CLASS_TEXT:
                int variation = attribute.inputType & InputType.TYPE_MASK_VARIATION;
                Log.i("onStartInput: TEXT, variation: " + variation);
                if (variation == InputType.TYPE_TEXT_VARIATION_PASSWORD ||
                        variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
                    Log.i("onStartInput: PASSWORD || VISIBLE_PASSWORD");
                    // We refuse to recognize passwords for privacy reasons.
                    switchIme(false);
                } else if (variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
                    Log.i("onStartInput: EMAIL_ADDRESS");
                    switchIme(false);
                } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
                    Log.i("onStartInput: URI");
                    // URI bar of Chrome and Firefox, can also handle search queries, thus supported
                } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
                    Log.i("onStartInput: FILTER");
                    // List filtering? Used in the Dialer search bar, thus supported
                }

                // This is used in the standard search bar (e.g. in Google Play).
                if ((attribute.inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
                    Log.i("onStartInput: FLAG_AUTO_COMPLETE");
                }
                break;

            default:
        }
    }

    @Override
    public void onFinishInput() {
        super.onFinishInput();
        Log.i("onFinishInput");
        closeSession();
    }

    @Override
    public void onStartInputView(EditorInfo attribute, boolean restarting) {
        super.onStartInputView(attribute, restarting);
        Log.i("onStartInputView: " + attribute.inputType + "/" + attribute.imeOptions + "/" + restarting);

        closeSession();

        if (restarting) {
            return;
        }

        mInputView.setListener(getRecognizerIntent(getApplicationContext(), attribute), new VoiceImeView.VoiceImeViewListener() {

            TextUpdater mTextUpdater = new TextUpdater();

            @Override
            public void onPartialResult(String text) {
                mTextUpdater.commitPartialResult(getCurrentInputConnection(), text);
            }

            @Override
            public void onFinalResult(String text) {
                mTextUpdater.commitFinalResult(getCurrentInputConnection(), text);
            }

            @Override
            public void onSwitchIme(boolean isAskUser) {
                switchIme(isAskUser);
            }

            @Override
            public void onGo() {
                performGo();
                handleClose();
            }

            @Override
            public void onDeleteLastWord() {
                mTextUpdater.deleteWord(getCurrentInputConnection());
            }

            @Override
            public void onAddNewline() {
                mTextUpdater.addNewline(getCurrentInputConnection());
            }

            @Override
            public void onAddSpace() {
                mTextUpdater.addSpace(getCurrentInputConnection());
            }
        });

        // Launch recognition immediately (if set so)
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        if (Utils.getPrefBoolean(prefs, getResources(), R.string.keyImeAutoStart, R.bool.defaultImeAutoStart)) {
            Log.i("Auto-starting");
            mInputView.start();
        }
    }

    @Override
    public void onFinishInputView(boolean finishingInput) {
        super.onFinishInputView(finishingInput);
        Log.i("onFinishInputView: " + finishingInput);
        closeSession();
    }

    @Override
    public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
        Log.i("onCurrentInputMethodSubtypeChanged");
        closeSession();
    }

    private void closeSession() {
        if (mInputView != null) {
            mInputView.closeSession();
        }
    }

    private void handleClose() {
        requestHideSelf(0);
        closeSession();
    }

    private IBinder getToken() {
        final Dialog dialog = getWindow();
        if (dialog == null) {
            return null;
        }
        final Window window = dialog.getWindow();
        if (window == null) {
            return null;
        }
        return window.getAttributes().token;
    }

    /**
     * Switching to another IME (ideally the previous one). Either when the user tries to edit
     * a password, or explicitly presses the "switch IME" button.
     * TODO: not sure it is the best way to do it
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void switchIme(boolean isAskUser) {
        closeSession();
        if (isAskUser) {
            mInputMethodManager.showInputMethodPicker();
        } else {
            final IBinder token = getToken();
            try {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    mInputMethodManager.switchToNextInputMethod(getToken(), false /* not onlyCurrentIme */);
                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                    mInputMethodManager.switchToLastInputMethod(token);
                } else {
                    mInputMethodManager.showInputMethodPicker();
                }
            } catch (NoSuchMethodError e) {
                Log.e("IME switch failed", e);
            }
        }
    }


    /**
     * Performs the Go-action, e.g. to launch search on a searchbar.
     */
    private void performGo() {
        // Does not work on Google Searchbar
        // getCurrentInputConnection().performEditorAction(EditorInfo.IME_ACTION_DONE);

        // Works in Google Searchbar, GF Translator
        getCurrentInputConnection().performEditorAction(EditorInfo.IME_ACTION_GO);
    }


    private static String asString(Object o) {
        if (o == null) {
            return null;
        }
        if (o instanceof SpannableString) {
            SpannableString ss = (SpannableString) o;
            return ss.subSequence(0, ss.length()).toString();
        }
        return o.toString();
    }

    private static Intent getRecognizerIntent(Context context, EditorInfo attribute) {
        Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
        intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, context.getPackageName());
        intent.putExtra(Extras.EXTRA_UNLIMITED_DURATION, true);
        intent.putExtra(Extras.EXTRA_EDITOR_INFO, toBundle(attribute));
        return intent;
    }

    private static Bundle toBundle(EditorInfo attribute) {
        Bundle bundle = new Bundle();
        bundle.putBundle("extras", attribute.extras);
        bundle.putString("actionLabel", asString(attribute.actionLabel));
        bundle.putString("fieldName", asString(attribute.fieldName));
        bundle.putString("hintText", asString(attribute.hintText));
        bundle.putString("inputType", String.valueOf(attribute.inputType));
        bundle.putString("label", asString(attribute.label));
        // This line gets the actual caller package registered in the package registry.
        // The key needs to be "packageName".
        bundle.putString("packageName", asString(attribute.packageName));
        return bundle;
    }


    private static class TextUpdater {

        // Maximum number of characters that left-swipe is willing to delete
        private static final int MAX_DELETABLE_CONTEXT = 100;
        private static final Pattern WHITESPACE = Pattern.compile("\\s{1,}");

        private String mPrevText = "";

        public TextUpdater() {
        }


        /**
         * Writes the text into the text field and forgets the previous entry.
         */
        public void commitFinalResult(InputConnection ic, String text) {
            commitText(ic, text);
            mPrevText = "";
        }

        /**
         * Writes the text into the text field and stores it for future reference.
         */
        public void commitPartialResult(InputConnection ic, String text) {
            commitText(ic, text);
            mPrevText = text;
        }

        public void addNewline(InputConnection ic) {
            if (ic != null) {
                ic.commitText("\n", 1);
            }
        }

        public void addSpace(InputConnection ic) {
            if (ic != null) {
                ic.commitText(" ", 1);
            }
        }

        /**
         * Deletes all characters up to the leftmost whitespace from the cursor (including the whitespace).
         * If something is selected then delete the selection.
         * TODO: maybe expensive?
         */
        @TargetApi(Build.VERSION_CODES.GINGERBREAD)
        public void deleteWord(InputConnection ic) {
            if (ic != null) {
                // If something is selected then delete the selection and return
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD
                        && ic.getSelectedText(0) != null) {
                    ic.commitText("", 0);
                } else {
                    CharSequence beforeCursor = ic.getTextBeforeCursor(MAX_DELETABLE_CONTEXT, 0);
                    if (beforeCursor != null) {
                        int beforeCursorLength = beforeCursor.length();
                        Matcher m = WHITESPACE.matcher(beforeCursor);
                        int lastIndex = 0;
                        while (m.find()) {
                            lastIndex = m.start();
                        }
                        if (lastIndex > 0) {
                            ic.deleteSurroundingText(beforeCursorLength - lastIndex, 0);
                        } else if (beforeCursorLength < MAX_DELETABLE_CONTEXT) {
                            ic.deleteSurroundingText(beforeCursorLength, 0);
                        }
                    }
                }
            }
        }

        /**
         * Updates the text field, modifying only the parts that have changed.
         */
        private void commitText(InputConnection ic, String text) {
            if (ic != null && text != null && text.length() > 0) {
                // Calculate the length of the text that has changed
                String commonPrefix = greatestCommonPrefix(mPrevText, text);
                int commonPrefixLength = commonPrefix.length();
                int prevLength = mPrevText.length();
                int deletableLength = prevLength - commonPrefixLength;

                if (deletableLength > 0) {
                    ic.deleteSurroundingText(deletableLength, 0);
                }

                if (commonPrefixLength == text.length()) {
                    return;
                }

                // We look at the left context of the cursor
                // to decide which glue symbol to use and whether to capitalize the text.
                CharSequence leftContext = ic.getTextBeforeCursor(MAX_DELETABLE_CONTEXT, 0);
                String glue = "";
                if (commonPrefixLength == 0) {
                    char firstChar = text.charAt(0);

                    if (!(leftContext.length() == 0
                            || Constants.CHARACTERS_WS.contains(firstChar)
                            || Constants.CHARACTERS_PUNCT.contains(firstChar)
                            || Constants.CHARACTERS_WS.contains(leftContext.charAt(leftContext.length() - 1)))) {
                        glue = " ";
                    }
                } else {
                    text = text.substring(commonPrefixLength);
                }

                // Capitalize if required by left context
                String leftContextTrimmed = leftContext.toString().trim();
                if (leftContextTrimmed.length() == 0
                        || Constants.CHARACTERS_EOS.contains(leftContextTrimmed.charAt(leftContextTrimmed.length() - 1))) {
                    // Since the text can start with whitespace (newline),
                    // we capitalize the first non-whitespace character.
                    int firstNonWhitespaceIndex = -1;
                    for (int i = 0; i < text.length(); i++) {
                        if (!Constants.CHARACTERS_WS.contains(text.charAt(i))) {
                            firstNonWhitespaceIndex = i;
                            break;
                        }
                    }
                    if (firstNonWhitespaceIndex > -1) {
                        String newText = text.substring(0, firstNonWhitespaceIndex)
                                + Character.toUpperCase(text.charAt(firstNonWhitespaceIndex));
                        if (firstNonWhitespaceIndex < text.length() - 1) {
                            newText += text.substring(firstNonWhitespaceIndex + 1);
                        }
                        text = newText;
                    }
                }

                ic.commitText(glue + text, 1);
            }
        }

    }

    public static String greatestCommonPrefix(String a, String b) {
        int minLength = Math.min(a.length(), b.length());
        for (int i = 0; i < minLength; i++) {
            if (a.charAt(i) != b.charAt(i)) {
                return a.substring(0, i);
            }
        }
        return a.substring(0, minLength);
    }
}




Java Source Code List

ee.ioc.phon.android.speak.AboutActivity.java
ee.ioc.phon.android.speak.AppListActivity.java
ee.ioc.phon.android.speak.AppListCursorAdapter.java
ee.ioc.phon.android.speak.AudioCue.java
ee.ioc.phon.android.speak.AudioPauser.java
ee.ioc.phon.android.speak.Caller.java
ee.ioc.phon.android.speak.ChunkedWebRecSessionBuilder.java
ee.ioc.phon.android.speak.Constants.java
ee.ioc.phon.android.speak.DetailsActivity.java
ee.ioc.phon.android.speak.ExecutableString.java
ee.ioc.phon.android.speak.Executable.java
ee.ioc.phon.android.speak.Extras.java
ee.ioc.phon.android.speak.GetLanguageDetailsReceiver.java
ee.ioc.phon.android.speak.GrammarListActivity.java
ee.ioc.phon.android.speak.Log.java
ee.ioc.phon.android.speak.MicButton.java
ee.ioc.phon.android.speak.OnSwipeTouchListener.java
ee.ioc.phon.android.speak.PackageNameRegistry.java
ee.ioc.phon.android.speak.PreferencesRecognitionServiceHttp.java
ee.ioc.phon.android.speak.PreferencesRecognitionServiceWs.java
ee.ioc.phon.android.speak.Preferences.java
ee.ioc.phon.android.speak.RawAudioRecorder.java
ee.ioc.phon.android.speak.RecognizerIntentActivity.java
ee.ioc.phon.android.speak.RecognizerIntentListActivity.java
ee.ioc.phon.android.speak.RecognizerIntentService.java
ee.ioc.phon.android.speak.RecognizerIntent.java
ee.ioc.phon.android.speak.ServerListActivity.java
ee.ioc.phon.android.speak.SpeechRecognitionService.java
ee.ioc.phon.android.speak.Utils.java
ee.ioc.phon.android.speak.VoiceImeService.java
ee.ioc.phon.android.speak.VoiceImeView.java
ee.ioc.phon.android.speak.WebSocketRecognizer.java
ee.ioc.phon.android.speak.WebSocketResponse.java
ee.ioc.phon.android.speak.demo.AbstractRecognizerDemoActivity.java
ee.ioc.phon.android.speak.demo.ExtrasDemo.java
ee.ioc.phon.android.speak.demo.RepeaterDemo.java
ee.ioc.phon.android.speak.demo.SimpleDemo.java
ee.ioc.phon.android.speak.demo.VoiceSearchDemo.java
ee.ioc.phon.android.speak.provider.App.java
ee.ioc.phon.android.speak.provider.AppsContentProvider.java
ee.ioc.phon.android.speak.provider.BaseColumnsImpl.java
ee.ioc.phon.android.speak.provider.FileContentProvider.java
ee.ioc.phon.android.speak.provider.Grammar.java
ee.ioc.phon.android.speak.provider.Server.java