481 lines
18 KiB
Java
481 lines
18 KiB
Java
|
/*
|
||
|
* Copyright (C) 2010 The Android Open Source Project
|
||
|
*
|
||
|
* 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 android.speech;
|
||
|
|
||
|
import android.content.ComponentName;
|
||
|
import android.content.Context;
|
||
|
import android.content.Intent;
|
||
|
import android.content.ServiceConnection;
|
||
|
import android.content.pm.ResolveInfo;
|
||
|
import android.os.Bundle;
|
||
|
import android.os.Handler;
|
||
|
import android.os.IBinder;
|
||
|
import android.os.Looper;
|
||
|
import android.os.Message;
|
||
|
import android.os.RemoteException;
|
||
|
import android.provider.Settings;
|
||
|
import android.text.TextUtils;
|
||
|
import android.util.Log;
|
||
|
|
||
|
import java.util.LinkedList;
|
||
|
import java.util.List;
|
||
|
import java.util.Queue;
|
||
|
|
||
|
/**
|
||
|
* This class provides access to the speech recognition service. This service allows access to the
|
||
|
* speech recognizer. Do not instantiate this class directly, instead, call
|
||
|
* {@link SpeechRecognizer#createSpeechRecognizer(Context)}. This class's methods must be
|
||
|
* invoked only from the main application thread. Please note that the application must have
|
||
|
* {@link android.Manifest.permission#RECORD_AUDIO} permission to use this class.
|
||
|
*/
|
||
|
public class SpeechRecognizer {
|
||
|
/** DEBUG value to enable verbose debug prints */
|
||
|
private final static boolean DBG = false;
|
||
|
|
||
|
/** Log messages identifier */
|
||
|
private static final String TAG = "SpeechRecognizer";
|
||
|
|
||
|
/**
|
||
|
* Used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the
|
||
|
* {@link RecognitionListener#onResults(Bundle)} and
|
||
|
* {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible
|
||
|
* recognition results, where the first element is the most likely candidate.
|
||
|
*/
|
||
|
public static final String RESULTS_RECOGNITION = "results_recognition";
|
||
|
|
||
|
/** Network operation timed out. */
|
||
|
public static final int ERROR_NETWORK_TIMEOUT = 1;
|
||
|
|
||
|
/** Other network related errors. */
|
||
|
public static final int ERROR_NETWORK = 2;
|
||
|
|
||
|
/** Audio recording error. */
|
||
|
public static final int ERROR_AUDIO = 3;
|
||
|
|
||
|
/** Server sends error status. */
|
||
|
public static final int ERROR_SERVER = 4;
|
||
|
|
||
|
/** Other client side errors. */
|
||
|
public static final int ERROR_CLIENT = 5;
|
||
|
|
||
|
/** No speech input */
|
||
|
public static final int ERROR_SPEECH_TIMEOUT = 6;
|
||
|
|
||
|
/** No recognition result matched. */
|
||
|
public static final int ERROR_NO_MATCH = 7;
|
||
|
|
||
|
/** RecognitionService busy. */
|
||
|
public static final int ERROR_RECOGNIZER_BUSY = 8;
|
||
|
|
||
|
/** Insufficient permissions */
|
||
|
public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9;
|
||
|
|
||
|
/** action codes */
|
||
|
private final static int MSG_START = 1;
|
||
|
private final static int MSG_STOP = 2;
|
||
|
private final static int MSG_CANCEL = 3;
|
||
|
private final static int MSG_CHANGE_LISTENER = 4;
|
||
|
|
||
|
/** The actual RecognitionService endpoint */
|
||
|
private IRecognitionService mService;
|
||
|
|
||
|
/** The connection to the actual service */
|
||
|
private Connection mConnection;
|
||
|
|
||
|
/** Context with which the manager was created */
|
||
|
private final Context mContext;
|
||
|
|
||
|
/** Component to direct service intent to */
|
||
|
private final ComponentName mServiceComponent;
|
||
|
|
||
|
/** Handler that will execute the main tasks */
|
||
|
private Handler mHandler = new Handler() {
|
||
|
@Override
|
||
|
public void handleMessage(Message msg) {
|
||
|
switch (msg.what) {
|
||
|
case MSG_START:
|
||
|
handleStartListening((Intent) msg.obj);
|
||
|
break;
|
||
|
case MSG_STOP:
|
||
|
handleStopMessage();
|
||
|
break;
|
||
|
case MSG_CANCEL:
|
||
|
handleCancelMessage();
|
||
|
break;
|
||
|
case MSG_CHANGE_LISTENER:
|
||
|
handleChangeListener((RecognitionListener) msg.obj);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Temporary queue, saving the messages until the connection will be established, afterwards,
|
||
|
* only mHandler will receive the messages
|
||
|
*/
|
||
|
private final Queue<Message> mPendingTasks = new LinkedList<Message>();
|
||
|
|
||
|
/** The Listener that will receive all the callbacks */
|
||
|
private final InternalListener mListener = new InternalListener();
|
||
|
|
||
|
/**
|
||
|
* The right way to create a {@code SpeechRecognizer} is by using
|
||
|
* {@link #createSpeechRecognizer} static factory method
|
||
|
*/
|
||
|
private SpeechRecognizer(final Context context, final ComponentName serviceComponent) {
|
||
|
mContext = context;
|
||
|
mServiceComponent = serviceComponent;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Basic ServiceConnection that records the mService variable. Additionally, on creation it
|
||
|
* invokes the {@link IRecognitionService#startListening(Intent, IRecognitionListener)}.
|
||
|
*/
|
||
|
private class Connection implements ServiceConnection {
|
||
|
|
||
|
public void onServiceConnected(final ComponentName name, final IBinder service) {
|
||
|
// always done on the application main thread, so no need to send message to mHandler
|
||
|
mService = IRecognitionService.Stub.asInterface(service);
|
||
|
if (DBG) Log.d(TAG, "onServiceConnected - Success");
|
||
|
while (!mPendingTasks.isEmpty()) {
|
||
|
mHandler.sendMessage(mPendingTasks.poll());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void onServiceDisconnected(final ComponentName name) {
|
||
|
// always done on the application main thread, so no need to send message to mHandler
|
||
|
mService = null;
|
||
|
mConnection = null;
|
||
|
mPendingTasks.clear();
|
||
|
if (DBG) Log.d(TAG, "onServiceDisconnected - Success");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks whether a speech recognition service is available on the system. If this method
|
||
|
* returns {@code false}, {@link SpeechRecognizer#createSpeechRecognizer(Context)} will
|
||
|
* fail.
|
||
|
*
|
||
|
* @param context with which {@code SpeechRecognizer} will be created
|
||
|
* @return {@code true} if recognition is available, {@code false} otherwise
|
||
|
*/
|
||
|
public static boolean isRecognitionAvailable(final Context context) {
|
||
|
final List<ResolveInfo> list = context.getPackageManager().queryIntentServices(
|
||
|
new Intent(RecognitionService.SERVICE_INTERFACE), 0);
|
||
|
return list != null && list.size() != 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Factory method to create a new {@code SpeechRecognizer}. Please note that
|
||
|
* {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any
|
||
|
* command to the created {@code SpeechRecognizer}, otherwise no notifications will be
|
||
|
* received.
|
||
|
*
|
||
|
* @param context in which to create {@code SpeechRecognizer}
|
||
|
* @return a new {@code SpeechRecognizer}
|
||
|
*/
|
||
|
public static SpeechRecognizer createSpeechRecognizer(final Context context) {
|
||
|
return createSpeechRecognizer(context, null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Factory method to create a new {@code SpeechRecognizer}. Please note that
|
||
|
* {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any
|
||
|
* command to the created {@code SpeechRecognizer}, otherwise no notifications will be
|
||
|
* received.
|
||
|
*
|
||
|
* Use this version of the method to specify a specific service to direct this
|
||
|
* {@link SpeechRecognizer} to. Normally you would not use this; use
|
||
|
* {@link #createSpeechRecognizer(Context)} instead to use the system default recognition
|
||
|
* service.
|
||
|
*
|
||
|
* @param context in which to create {@code SpeechRecognizer}
|
||
|
* @param serviceComponent the {@link ComponentName} of a specific service to direct this
|
||
|
* {@code SpeechRecognizer} to
|
||
|
* @return a new {@code SpeechRecognizer}
|
||
|
*/
|
||
|
public static SpeechRecognizer createSpeechRecognizer(final Context context,
|
||
|
final ComponentName serviceComponent) {
|
||
|
if (context == null) {
|
||
|
throw new IllegalArgumentException("Context cannot be null)");
|
||
|
}
|
||
|
checkIsCalledFromMainThread();
|
||
|
return new SpeechRecognizer(context, serviceComponent);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the listener that will receive all the callbacks. The previous unfinished commands will
|
||
|
* be executed with the old listener, while any following command will be executed with the new
|
||
|
* listener.
|
||
|
*
|
||
|
* @param listener listener that will receive all the callbacks from the created
|
||
|
* {@link SpeechRecognizer}, this must not be null.
|
||
|
*/
|
||
|
public void setRecognitionListener(RecognitionListener listener) {
|
||
|
checkIsCalledFromMainThread();
|
||
|
putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Starts listening for speech. Please note that
|
||
|
* {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
|
||
|
* no notifications will be received.
|
||
|
*
|
||
|
* @param recognizerIntent contains parameters for the recognition to be performed. The intent
|
||
|
* may also contain optional extras, see {@link RecognizerIntent}. If these values are
|
||
|
* not set explicitly, default values will be used by the recognizer.
|
||
|
*/
|
||
|
public void startListening(final Intent recognizerIntent) {
|
||
|
if (recognizerIntent == null) {
|
||
|
throw new IllegalArgumentException("intent must not be null");
|
||
|
}
|
||
|
checkIsCalledFromMainThread();
|
||
|
if (mConnection == null) { // first time connection
|
||
|
mConnection = new Connection();
|
||
|
|
||
|
Intent serviceIntent = new Intent(RecognitionService.SERVICE_INTERFACE);
|
||
|
|
||
|
if (mServiceComponent == null) {
|
||
|
String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
|
||
|
Settings.Secure.VOICE_RECOGNITION_SERVICE);
|
||
|
|
||
|
if (TextUtils.isEmpty(serviceComponent)) {
|
||
|
Log.e(TAG, "no selected voice recognition service");
|
||
|
mListener.onError(ERROR_CLIENT);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
serviceIntent.setComponent(ComponentName.unflattenFromString(serviceComponent));
|
||
|
} else {
|
||
|
serviceIntent.setComponent(mServiceComponent);
|
||
|
}
|
||
|
|
||
|
if (!mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
|
||
|
Log.e(TAG, "bind to recognition service failed");
|
||
|
mConnection = null;
|
||
|
mService = null;
|
||
|
mListener.onError(ERROR_CLIENT);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Stops listening for speech. Speech captured so far will be recognized as if the user had
|
||
|
* stopped speaking at this point. Note that in the default case, this does not need to be
|
||
|
* called, as the speech endpointer will automatically stop the recognizer listening when it
|
||
|
* determines speech has completed. However, you can manipulate endpointer parameters directly
|
||
|
* using the intent extras defined in {@link RecognizerIntent}, in which case you may sometimes
|
||
|
* want to manually call this method to stop listening sooner. Please note that
|
||
|
* {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
|
||
|
* no notifications will be received.
|
||
|
*/
|
||
|
public void stopListening() {
|
||
|
checkIsCalledFromMainThread();
|
||
|
putMessage(Message.obtain(mHandler, MSG_STOP));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Cancels the speech recognition. Please note that
|
||
|
* {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
|
||
|
* no notifications will be received.
|
||
|
*/
|
||
|
public void cancel() {
|
||
|
checkIsCalledFromMainThread();
|
||
|
putMessage(Message.obtain(mHandler, MSG_CANCEL));
|
||
|
}
|
||
|
|
||
|
private static void checkIsCalledFromMainThread() {
|
||
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||
|
throw new RuntimeException(
|
||
|
"SpeechRecognizer should be used only from the application's main thread");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void putMessage(Message msg) {
|
||
|
if (mService == null) {
|
||
|
mPendingTasks.offer(msg);
|
||
|
} else {
|
||
|
mHandler.sendMessage(msg);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** sends the actual message to the service */
|
||
|
private void handleStartListening(Intent recognizerIntent) {
|
||
|
if (!checkOpenConnection()) {
|
||
|
return;
|
||
|
}
|
||
|
try {
|
||
|
mService.startListening(recognizerIntent, mListener);
|
||
|
if (DBG) Log.d(TAG, "service start listening command succeded");
|
||
|
} catch (final RemoteException e) {
|
||
|
Log.e(TAG, "startListening() failed", e);
|
||
|
mListener.onError(ERROR_CLIENT);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** sends the actual message to the service */
|
||
|
private void handleStopMessage() {
|
||
|
if (!checkOpenConnection()) {
|
||
|
return;
|
||
|
}
|
||
|
try {
|
||
|
mService.stopListening(mListener);
|
||
|
if (DBG) Log.d(TAG, "service stop listening command succeded");
|
||
|
} catch (final RemoteException e) {
|
||
|
Log.e(TAG, "stopListening() failed", e);
|
||
|
mListener.onError(ERROR_CLIENT);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** sends the actual message to the service */
|
||
|
private void handleCancelMessage() {
|
||
|
if (!checkOpenConnection()) {
|
||
|
return;
|
||
|
}
|
||
|
try {
|
||
|
mService.cancel(mListener);
|
||
|
if (DBG) Log.d(TAG, "service cancel command succeded");
|
||
|
} catch (final RemoteException e) {
|
||
|
Log.e(TAG, "cancel() failed", e);
|
||
|
mListener.onError(ERROR_CLIENT);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private boolean checkOpenConnection() {
|
||
|
if (mService != null) {
|
||
|
return true;
|
||
|
}
|
||
|
mListener.onError(ERROR_CLIENT);
|
||
|
Log.e(TAG, "not connected to the recognition service");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/** changes the listener */
|
||
|
private void handleChangeListener(RecognitionListener listener) {
|
||
|
if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener);
|
||
|
mListener.mInternalListener = listener;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Destroys the {@code SpeechRecognizer} object.
|
||
|
*/
|
||
|
public void destroy() {
|
||
|
if (mConnection != null) {
|
||
|
mContext.unbindService(mConnection);
|
||
|
}
|
||
|
mPendingTasks.clear();
|
||
|
mService = null;
|
||
|
mConnection = null;
|
||
|
mListener.mInternalListener = null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Internal wrapper of IRecognitionListener which will propagate the results to
|
||
|
* RecognitionListener
|
||
|
*/
|
||
|
private class InternalListener extends IRecognitionListener.Stub {
|
||
|
private RecognitionListener mInternalListener;
|
||
|
|
||
|
private final static int MSG_BEGINNING_OF_SPEECH = 1;
|
||
|
private final static int MSG_BUFFER_RECEIVED = 2;
|
||
|
private final static int MSG_END_OF_SPEECH = 3;
|
||
|
private final static int MSG_ERROR = 4;
|
||
|
private final static int MSG_READY_FOR_SPEECH = 5;
|
||
|
private final static int MSG_RESULTS = 6;
|
||
|
private final static int MSG_PARTIAL_RESULTS = 7;
|
||
|
private final static int MSG_RMS_CHANGED = 8;
|
||
|
private final static int MSG_ON_EVENT = 9;
|
||
|
|
||
|
private final Handler mInternalHandler = new Handler() {
|
||
|
@Override
|
||
|
public void handleMessage(Message msg) {
|
||
|
if (mInternalListener == null) {
|
||
|
return;
|
||
|
}
|
||
|
switch (msg.what) {
|
||
|
case MSG_BEGINNING_OF_SPEECH:
|
||
|
mInternalListener.onBeginningOfSpeech();
|
||
|
break;
|
||
|
case MSG_BUFFER_RECEIVED:
|
||
|
mInternalListener.onBufferReceived((byte[]) msg.obj);
|
||
|
break;
|
||
|
case MSG_END_OF_SPEECH:
|
||
|
mInternalListener.onEndOfSpeech();
|
||
|
break;
|
||
|
case MSG_ERROR:
|
||
|
mInternalListener.onError((Integer) msg.obj);
|
||
|
break;
|
||
|
case MSG_READY_FOR_SPEECH:
|
||
|
mInternalListener.onReadyForSpeech((Bundle) msg.obj);
|
||
|
break;
|
||
|
case MSG_RESULTS:
|
||
|
mInternalListener.onResults((Bundle) msg.obj);
|
||
|
break;
|
||
|
case MSG_PARTIAL_RESULTS:
|
||
|
mInternalListener.onPartialResults((Bundle) msg.obj);
|
||
|
break;
|
||
|
case MSG_RMS_CHANGED:
|
||
|
mInternalListener.onRmsChanged((Float) msg.obj);
|
||
|
break;
|
||
|
case MSG_ON_EVENT:
|
||
|
mInternalListener.onEvent(msg.arg1, (Bundle) msg.obj);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
public void onBeginningOfSpeech() {
|
||
|
Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget();
|
||
|
}
|
||
|
|
||
|
public void onBufferReceived(final byte[] buffer) {
|
||
|
Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget();
|
||
|
}
|
||
|
|
||
|
public void onEndOfSpeech() {
|
||
|
Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget();
|
||
|
}
|
||
|
|
||
|
public void onError(final int error) {
|
||
|
Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget();
|
||
|
}
|
||
|
|
||
|
public void onReadyForSpeech(final Bundle noiseParams) {
|
||
|
Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget();
|
||
|
}
|
||
|
|
||
|
public void onResults(final Bundle results) {
|
||
|
Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget();
|
||
|
}
|
||
|
|
||
|
public void onPartialResults(final Bundle results) {
|
||
|
Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget();
|
||
|
}
|
||
|
|
||
|
public void onRmsChanged(final float rmsdB) {
|
||
|
Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget();
|
||
|
}
|
||
|
|
||
|
public void onEvent(final int eventType, final Bundle params) {
|
||
|
Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params)
|
||
|
.sendToTarget();
|
||
|
}
|
||
|
}
|
||
|
}
|