Home | History | Annotate | Download | only in speech
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.speech;
     18 
     19 import android.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.ServiceConnection;
     23 import android.content.pm.ResolveInfo;
     24 import android.os.Bundle;
     25 import android.os.Handler;
     26 import android.os.IBinder;
     27 import android.os.Looper;
     28 import android.os.Message;
     29 import android.os.RemoteException;
     30 import android.provider.Settings;
     31 import android.text.TextUtils;
     32 import android.util.Log;
     33 
     34 import java.util.LinkedList;
     35 import java.util.List;
     36 import java.util.Queue;
     37 
     38 /**
     39  * This class provides access to the speech recognition service. This service allows access to the
     40  * speech recognizer. Do not instantiate this class directly, instead, call
     41  * {@link SpeechRecognizer#createSpeechRecognizer(Context)}. This class's methods must be
     42  * invoked only from the main application thread. Please note that the application must have
     43  * {@link android.Manifest.permission#RECORD_AUDIO} permission to use this class.
     44  */
     45 public class SpeechRecognizer {
     46     /** DEBUG value to enable verbose debug prints */
     47     private final static boolean DBG = false;
     48 
     49     /** Log messages identifier */
     50     private static final String TAG = "SpeechRecognizer";
     51 
     52     /**
     53      * Used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the
     54      * {@link RecognitionListener#onResults(Bundle)} and
     55      * {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible
     56      * recognition results, where the first element is the most likely candidate.
     57      */
     58     public static final String RESULTS_RECOGNITION = "results_recognition";
     59 
     60     /** Network operation timed out. */
     61     public static final int ERROR_NETWORK_TIMEOUT = 1;
     62 
     63     /** Other network related errors. */
     64     public static final int ERROR_NETWORK = 2;
     65 
     66     /** Audio recording error. */
     67     public static final int ERROR_AUDIO = 3;
     68 
     69     /** Server sends error status. */
     70     public static final int ERROR_SERVER = 4;
     71 
     72     /** Other client side errors. */
     73     public static final int ERROR_CLIENT = 5;
     74 
     75     /** No speech input */
     76     public static final int ERROR_SPEECH_TIMEOUT = 6;
     77 
     78     /** No recognition result matched. */
     79     public static final int ERROR_NO_MATCH = 7;
     80 
     81     /** RecognitionService busy. */
     82     public static final int ERROR_RECOGNIZER_BUSY = 8;
     83 
     84     /** Insufficient permissions */
     85     public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9;
     86 
     87     /** action codes */
     88     private final static int MSG_START = 1;
     89     private final static int MSG_STOP = 2;
     90     private final static int MSG_CANCEL = 3;
     91     private final static int MSG_CHANGE_LISTENER = 4;
     92 
     93     /** The actual RecognitionService endpoint */
     94     private IRecognitionService mService;
     95 
     96     /** The connection to the actual service */
     97     private Connection mConnection;
     98 
     99     /** Context with which the manager was created */
    100     private final Context mContext;
    101 
    102     /** Component to direct service intent to */
    103     private final ComponentName mServiceComponent;
    104 
    105     /** Handler that will execute the main tasks */
    106     private Handler mHandler = new Handler() {
    107         @Override
    108         public void handleMessage(Message msg) {
    109             switch (msg.what) {
    110                 case MSG_START:
    111                     handleStartListening((Intent) msg.obj);
    112                     break;
    113                 case MSG_STOP:
    114                     handleStopMessage();
    115                     break;
    116                 case MSG_CANCEL:
    117                     handleCancelMessage();
    118                     break;
    119                 case MSG_CHANGE_LISTENER:
    120                     handleChangeListener((RecognitionListener) msg.obj);
    121                     break;
    122             }
    123         }
    124     };
    125 
    126     /**
    127      * Temporary queue, saving the messages until the connection will be established, afterwards,
    128      * only mHandler will receive the messages
    129      */
    130     private final Queue<Message> mPendingTasks = new LinkedList<Message>();
    131 
    132     /** The Listener that will receive all the callbacks */
    133     private final InternalListener mListener = new InternalListener();
    134 
    135     /**
    136      * The right way to create a {@code SpeechRecognizer} is by using
    137      * {@link #createSpeechRecognizer} static factory method
    138      */
    139     private SpeechRecognizer(final Context context, final ComponentName serviceComponent) {
    140         mContext = context;
    141         mServiceComponent = serviceComponent;
    142     }
    143 
    144     /**
    145      * Basic ServiceConnection that records the mService variable. Additionally, on creation it
    146      * invokes the {@link IRecognitionService#startListening(Intent, IRecognitionListener)}.
    147      */
    148     private class Connection implements ServiceConnection {
    149 
    150         public void onServiceConnected(final ComponentName name, final IBinder service) {
    151             // always done on the application main thread, so no need to send message to mHandler
    152             mService = IRecognitionService.Stub.asInterface(service);
    153             if (DBG) Log.d(TAG, "onServiceConnected - Success");
    154             while (!mPendingTasks.isEmpty()) {
    155                 mHandler.sendMessage(mPendingTasks.poll());
    156             }
    157         }
    158 
    159         public void onServiceDisconnected(final ComponentName name) {
    160             // always done on the application main thread, so no need to send message to mHandler
    161             mService = null;
    162             mConnection = null;
    163             mPendingTasks.clear();
    164             if (DBG) Log.d(TAG, "onServiceDisconnected - Success");
    165         }
    166     }
    167 
    168     /**
    169      * Checks whether a speech recognition service is available on the system. If this method
    170      * returns {@code false}, {@link SpeechRecognizer#createSpeechRecognizer(Context)} will
    171      * fail.
    172      *
    173      * @param context with which {@code SpeechRecognizer} will be created
    174      * @return {@code true} if recognition is available, {@code false} otherwise
    175      */
    176     public static boolean isRecognitionAvailable(final Context context) {
    177         final List<ResolveInfo> list = context.getPackageManager().queryIntentServices(
    178                 new Intent(RecognitionService.SERVICE_INTERFACE), 0);
    179         return list != null && list.size() != 0;
    180     }
    181 
    182     /**
    183      * Factory method to create a new {@code SpeechRecognizer}. Please note that
    184      * {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any
    185      * command to the created {@code SpeechRecognizer}, otherwise no notifications will be
    186      * received.
    187      *
    188      * @param context in which to create {@code SpeechRecognizer}
    189      * @return a new {@code SpeechRecognizer}
    190      */
    191     public static SpeechRecognizer createSpeechRecognizer(final Context context) {
    192         return createSpeechRecognizer(context, null);
    193     }
    194 
    195     /**
    196      * Factory method to create a new {@code SpeechRecognizer}. Please note that
    197      * {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any
    198      * command to the created {@code SpeechRecognizer}, otherwise no notifications will be
    199      * received.
    200      *
    201      * Use this version of the method to specify a specific service to direct this
    202      * {@link SpeechRecognizer} to. Normally you would not use this; use
    203      * {@link #createSpeechRecognizer(Context)} instead to use the system default recognition
    204      * service.
    205      *
    206      * @param context in which to create {@code SpeechRecognizer}
    207      * @param serviceComponent the {@link ComponentName} of a specific service to direct this
    208      *        {@code SpeechRecognizer} to
    209      * @return a new {@code SpeechRecognizer}
    210      */
    211     public static SpeechRecognizer createSpeechRecognizer(final Context context,
    212             final ComponentName serviceComponent) {
    213         if (context == null) {
    214             throw new IllegalArgumentException("Context cannot be null)");
    215         }
    216         checkIsCalledFromMainThread();
    217         return new SpeechRecognizer(context, serviceComponent);
    218     }
    219 
    220     /**
    221      * Sets the listener that will receive all the callbacks. The previous unfinished commands will
    222      * be executed with the old listener, while any following command will be executed with the new
    223      * listener.
    224      *
    225      * @param listener listener that will receive all the callbacks from the created
    226      *        {@link SpeechRecognizer}, this must not be null.
    227      */
    228     public void setRecognitionListener(RecognitionListener listener) {
    229         checkIsCalledFromMainThread();
    230         putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener));
    231     }
    232 
    233     /**
    234      * Starts listening for speech. Please note that
    235      * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
    236      * no notifications will be received.
    237      *
    238      * @param recognizerIntent contains parameters for the recognition to be performed. The intent
    239      *        may also contain optional extras, see {@link RecognizerIntent}. If these values are
    240      *        not set explicitly, default values will be used by the recognizer.
    241      */
    242     public void startListening(final Intent recognizerIntent) {
    243         if (recognizerIntent == null) {
    244             throw new IllegalArgumentException("intent must not be null");
    245         }
    246         checkIsCalledFromMainThread();
    247         if (mConnection == null) { // first time connection
    248             mConnection = new Connection();
    249 
    250             Intent serviceIntent = new Intent(RecognitionService.SERVICE_INTERFACE);
    251 
    252             if (mServiceComponent == null) {
    253                 String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
    254                         Settings.Secure.VOICE_RECOGNITION_SERVICE);
    255 
    256                 if (TextUtils.isEmpty(serviceComponent)) {
    257                     Log.e(TAG, "no selected voice recognition service");
    258                     mListener.onError(ERROR_CLIENT);
    259                     return;
    260                 }
    261 
    262                 serviceIntent.setComponent(ComponentName.unflattenFromString(serviceComponent));
    263             } else {
    264                 serviceIntent.setComponent(mServiceComponent);
    265             }
    266 
    267             if (!mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
    268                 Log.e(TAG, "bind to recognition service failed");
    269                 mConnection = null;
    270                 mService = null;
    271                 mListener.onError(ERROR_CLIENT);
    272                 return;
    273             }
    274         }
    275         putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
    276     }
    277 
    278     /**
    279      * Stops listening for speech. Speech captured so far will be recognized as if the user had
    280      * stopped speaking at this point. Note that in the default case, this does not need to be
    281      * called, as the speech endpointer will automatically stop the recognizer listening when it
    282      * determines speech has completed. However, you can manipulate endpointer parameters directly
    283      * using the intent extras defined in {@link RecognizerIntent}, in which case you may sometimes
    284      * want to manually call this method to stop listening sooner. Please note that
    285      * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
    286      * no notifications will be received.
    287      */
    288     public void stopListening() {
    289         checkIsCalledFromMainThread();
    290         putMessage(Message.obtain(mHandler, MSG_STOP));
    291     }
    292 
    293     /**
    294      * Cancels the speech recognition. Please note that
    295      * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
    296      * no notifications will be received.
    297      */
    298     public void cancel() {
    299         checkIsCalledFromMainThread();
    300         putMessage(Message.obtain(mHandler, MSG_CANCEL));
    301     }
    302 
    303     private static void checkIsCalledFromMainThread() {
    304         if (Looper.myLooper() != Looper.getMainLooper()) {
    305             throw new RuntimeException(
    306                     "SpeechRecognizer should be used only from the application's main thread");
    307         }
    308     }
    309 
    310     private void putMessage(Message msg) {
    311         if (mService == null) {
    312             mPendingTasks.offer(msg);
    313         } else {
    314             mHandler.sendMessage(msg);
    315         }
    316     }
    317 
    318     /** sends the actual message to the service */
    319     private void handleStartListening(Intent recognizerIntent) {
    320         if (!checkOpenConnection()) {
    321             return;
    322         }
    323         try {
    324             mService.startListening(recognizerIntent, mListener);
    325             if (DBG) Log.d(TAG, "service start listening command succeded");
    326         } catch (final RemoteException e) {
    327             Log.e(TAG, "startListening() failed", e);
    328             mListener.onError(ERROR_CLIENT);
    329         }
    330     }
    331 
    332     /** sends the actual message to the service */
    333     private void handleStopMessage() {
    334         if (!checkOpenConnection()) {
    335             return;
    336         }
    337         try {
    338             mService.stopListening(mListener);
    339             if (DBG) Log.d(TAG, "service stop listening command succeded");
    340         } catch (final RemoteException e) {
    341             Log.e(TAG, "stopListening() failed", e);
    342             mListener.onError(ERROR_CLIENT);
    343         }
    344     }
    345 
    346     /** sends the actual message to the service */
    347     private void handleCancelMessage() {
    348         if (!checkOpenConnection()) {
    349             return;
    350         }
    351         try {
    352             mService.cancel(mListener);
    353             if (DBG) Log.d(TAG, "service cancel command succeded");
    354         } catch (final RemoteException e) {
    355             Log.e(TAG, "cancel() failed", e);
    356             mListener.onError(ERROR_CLIENT);
    357         }
    358     }
    359 
    360     private boolean checkOpenConnection() {
    361         if (mService != null) {
    362             return true;
    363         }
    364         mListener.onError(ERROR_CLIENT);
    365         Log.e(TAG, "not connected to the recognition service");
    366         return false;
    367     }
    368 
    369     /** changes the listener */
    370     private void handleChangeListener(RecognitionListener listener) {
    371         if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener);
    372         mListener.mInternalListener = listener;
    373     }
    374 
    375     /**
    376      * Destroys the {@code SpeechRecognizer} object.
    377      */
    378     public void destroy() {
    379         if (mConnection != null) {
    380             mContext.unbindService(mConnection);
    381         }
    382         mPendingTasks.clear();
    383         mService = null;
    384         mConnection = null;
    385         mListener.mInternalListener = null;
    386     }
    387 
    388     /**
    389      * Internal wrapper of IRecognitionListener which will propagate the results to
    390      * RecognitionListener
    391      */
    392     private class InternalListener extends IRecognitionListener.Stub {
    393         private RecognitionListener mInternalListener;
    394 
    395         private final static int MSG_BEGINNING_OF_SPEECH = 1;
    396         private final static int MSG_BUFFER_RECEIVED = 2;
    397         private final static int MSG_END_OF_SPEECH = 3;
    398         private final static int MSG_ERROR = 4;
    399         private final static int MSG_READY_FOR_SPEECH = 5;
    400         private final static int MSG_RESULTS = 6;
    401         private final static int MSG_PARTIAL_RESULTS = 7;
    402         private final static int MSG_RMS_CHANGED = 8;
    403         private final static int MSG_ON_EVENT = 9;
    404 
    405         private final Handler mInternalHandler = new Handler() {
    406             @Override
    407             public void handleMessage(Message msg) {
    408                 if (mInternalListener == null) {
    409                     return;
    410                 }
    411                 switch (msg.what) {
    412                     case MSG_BEGINNING_OF_SPEECH:
    413                         mInternalListener.onBeginningOfSpeech();
    414                         break;
    415                     case MSG_BUFFER_RECEIVED:
    416                         mInternalListener.onBufferReceived((byte[]) msg.obj);
    417                         break;
    418                     case MSG_END_OF_SPEECH:
    419                         mInternalListener.onEndOfSpeech();
    420                         break;
    421                     case MSG_ERROR:
    422                         mInternalListener.onError((Integer) msg.obj);
    423                         break;
    424                     case MSG_READY_FOR_SPEECH:
    425                         mInternalListener.onReadyForSpeech((Bundle) msg.obj);
    426                         break;
    427                     case MSG_RESULTS:
    428                         mInternalListener.onResults((Bundle) msg.obj);
    429                         break;
    430                     case MSG_PARTIAL_RESULTS:
    431                         mInternalListener.onPartialResults((Bundle) msg.obj);
    432                         break;
    433                     case MSG_RMS_CHANGED:
    434                         mInternalListener.onRmsChanged((Float) msg.obj);
    435                         break;
    436                     case MSG_ON_EVENT:
    437                         mInternalListener.onEvent(msg.arg1, (Bundle) msg.obj);
    438                         break;
    439                 }
    440             }
    441         };
    442 
    443         public void onBeginningOfSpeech() {
    444             Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget();
    445         }
    446 
    447         public void onBufferReceived(final byte[] buffer) {
    448             Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget();
    449         }
    450 
    451         public void onEndOfSpeech() {
    452             Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget();
    453         }
    454 
    455         public void onError(final int error) {
    456             Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget();
    457         }
    458 
    459         public void onReadyForSpeech(final Bundle noiseParams) {
    460             Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget();
    461         }
    462 
    463         public void onResults(final Bundle results) {
    464             Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget();
    465         }
    466 
    467         public void onPartialResults(final Bundle results) {
    468             Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget();
    469         }
    470 
    471         public void onRmsChanged(final float rmsdB) {
    472             Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget();
    473         }
    474 
    475         public void onEvent(final int eventType, final Bundle params) {
    476             Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params)
    477                     .sendToTarget();
    478         }
    479     }
    480 }
    481