Merge "Adding speech recognition service public API"

This commit is contained in:
Bjorn Bringert
2010-02-05 06:25:45 -08:00
committed by Android (Google) Code Review
6 changed files with 810 additions and 188 deletions

View File

@@ -135042,7 +135042,7 @@
<parameter name="error" type="int">
</parameter>
</method>
<method name="onInit"
<method name="onEvent"
return="void"
abstract="true"
native="false"
@@ -135052,6 +135052,10 @@
deprecated="not deprecated"
visibility="public"
>
<parameter name="eventType" type="int">
</parameter>
<parameter name="params" type="android.os.Bundle">
</parameter>
</method>
<method name="onPartialResults"
return="void"
@@ -135137,10 +135141,6 @@
>
<parameter name="context" type="android.content.Context">
</parameter>
<parameter name="listener" type="android.speech.RecognitionListener">
</parameter>
<parameter name="recognizerIntent" type="android.content.Intent">
</parameter>
</method>
<method name="destroy"
return="void"
@@ -135166,6 +135166,19 @@
<parameter name="context" type="android.content.Context">
</parameter>
</method>
<method name="setRecognitionListener"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="listener" type="android.speech.RecognitionListener">
</parameter>
</method>
<method name="startListening"
return="void"
abstract="false"
@@ -135190,7 +135203,7 @@
visibility="public"
>
</method>
<field name="AUDIO_ERROR"
<field name="ERROR_AUDIO"
type="int"
transient="false"
volatile="false"
@@ -135201,7 +135214,7 @@
visibility="public"
>
</field>
<field name="CLIENT_ERROR"
<field name="ERROR_CLIENT"
type="int"
transient="false"
volatile="false"
@@ -135212,7 +135225,7 @@
visibility="public"
>
</field>
<field name="MANAGER_NOT_INITIALIZED_ERROR"
<field name="ERROR_INSUFFICIENT_PERMISSIONS"
type="int"
transient="false"
volatile="false"
@@ -135223,7 +135236,7 @@
visibility="public"
>
</field>
<field name="NETWORK_ERROR"
<field name="ERROR_NETWORK"
type="int"
transient="false"
volatile="false"
@@ -135234,7 +135247,7 @@
visibility="public"
>
</field>
<field name="NETWORK_TIMEOUT_ERROR"
<field name="ERROR_NETWORK_TIMEOUT"
type="int"
transient="false"
volatile="false"
@@ -135245,7 +135258,7 @@
visibility="public"
>
</field>
<field name="NO_MATCH_ERROR"
<field name="ERROR_NO_MATCH"
type="int"
transient="false"
volatile="false"
@@ -135256,18 +135269,7 @@
visibility="public"
>
</field>
<field name="RECOGNITION_RESULTS_STRING_ARRAY"
type="java.lang.String"
transient="false"
volatile="false"
value="&quot;recognition_results_string_array&quot;"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="SERVER_BUSY_ERROR"
<field name="ERROR_RECOGNIZER_BUSY"
type="int"
transient="false"
volatile="false"
@@ -135278,7 +135280,7 @@
visibility="public"
>
</field>
<field name="SERVER_ERROR"
<field name="ERROR_SERVER"
type="int"
transient="false"
volatile="false"
@@ -135289,7 +135291,7 @@
visibility="public"
>
</field>
<field name="SPEECH_TIMEOUT_ERROR"
<field name="ERROR_SPEECH_TIMEOUT"
type="int"
transient="false"
volatile="false"
@@ -135300,6 +135302,213 @@
visibility="public"
>
</field>
<field name="RESULTS_RECOGNITION"
type="java.lang.String"
transient="false"
volatile="false"
value="&quot;results_recognition&quot;"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
</class>
<class name="RecognitionService"
extends="android.app.Service"
abstract="true"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<constructor name="RecognitionService"
type="android.speech.RecognitionService"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</constructor>
<method name="onBind"
return="android.os.IBinder"
abstract="false"
native="false"
synchronized="false"
static="false"
final="true"
deprecated="not deprecated"
visibility="public"
>
<parameter name="intent" type="android.content.Intent">
</parameter>
</method>
<method name="onCancel"
return="void"
abstract="true"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="protected"
>
<parameter name="listener" type="android.speech.RecognitionService.Callback">
</parameter>
</method>
<method name="onStartListening"
return="void"
abstract="true"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="protected"
>
<parameter name="recognizerIntent" type="android.content.Intent">
</parameter>
<parameter name="listener" type="android.speech.RecognitionService.Callback">
</parameter>
</method>
<method name="onStopListening"
return="void"
abstract="true"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="protected"
>
<parameter name="listener" type="android.speech.RecognitionService.Callback">
</parameter>
</method>
</class>
<class name="RecognitionService.Callback"
extends="java.lang.Object"
abstract="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<method name="beginningOfSpeech"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
<method name="bufferReceived"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="buffer" type="byte[]">
</parameter>
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
<method name="endOfSpeech"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
<method name="error"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="error" type="int">
</parameter>
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
<method name="partialResults"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="partialResults" type="android.os.Bundle">
</parameter>
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
<method name="readyForSpeech"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="params" type="android.os.Bundle">
</parameter>
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
<method name="results"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="results" type="android.os.Bundle">
</parameter>
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
<method name="rmsChanged"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="rmsdB" type="float">
</parameter>
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
</class>
<class name="RecognizerIntent"
extends="java.lang.Object"

