summaryrefslogtreecommitdiffstats
path: root/core/java/android/speech
diff options
context:
space:
mode:
authorBjorn Bringert <bringert@android.com>2010-02-05 06:25:45 -0800
committerAndroid (Google) Code Review <android-gerrit@google.com>2010-02-05 06:25:45 -0800
commit1009e82ef2985a55f0afcbfa7f31bddb73039982 (patch)
tree1f707050b2c0e1b8e06b2cb7dd71fab280d70303 /core/java/android/speech
parent17fe395dab5aa80241e2e6e75100e7936746aa56 (diff)
parent3da3cad97269d694a6153771fb4a0c3775ca6ab5 (diff)
downloadframeworks_base-1009e82ef2985a55f0afcbfa7f31bddb73039982.zip
frameworks_base-1009e82ef2985a55f0afcbfa7f31bddb73039982.tar.gz
frameworks_base-1009e82ef2985a55f0afcbfa7f31bddb73039982.tar.bz2
Merge "Adding speech recognition service public API"
Diffstat (limited to 'core/java/android/speech')
-rw-r--r--core/java/android/speech/IRecognitionListener.aidl12
-rw-r--r--core/java/android/speech/IRecognitionService.aidl12
-rw-r--r--core/java/android/speech/RecognitionListener.java17
-rw-r--r--core/java/android/speech/RecognitionManager.java389
-rw-r--r--core/java/android/speech/RecognitionService.java295
5 files changed, 569 insertions, 156 deletions
diff --git a/core/java/android/speech/IRecognitionListener.aidl b/core/java/android/speech/IRecognitionListener.aidl
index 5b48bd2..2a0f986 100644
--- a/core/java/android/speech/IRecognitionListener.aidl
+++ b/core/java/android/speech/IRecognitionListener.aidl
@@ -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);
}
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index ca9af15..8b27e63 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -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);
}
diff --git a/core/java/android/speech/RecognitionListener.java b/core/java/android/speech/RecognitionListener.java
index eab3f40..5434887 100644
--- a/core/java/android/speech/RecognitionListener.java
+++ b/core/java/android/speech/RecognitionListener.java
@@ -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);
}
diff --git a/core/java/android/speech/RecognitionManager.java b/core/java/android/speech/RecognitionManager.java
index 79ae480..0d25b2f 100644
--- a/core/java/android/speech/RecognitionManager.java
+++ b/core/java/android/speech/RecognitionManager.java
@@ -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,92 +53,118 @@ 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";
-
- /** 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;
-
- /** Listener that will receive all the callbacks */
- private final RecognitionListener mListener;
-
- /** Helper class wrapping the IRecognitionListener */
- private final InternalRecognitionListener mInternalRecognitionListener;
+ public static final String RESULTS_RECOGNITION = "results_recognition";
/** Network operation timed out. */
- public static final int NETWORK_TIMEOUT_ERROR = 1;
+ public static final int ERROR_NETWORK_TIMEOUT = 1;
/** Other network related errors. */
- public static final int NETWORK_ERROR = 2;
+ public static final int ERROR_NETWORK = 2;
/** Audio recording error. */
- public static final int AUDIO_ERROR = 3;
+ public static final int ERROR_AUDIO = 3;
/** Server sends error status. */
- public static final int SERVER_ERROR = 4;
+ public static final int ERROR_SERVER = 4;
/** Other client side errors. */
- public static final int CLIENT_ERROR = 5;
+ public static final int ERROR_CLIENT = 5;
/** No speech input */
- public static final int SPEECH_TIMEOUT_ERROR = 6;
+ public static final int ERROR_SPEECH_TIMEOUT = 6;
/** No recognition result matched. */
- public static final int NO_MATCH_ERROR = 7;
+ public static final int ERROR_NO_MATCH = 7;
/** RecognitionService busy. */
- public static final int SERVER_BUSY_ERROR = 8;
+ 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;
+
+ /** 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() {
+ 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) {
- return; // service is not connected, but no need to reconnect at this point
+ mPendingTasks.offer(msg);
+ } else {
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ /** sends the actual message to the service */
+ private void handleStartListening(Intent recognizerIntent) {
+ 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() {
try {
- mService.stopListening();
+ 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();
}
}
}
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
new file mode 100644
index 0000000..0d74960
--- /dev/null
+++ b/core/java/android/speech/RecognitionService.java
@@ -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);
+ }
+ }
+}