/* * Copyright (c) 2013 Menny Even-Danan * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.anysoftkeyboard.voice; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.inputmethodservice.InputMethodService; import android.os.Handler; import android.os.IBinder; import android.speech.RecognizerIntent; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import com.anysoftkeyboard.utils.Log; import com.menny.android.anysoftkeyboard.FeaturesSet; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Triggers a voice recognition using the Intent api. */ class IntentApiTrigger implements Trigger { private static final String TAG = "VoiceIntentApiTrigger"; private final InputMethodService mInputMethodService; private final ServiceBridge mServiceBridge; private String mLastRecognitionResult; private Set<Character> mUpperCaseChars; private final Handler mHandler; private IBinder mToken; public IntentApiTrigger(InputMethodService inputMethodService) { mInputMethodService = inputMethodService; mServiceBridge = new ServiceBridge(new Callback() { public void onRecognitionResult(String recognitionResult) { postResult(recognitionResult); } }); mUpperCaseChars = new HashSet<Character>(); mUpperCaseChars.add('.'); mUpperCaseChars.add('!'); mUpperCaseChars.add('?'); mUpperCaseChars.add('\n'); mHandler = new Handler(); } public void startVoiceRecognition(String language) { mToken = mInputMethodService.getWindow().getWindow().getAttributes().token; mServiceBridge.startVoiceRecognition(mInputMethodService, language); } public static boolean isInstalled(InputMethodService inputMethodService) { PackageManager pm = inputMethodService.getPackageManager(); List<ResolveInfo> activities = pm.queryIntentActivities( new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0); if (FeaturesSet.DEBUG_LOG) { for (ResolveInfo ri : activities) { Log.d(TAG, "Found IntentApiTrigger ACTION_RECOGNIZE_SPEECH handler: " + ri.activityInfo.targetActivity + " @ " + ri.activityInfo.packageName); } } return activities.size() > 0; } private InputMethodManager getInputMethodManager() { return (InputMethodManager) mInputMethodService .getSystemService(Context.INPUT_METHOD_SERVICE); } private void postResult(String recognitionResult) { mLastRecognitionResult = recognitionResult; // Request the system to display the IME. getInputMethodManager().showSoftInputFromInputMethod(mToken, InputMethodManager.SHOW_IMPLICIT); } public void onStartInputView() { if (mLastRecognitionResult != null) { scheduleCommit(); } } private void scheduleCommit() { mHandler.post(new Runnable() { public void run() { commitResult(); } }); } private void commitResult() { if (mLastRecognitionResult == null) { return; } String result = mLastRecognitionResult; InputConnection conn = mInputMethodService.getCurrentInputConnection(); if (conn == null) { Log.i(TAG, "Unable to commit recognition result, as the current input connection " + "is null. Did someone kill the IME?"); return; } if (!conn.beginBatchEdit()) { Log.i(TAG, "Unable to commit recognition result, as a batch edit cannot start"); return; } try { ExtractedTextRequest etr = new ExtractedTextRequest(); etr.flags = InputConnection.GET_TEXT_WITH_STYLES; ExtractedText et = conn.getExtractedText(etr, 0); if (et == null) { Log.i(TAG, "Unable to commit recognition result, as extracted text is null"); return; } if (et.text != null) { if (et.selectionStart != et.selectionEnd) { conn.deleteSurroundingText(et.selectionStart, et.selectionEnd); } result = format(et, result); } if (!conn.commitText(result, 0)) { Log.i(TAG, "Unable to commit recognition result"); return; } mLastRecognitionResult = null; } finally { conn.endBatchEdit(); } } /** * Formats the recognised text by adding white spaces at the beginning or at the end, and * by making the first char upper case if necessary. */ private String format(ExtractedText et, String result) { int pos = et.selectionStart - 1; while (pos > 0 && Character.isWhitespace(et.text.charAt(pos))) { pos--; } if (pos == -1 || mUpperCaseChars.contains(et.text.charAt(pos))) { result = Character.toUpperCase(result.charAt(0)) + result.substring(1); } if (et.selectionStart - 1 > 0 && !Character.isWhitespace(et.text.charAt(et.selectionStart - 1))) { result = " " + result; } if (et.selectionEnd < et.text.length() && !Character.isWhitespace(et.text.charAt(et.selectionEnd))) { result = result + " "; } return result; } interface Callback { void onRecognitionResult(String recognitionResult); } }