View File

@@ -19,12 +19,12 @@ package android.speech;
import android.os.Bundle;
/**
* Listener for speech recognition events, used with RecognitionService.
* Listener for speech recognition events, used with RecognitionService.
* This gives you both the final recognition results, as well as various
* intermediate events that can be used to show visual feedback to the user.
* {@hide}
*/
interface IRecognitionListener {
oneway interface IRecognitionListener {
/**
* Called when the endpointer is ready for the user to start speaking.
*
@@ -76,4 +76,12 @@ interface IRecognitionListener {
* @param results a Bundle containing the current most likely result.
*/
void onPartialResults(in Bundle results);
/**
* Reserved for adding future events.
*
* @param eventType the type of the occurred event
* @param params a Bundle containing the passed parameters
*/
void onEvent(in int eventType, in Bundle params);
}

View File

@@ -29,7 +29,7 @@ import android.speech.IRecognitionListener;
* accessing recognition service.
* {@hide}
*/
interface IRecognitionService {
oneway interface IRecognitionService {
/**
* Starts listening for speech. Please note that the recognition service supports
* one listener only, therefore, if this function is called from two different threads,
@@ -38,7 +38,7 @@ interface IRecognitionService {
* @param recognizerIntent the intent from which the invocation occurred. Additionally,
* this intent can contain extra parameters to manipulate the behavior of the recognition
* client. For more information see {@link RecognizerIntent}.
* @param listener to receive callbacks
* @param listener to receive callbacks, note that this must be non-null
*/
void startListening(in Intent recognizerIntent, in IRecognitionListener listener);
@@ -46,11 +46,15 @@ interface IRecognitionService {
* Stops listening for speech. Speech captured so far will be recognized as
* if the user had stopped speaking at this point. The function has no effect unless it
* is called during the speech capturing.
*
* @param listener to receive callbacks, note that this must be non-null
*/
void stopListening();
void stopListening(in IRecognitionListener listener);
/**
* Cancels the speech recognition.
*
* @param listener to receive callbacks, note that this must be non-null
*/
void cancel();
void cancel(in IRecognitionListener listener);
}

View File

@@ -24,12 +24,6 @@ import android.os.Bundle;
* Application main thread.
*/
public interface RecognitionListener {
/**
* Called when RecognitionManager is successfully initialized
*/
void onInit();
/**
* Called when the endpointer is ready for the user to start speaking.
*
@@ -76,7 +70,7 @@ public interface RecognitionListener {
*
* @param results the recognition results. To retrieve the results in {@code
* ArrayList&lt;String&gt;} format use {@link Bundle#getStringArrayList(String)} with
* {@link RecognitionManager#RECOGNITION_RESULTS_STRING_ARRAY} as a parameter
* {@link RecognitionManager#RESULTS_RECOGNITION} as a parameter
*/
void onResults(Bundle results);
@@ -89,8 +83,15 @@ public interface RecognitionListener {
*
* @param partialResults the returned results. To retrieve the results in
* ArrayList&lt;String&gt; format use {@link Bundle#getStringArrayList(String)} with
* {@link RecognitionManager#RECOGNITION_RESULTS_STRING_ARRAY} as a parameter
* {@link RecognitionManager#RESULTS_RECOGNITION} as a parameter
*/
void onPartialResults(Bundle partialResults);
/**
* Reserved for adding future events.
*
* @param eventType the type of the occurred event
* @param params a Bundle containing the passed parameters
*/
void onEvent(int eventType, Bundle params);
}

View File

@@ -22,17 +22,23 @@ 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.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 RecognitionManager#createRecognitionManager(Context, RecognitionListener, Intent)}. This
* class is not thread safe and must be synchronized externally if accessed from multiple threads.
* {@link RecognitionManager#createRecognitionManager(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 RecognitionManager {
/** DEBUG value to enable verbose debug prints */
@@ -47,8 +53,40 @@ public class RecognitionManager {
* {@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 RECOGNITION_RESULTS_STRING_ARRAY =
"recognition_results_string_array";
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;
@@ -59,80 +97,74 @@ public class RecognitionManager {
/** Context with which the manager was created */
private final Context mContext;
/** Listener that will receive all the callbacks */
private final RecognitionListener mListener;
/** Helper class wrapping the IRecognitionListener */
private final InternalRecognitionListener mInternalRecognitionListener;
/** Network operation timed out. */
public static final int NETWORK_TIMEOUT_ERROR = 1;
/** Other network related errors. */
public static final int NETWORK_ERROR = 2;
/** Audio recording error. */
public static final int AUDIO_ERROR = 3;
/** Server sends error status. */
public static final int SERVER_ERROR = 4;
/** Other client side errors. */
public static final int CLIENT_ERROR = 5;
/** No speech input */
public static final int SPEECH_TIMEOUT_ERROR = 6;
/** No recognition result matched. */
public static final int NO_MATCH_ERROR = 7;
/** RecognitionService busy. */
public static final int SERVER_BUSY_ERROR = 8;
/** 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;
}
}
};
/**
* RecognitionManager was not initialized yet, most probably because
* {@link RecognitionListener#onInit()} was not called yet.
* Temporary queue, saving the messages until the connection will be established, afterwards,
* only mHandler will receive the messages
*/
public static final int MANAGER_NOT_INITIALIZED_ERROR = 9;
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 RecognitionManager is by using
* The right way to create a {@code RecognitionManager} is by using
* {@link #createRecognitionManager} static factory method
*/
private RecognitionManager(final RecognitionListener listener, final Context context) {
mInternalRecognitionListener = new InternalRecognitionListener();
private RecognitionManager(final Context context) {
mContext = context;
mListener = listener;
}
/**
* Basic ServiceConnection which just records mService variable.
* Basic ServiceConnection that records the mService variable. Additionally, on creation it
* invokes the {@link IRecognitionService#startListening(Intent, IRecognitionListener)}.
*/
private class Connection implements ServiceConnection {
public synchronized void onServiceConnected(final ComponentName name,
final IBinder service) {
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 (mListener != null) {
mListener.onInit();
}
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 RecognitionManager#createRecognitionManager(Context, RecognitionListener, Intent)}
* will fail.
* returns {@code false}, {@link RecognitionManager#createRecognitionManager(Context)} will
* fail.
*
* @param context with which RecognitionManager will be created
* @param context with which {@code RecognitionManager} will be created
* @return {@code true} if recognition is available, {@code false} otherwise
*/
public static boolean isRecognitionAvailable(final Context context) {
@@ -142,78 +174,61 @@ public class RecognitionManager {
}
/**
* Factory method to create a new RecognitionManager
* Factory method to create a new {@code RecognitionManager}, please note that
* {@link #setRecognitionListener(RecognitionListener)} must be called before dispatching any
* command to the created {@code RecognitionManager}.
*
* @param context in which to create RecognitionManager
* @param listener that will receive all the callbacks from the created
* {@link RecognitionManager}
* @param recognizerIntent contains initialization parameters for the speech recognizer. The
* intent action should be {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH}. Future
* versions of this API may add startup parameters for speech recognizer.
* @return null if a recognition service implementation is not installed or if speech
* recognition is not supported by the device, otherwise a new RecognitionManager is
* returned. The created RecognitionManager can only be used after the
* {@link RecognitionListener#onInit()} method has been called.
* @param context in which to create {@code RecognitionManager}
* @return a new {@code RecognitionManager}
*/
public static RecognitionManager createRecognitionManager(final Context context,
final RecognitionListener listener, final Intent recognizerIntent) {
if (context == null || recognizerIntent == null) {
throw new IllegalArgumentException(
"Context and recognizerListener argument cannot be null)");
public static RecognitionManager createRecognitionManager(final Context context) {
if (context == null) {
throw new IllegalArgumentException("Context cannot be null)");
}
RecognitionManager manager = new RecognitionManager(listener, context);
manager.mConnection = manager.new Connection();
if (!context.bindService(recognizerIntent, manager.mConnection, Context.BIND_AUTO_CREATE)) {
Log.e(TAG, "bind to recognition service failed");
listener.onError(CLIENT_ERROR);
return null;
}
return manager;
checkIsCalledFromMainThread();
return new RecognitionManager(context);
}
/**
* Checks whether the service is connected
*
* @param functionName from which the call originated
* @return {@code true} if the service was successfully initialized, {@code false} otherwise
* 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 RecognitionManager}, this must not be null.
*/
private boolean connectToService(final String functionName) {
if (mService != null) {
return true;
}
if (mConnection == null) {
if (DBG) Log.d(TAG, "restarting connection to the recognition service");
mConnection = new Connection();
mContext.bindService(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), mConnection,
Context.BIND_AUTO_CREATE);
}
mInternalRecognitionListener.onError(MANAGER_NOT_INITIALIZED_ERROR);
Log.e(TAG, functionName + " was called before service connection was initialized");
return false;
public void setRecognitionListener(RecognitionListener listener) {
checkIsCalledFromMainThread();
putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener));
}
/**
* Starts listening for speech.
* Starts listening for speech. Please note that
* {@link #setRecognitionListener(RecognitionListener)} must be called beforehand, otherwise a
* {@link RuntimeException} will be thrown.
*
* @param recognizerIntent contains parameters for the recognition to be performed. The intent
* action should be {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH}. 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.
* 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(Intent recognizerIntent) {
public void startListening(final Intent recognizerIntent) {
if (recognizerIntent == null) {
throw new IllegalArgumentException("recognizerIntent argument cannot be null");
throw new IllegalArgumentException("intent must not be null");
}
if (!connectToService("startListening")) {
return; // service is not connected yet, reconnect in progress
}
try {
mService.startListening(recognizerIntent, mInternalRecognitionListener);
if (DBG) Log.d(TAG, "service start listening command succeded");
} catch (final RemoteException e) {
Log.e(TAG, "startListening() failed", e);
mInternalRecognitionListener.onError(CLIENT_ERROR);
checkIsCalledFromMainThread();
checkIsCommandAllowed();
if (mConnection == null) { // first time connection
mConnection = new Connection();
if (!mContext.bindService(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
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));
}
/**
@@ -222,100 +237,190 @@ public class RecognitionManager {
* 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.
* want to manually call this method to stop listening sooner. Please note that
* {@link #setRecognitionListener(RecognitionListener)} must be called beforehand, otherwise a
* {@link RuntimeException} will be thrown.
*/
public void stopListening() {
if (mService == null) {
return; // service is not connected, but no need to reconnect at this point
checkIsCalledFromMainThread();
checkIsCommandAllowed();
putMessage(Message.obtain(mHandler, MSG_STOP));
}
/**
* Cancels the speech recognition. Please note that
* {@link #setRecognitionListener(RecognitionListener)} must be called beforehand, otherwise a
* {@link RuntimeException} will be thrown.
*/
public void cancel() {
checkIsCalledFromMainThread();
checkIsCommandAllowed();
putMessage(Message.obtain(mHandler, MSG_CANCEL));
}
private static void checkIsCalledFromMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new RuntimeException(
"RecognitionManager should be used only from the application's main thread");
}
}
private void checkIsCommandAllowed() {
if (mService == null && mPendingTasks.isEmpty()) { // setListener message must be there
throw new IllegalStateException("Listener must be set before any command is called");
}
}
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) {
try {
mService.stopListening();
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() {
try {
mService.stopListening(mListener);
if (DBG) Log.d(TAG, "service stop listening command succeded");
} catch (final RemoteException e) {
Log.e(TAG, "stopListening() failed", e);
mInternalRecognitionListener.onError(CLIENT_ERROR);
mListener.onError(ERROR_CLIENT);
}
}
/**
* Cancels the speech recognition.
*/
public void cancel() {
if (mService == null) {
return; // service is not connected, but no need to reconnect at this point
}
/** sends the actual message to the service */
private void handleCancelMessage() {
try {
mService.cancel();
mService.cancel(mListener);
if (DBG) Log.d(TAG, "service cancel command succeded");
} catch (final RemoteException e) {
Log.e(TAG, "cancel() failed", e);
mInternalRecognitionListener.onError(CLIENT_ERROR);
mListener.onError(ERROR_CLIENT);
}
}
/** changes the listener */
private void handleChangeListener(RecognitionListener listener) {
if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener);
mListener.mInternalListener = listener;
}
/**
* Destroys the RecognitionManager object. Note that after calling this method all method calls
* on this object will fail, triggering {@link RecognitionListener#onError}.
* Destroys the {@code RecognitionManager} object. Note that after calling this method all
* method calls on this object will fail, triggering {@link RecognitionListener#onError}.
*/
public void destroy() {
if (mConnection != null) {
mContext.unbindService(mConnection);
}
mPendingTasks.clear();
mService = null;
mConnection = null;
}
/**
* Internal wrapper of IRecognitionListener which will propagate the results
* to RecognitionListener
* Internal wrapper of IRecognitionListener which will propagate the results to
* RecognitionListener
*/
private class InternalRecognitionListener extends IRecognitionListener.Stub {
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() {
if (mListener != null) {
mListener.onBeginningOfSpeech();
}
Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget();
}
public void onBufferReceived(final byte[] buffer) {
if (mListener != null) {
mListener.onBufferReceived(buffer);
}
Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget();
}
public void onEndOfSpeech() {
if (mListener != null) {
mListener.onEndOfSpeech();
}
Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget();
}
public void onError(final int error) {
if (mListener != null) {
mListener.onError(error);
}
Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget();
}
public void onReadyForSpeech(final Bundle noiseParams) {
if (mListener != null) {
mListener.onReadyForSpeech(noiseParams);
}
Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget();
}
public void onResults(final Bundle results) {
if (mListener != null) {
mListener.onResults(results);
}
Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget();
}
public void onPartialResults(final Bundle results) {
if (mListener != null) {
mListener.onPartialResults(results);
}
Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget();
}
public void onRmsChanged(final float rmsdB) {
if (mListener != null) {
mListener.onRmsChanged(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();
}
}
}

View File

@@ -0,0 +1,295 @@
/*
* 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.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
/**
* This class provides a base class for recognition service implementations. This class should be
* extended only in case you wish to implement a new speech recognizer. Please not that the
* implementation of this service is state-less.
*/
public abstract class RecognitionService extends Service {
/** Log messages identifier */
private static final String TAG = "RecognitionService";
/** Debugging flag */
private static final boolean DBG = false;
/**
* The current callback of an application that invoked the
* {@link RecognitionService#onStartListening(Intent, Callback)} method
*/
private Callback mCurrentCallback = null;
private static final int MSG_START_LISTENING = 1;
private static final int MSG_STOP_LISTENING = 2;
private static final int MSG_CANCEL = 3;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_START_LISTENING:
StartListeningArgs args = (StartListeningArgs) msg.obj;
dispatchStartListening(args.mIntent, args.mListener);
break;
case MSG_STOP_LISTENING:
dispatchStopListening((IRecognitionListener) msg.obj);
break;
case MSG_CANCEL:
dispatchCancel((IRecognitionListener) msg.obj);
}
}
};
private void dispatchStartListening(Intent intent, IRecognitionListener listener) {
if (mCurrentCallback == null) {
if (DBG) Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder());
mCurrentCallback = new Callback(listener);
RecognitionService.this.onStartListening(intent, mCurrentCallback);
} else {
try {
listener.onError(RecognitionManager.ERROR_RECOGNIZER_BUSY);
} catch (RemoteException e) {
Log.d(TAG, "onError call from startListening failed");
}
Log.i(TAG, "concurrent startListening received - ignoring this call");
}
}
private void dispatchStopListening(IRecognitionListener listener) {
try {
if (mCurrentCallback == null) {
listener.onError(RecognitionManager.ERROR_CLIENT);
Log.w(TAG, "stopListening called with no preceding startListening - ignoring");
} else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
listener.onError(RecognitionManager.ERROR_RECOGNIZER_BUSY);
Log.w(TAG, "stopListening called by other caller than startListening - ignoring");
} else { // the correct state
RecognitionService.this.onStopListening(mCurrentCallback);
}
} catch (RemoteException e) { // occurs if onError fails
Log.d(TAG, "onError call from stopListening failed");
}
}
private void dispatchCancel(IRecognitionListener listener) {
if (mCurrentCallback == null) {
Log.w(TAG, "cancel called with no preceding startListening - ignoring");
} else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
} else { // the correct state
RecognitionService.this.onCancel(mCurrentCallback);
mCurrentCallback = null;
if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
}
}
private class StartListeningArgs {
public final Intent mIntent;
public final IRecognitionListener mListener;
public StartListeningArgs(Intent intent, IRecognitionListener listener) {
this.mIntent = intent;
this.mListener = listener;
}
}
/** Binder of the recognition service */
private final IRecognitionService.Stub mBinder = new IRecognitionService.Stub() {
public void startListening(Intent recognizerIntent, IRecognitionListener listener) {
if (DBG) Log.d(TAG, "startListening called by:" + listener.asBinder());
if (checkPermissions(listener)) {
mHandler.sendMessage(Message.obtain(mHandler, MSG_START_LISTENING,
new StartListeningArgs(recognizerIntent, listener)));
}
}
public void stopListening(IRecognitionListener listener) {
if (DBG) Log.d(TAG, "stopListening called by:" + listener.asBinder());
if (checkPermissions(listener)) {
mHandler.sendMessage(Message.obtain(mHandler, MSG_STOP_LISTENING, listener));
}
}
public void cancel(IRecognitionListener listener) {
if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder());
if (checkPermissions(listener)) {
mHandler.sendMessage(Message.obtain(mHandler, MSG_CANCEL, listener));
}
}
};
/**
* Checks whether the caller has sufficient permissions
*
* @param listener to send the error message to in case of error
* @return {@code true} if the caller has enough permissions, {@code false} otherwise
*/
private boolean checkPermissions(IRecognitionListener listener) {
if (DBG) Log.d(TAG, "checkPermissions");
if (RecognitionService.this.checkCallingOrSelfPermission(android.Manifest.permission.
RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
return true;
}
try {
Log.e(TAG, "call for recognition service without RECORD_AUDIO permissions");
listener.onError(RecognitionManager.ERROR_INSUFFICIENT_PERMISSIONS);
} catch (RemoteException re) {
Log.e(TAG, "sending ERROR_INSUFFICIENT_PERMISSIONS message failed", re);
}
return false;
}
/**
* Notifies the service that it should start listening for speech.
*
* @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 should be used by the recognizer.
* @param listener that will receive the service's callbacks
*/
protected abstract void onStartListening(Intent recognizerIntent, Callback listener);
/**
* Notifies the service that it should cancel the speech recognition.
*/
protected abstract void onCancel(Callback listener);
/**
* Notifies the service that it should stop listening for speech. Speech captured so far should
* be recognized as if the user had stopped speaking at this point. This method is only called
* if the application calls it explicitly.
*/
protected abstract void onStopListening(Callback listener);
@Override
public final IBinder onBind(final Intent intent) {
if (DBG) Log.d(TAG, "onBind, intent=" + intent);
return mBinder;
}
/**
* This class receives callbacks from the speech recognition service and forwards them to the
* user. An instance of this class is passed to the
* {@link RecognitionService#onStartListening(Intent, Callback)} method. Recognizers may call
* these methods on any thread.
*/
public class Callback {
private final IRecognitionListener mListener;
private Callback(IRecognitionListener listener) {
mListener = listener;
}
/**
* The service should call this method when the user has started to speak.
*/
public void beginningOfSpeech() throws RemoteException {
if (DBG) Log.d(TAG, "beginningOfSpeech");
mListener.onBeginningOfSpeech();
}
/**
* The service should call this method when sound has been received. The purpose of this
* function is to allow giving feedback to the user regarding the captured audio.
*
* @param buffer a buffer containing a sequence of big-endian 16-bit integers representing a
* single channel audio stream. The sample rate is implementation dependent.
*/
public void bufferReceived(byte[] buffer) throws RemoteException {
mListener.onBufferReceived(buffer);
}
/**
* The service should call this method after the user stops speaking.
*/
public void endOfSpeech() throws RemoteException {
mListener.onEndOfSpeech();
}
/**
* The service should call this method when a network or recognition error occurred.
*
* @param error code is defined in {@link RecognitionManager}
*/
public void error(int error) throws RemoteException {
mCurrentCallback = null;
mListener.onError(error);
}
/**
* The service should call this method when partial recognition results are available. This
* method can be called at any time between {@link #beginningOfSpeech()} and
* {@link #results(Bundle)} when partial results are ready. This method may be called zero,
* one or multiple times for each call to {@link RecognitionManager#startListening(Intent)},
* depending on the speech recognition service implementation.
*
* @param partialResults the returned results. To retrieve the results in
* ArrayList&lt;String&gt; format use {@link Bundle#getStringArrayList(String)} with
* {@link RecognitionManager#RESULTS_RECOGNITION} as a parameter
*/
public void partialResults(Bundle partialResults) throws RemoteException {
mListener.onPartialResults(partialResults);
}
/**
* The service should call this method when the endpointer is ready for the user to start
* speaking.
*
* @param params parameters set by the recognition service. Reserved for future use.
*/
public void readyForSpeech(Bundle params) throws RemoteException {
mListener.onReadyForSpeech(params);
}
/**
* The service should call this method when recognition results are ready.
*
* @param results the recognition results. To retrieve the results in {@code
* ArrayList&lt;String&gt;} format use {@link Bundle#getStringArrayList(String)} with
* {@link RecognitionManager#RESULTS_RECOGNITION} as a parameter
*/
public void results(Bundle results) throws RemoteException {
mCurrentCallback = null;
mListener.onResults(results);
}
/**
* The service should call this method when the sound level in the audio stream has changed.
* There is no guarantee that this method will be called.
*
* @param rmsdB the new RMS dB value
*/
public void rmsChanged(float rmsdB) throws RemoteException {
mListener.onRmsChanged(rmsdB);
}
}
}