FileDocCategorySizeDatePackage
RecognizerEngine.javaAPI DocAndroid 1.5 API48060Wed May 06 22:42:48 BST 2009com.android.voicedialer

RecognizerEngine

public class RecognizerEngine extends Object
This class knows how to recognize speech. A usage cycle is as follows:
  • Create with a reference to the {@link VoiceDialerActivity}.
  • Signal the user to start speaking with the Vibrator or beep.
  • Start audio input by creating a {@link MicrophoneInputStream}.
  • Create and configure a {@link Recognizer}.
  • Fetch a List of {@link VoiceContact} and determine if there is a corresponding g2g file which is up-to-date.
  • If the g2g file is out-of-date, update and save it.
  • Start the {@link Recognizer} running using data already being collected by the {@link Microphone}.
  • Wait for the {@link Recognizer} to complete.
  • Pass a list of {@link Intent} corresponding to the recognition results to the {@link VoiceDialerActivity}, which notifies the user.
  • Shut down and clean up.
Notes:
  • Audio many be read from a file.
  • A directory tree of audio files may be stepped through.
  • A contact list may be read from a file.
  • A {@link RecognizerLogger} may generate a set of log files from a recognition session.
  • A static instance of this class is held and reused by the {@link VoiceDialerActivity}, which saves setup time.

Fields Summary
private static final String
TAG
public static final String
SENTENCE_EXTRA
private final String
SREC_DIR
private static final String
OPEN_ENTRIES
private static final int
RESULT_LIMIT
private static final int
SAMPLE_RATE
private com.android.voicedialer.VoiceDialerActivity
mVoiceDialerActivity
private android.speech.srec.Recognizer
mSrec
private Recognizer.Grammar
mSrecGrammar
private com.android.voicedialer.RecognizerLogger
mLogger
private static final char[]
mLatin1Letters
private static final int
mLatin1Base
private static final String
mNanpFormats
private static final String
mPlusFormats
Constructors Summary
public RecognizerEngine()
Constructor.


          
      
    
Methods Summary
private static voidaddCallIntent(java.util.ArrayList intents, android.net.Uri uri, java.lang.String literal, int flags)

        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, uri).
        setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | flags).
        putExtra(SENTENCE_EXTRA, literal);
        addIntent(intents, intent);
    
private static voidaddClassName(java.util.HashMap openEntries, java.lang.String label, java.lang.String className)
Add a className to a hash table of class name lists.

param
openEntries HashMap of lists of class names.
param
label a label or word corresponding to the list of classes.
param
className class name to add
return

        String labelLowerCase = label.toLowerCase();
        String classList = openEntries.get(labelLowerCase);
        
        // first item in the list
        if (classList == null) {
            openEntries.put(labelLowerCase, className);
            return;
        }
        // already in list
        int index = classList.indexOf(className);
        int after = index + className.length();
        if (index != -1 && (index == 0 || classList.charAt(index - 1) == ' ") &&
                (after == classList.length() || classList.charAt(after) == ' ")) return;
        
        // add it to the end
        openEntries.put(labelLowerCase, classList + ' " + className);
    
private static voidaddIntent(java.util.ArrayList intents, android.content.Intent intent)

        for (Intent in : intents) {
            if (in.getAction() != null &&
                    in.getAction().equals(intent.getAction()) &&
                    in.getData() != null &&
                    in.getData().equals(intent.getData())) {
                return;
            }
        }
        intents.add(intent);
    
private voidaddNameEntriesToGrammar(java.util.List contacts)
Add a list of names to the grammar

param
contacts list of VoiceContacts to be added.

        if (Config.LOGD) Log.d(TAG, "addNameEntriesToGrammar " + contacts.size());
        
        HashSet entries = new HashSet<String>();
        StringBuffer sb = new StringBuffer();
        for (VoiceContact contact : contacts) {
            if (Thread.interrupted()) throw new InterruptedException();
            String name = scrubName(contact.mName);
            if (name.length() == 0 || !entries.add(name)) continue;
            sb.setLength(0);
            sb.append("V='");
            sb.append(contact.mPersonId).append(' ");
            sb.append(contact.mPrimaryId).append(' ");
            sb.append(contact.mHomeId).append(' ");
            sb.append(contact.mMobileId).append(' ");
            sb.append(contact.mWorkId).append(' ");
            sb.append(contact.mOtherId);
            sb.append("'");
            mSrecGrammar.addWordToSlot("@Names", name, null, 1, sb.toString());           
        }
    
private voidaddOpenEntriesToGrammar()
add a list of application labels to the 'open x' grammar

        if (Config.LOGD) Log.d(TAG, "addOpenEntriesToGrammar");

        // fill this
        HashMap<String, String> openEntries;
        File oe = mVoiceDialerActivity.getFileStreamPath(OPEN_ENTRIES);
        
        // build and write list of entries
        if (!oe.exists()) {
            openEntries = new HashMap<String, String>();
            
            // build a list of 'open' entries
            PackageManager pm = mVoiceDialerActivity.getPackageManager();
            List<ResolveInfo> riList = pm.queryIntentActivities(
                            new Intent(Intent.ACTION_MAIN).
                            addCategory("android.intent.category.VOICE_LAUNCH"),
                            PackageManager.GET_ACTIVITIES);
            if (Thread.interrupted()) throw new InterruptedException();
            riList.addAll(pm.queryIntentActivities(
                            new Intent(Intent.ACTION_MAIN).
                            addCategory("android.intent.category.LAUNCHER"),
                            PackageManager.GET_ACTIVITIES));
            String voiceDialerClassName = mVoiceDialerActivity.getComponentName().getClassName();

            // scan list, adding complete phrases, as well as individual words
            for (ResolveInfo ri : riList) {
                if (Thread.interrupted()) throw new InterruptedException();

                // skip self
                if (voiceDialerClassName.equals(ri.activityInfo.name)) continue;

                // fetch a scrubbed window label
                String label = scrubName(ri.loadLabel(pm).toString());
                if (label.length() == 0) continue;

                // insert it into the result list
                addClassName(openEntries, label, ri.activityInfo.name);

                // split it into individual words, and insert them
                String[] words = label.split(" ");
                if (words.length > 1) {
                    for (String word : words) {
                        word = word.trim();
                        // words must be three characters long, or two if capitalized
                        int len = word.length();
                        if (len <= 1) continue;
                        if (len == 2 && !(Character.isUpperCase(word.charAt(0)) &&
                                        Character.isUpperCase(word.charAt(1)))) continue;
                        if ("and".equalsIgnoreCase(word) || "the".equalsIgnoreCase(word)) continue;
                        // add the word
                        addClassName(openEntries, word, ri.activityInfo.name);
                    }
                }
            }

            // write list
            if (Config.LOGD) Log.d(TAG, "addOpenEntriesToGrammar writing " + oe);
            try {
                FileOutputStream fos = new FileOutputStream(oe);
                try {
                    ObjectOutputStream oos = new ObjectOutputStream(fos);
                    oos.writeObject(openEntries);
                    oos.close();
                } finally {
                    fos.close();
                }
            } catch (IOException ioe) {
                deleteCachedGrammarFiles(mVoiceDialerActivity);
                throw ioe;
            }
        }
        
        // read the list
        else {
            if (Config.LOGD) Log.d(TAG, "addOpenEntriesToGrammar reading " + oe);
            try {
                FileInputStream fis = new FileInputStream(oe);
                try {
                    ObjectInputStream ois = new ObjectInputStream(fis);
                    openEntries = (HashMap<String, String>)ois.readObject();
                    ois.close();
                } finally {
                    fis.close();
                }
            } catch (Exception e) {
                deleteCachedGrammarFiles(mVoiceDialerActivity);
                throw new IOException(e.toString());
            }
        }

        // add list of 'open' entries to the grammar
        for (String label : openEntries.keySet()) {
            if (Thread.interrupted()) throw new InterruptedException();
            String entry = openEntries.get(label);
            // don't add if too many results
            int count = 0;
            for (int i = 0; 0 != (i = entry.indexOf(' ", i) + 1); count++) ;
            if (count > RESULT_LIMIT) continue;
            // add the word to the grammar
            mSrecGrammar.addWordToSlot("@Opens", label, null, 1, "V='" + entry + "'");
        }
    
private static voiddeleteAllG2GFiles(android.content.Context context)
Delete all g2g files in the directory indicated by {@link File}, which is typically /data/data/com.android.voicedialer/files. There should only be one g2g file at any one time, with a hashcode embedded in it's name, but if stale ones are present, this will delete them all.

param
context fetch directory for the stuffed and compiled g2g file.

        FileFilter ff = new FileFilter() {
            public boolean accept(File f) {
                String name = f.getName();
                return name.endsWith(".g2g");
            }
        };
        File[] files = context.getFilesDir().listFiles(ff);
        if (files != null) {
            for (File file : files) {
                if (Config.LOGD) Log.d(TAG, "deleteAllG2GFiles " + file);
                file.delete();            
            }
        }
    
public static voiddeleteCachedGrammarFiles(android.content.Context context)
Delete G2G and OpenEntries files, to force regeneration of the g2g file from scratch.

param
context fetch directory for file.

        deleteAllG2GFiles(context);
        File oe = context.getFileStreamPath(OPEN_ENTRIES);
        if (Config.LOGD) Log.d(TAG, "deleteCachedGrammarFiles " + oe);
        if (oe.exists()) oe.delete();
    
private static java.lang.StringformatNumber(java.lang.String num)
Format a phone number string. At some point, PhoneNumberUtils.formatNumber will handle this.

param
num phone number string.
return
formatted phone number string.

        String fmt = null;
        
        fmt = formatNumber(mPlusFormats, num);
        if (fmt != null) return fmt;
        
        fmt = formatNumber(mNanpFormats, num);
        if (fmt != null) return fmt;
        
        return null;
    
private static java.lang.StringformatNumber(java.lang.String formats, java.lang.String number)

        // Uzbekistan
    

    // TODO: need to handle variable number notation
           
        number = number.trim();
        final int nlen = number.length();
        final int formatslen = formats.length();
        StringBuffer sb = new StringBuffer();
        
        // loop over country codes
        for (int f = 0; f < formatslen; ) {
            sb.setLength(0);
            int n = 0;
            
            // loop over letters of pattern
            while (true) {
                final char fch = formats.charAt(f);
                if (fch == '\n" && n >= nlen) return sb.toString();
                if (fch == '\n" || n >= nlen) break;
                final char nch = number.charAt(n);
                // pattern matches number
                if (fch == nch || (fch == 'x" && Character.isDigit(nch))) {
                    f++;
                    n++;
                    sb.append(nch);
                }
                // don't match ' ' in pattern, but insert into result
                else if (fch == ' ") {
                    f++;
                    sb.append(' ");
                    // ' ' at end -> match all the rest
                    if (formats.charAt(f) == '\n") return sb.append(number, n, nlen).toString();
                }
                // match failed
                else break;
            }
            
            // step to the next pattern
            f = formats.indexOf('\n", f) + 1;
            if (f == 0) break;
        }
        
        return null;
    
private voidonRecognitionSuccess()
Called when recognition succeeds. It receives a list of results, builds a corresponding list of Intents, and passes them to the {@link VoiceDialerActivity}, which selects and performs a corresponding action.

param
nbest a list of recognition results.

        if (Config.LOGD) Log.d(TAG, "onRecognitionSuccess");
        
        if (mLogger != null) mLogger.logNbestHeader();

        ArrayList<Intent> intents = new ArrayList<Intent>();

        // loop over results
        for (int result = 0; result < mSrec.getResultCount() &&
                intents.size() < RESULT_LIMIT; result++) {

            // parse the semanticMeaning string and build an Intent
            String conf = mSrec.getResult(result, Recognizer.KEY_CONFIDENCE);
            String literal = mSrec.getResult(result, Recognizer.KEY_LITERAL);
            String semantic = mSrec.getResult(result, Recognizer.KEY_MEANING);
            String msg = "conf=" + conf + " lit=" + literal + " sem=" + semantic;
            if (Config.LOGD) Log.d(TAG, msg);
            if (mLogger != null) mLogger.logLine(msg);
            String[] commands = semantic.trim().split(" ");

            // DIAL 650 867 5309
            // DIAL 867 5309
            // DIAL 911
            if ("DIAL".equals(commands[0])) {
                Uri uri = Uri.fromParts("tel", commands[1], null);
                String num =  formatNumber(commands[1]);
                if (num != null) {
                    addCallIntent(intents, uri,
                            literal.split(" ")[0].trim() + " " + num, 0);
                }
            }

            // CALL JACK JONES
            else if ("CALL".equals(commands[0]) && commands.length >= 7) {
                // parse the ids
                long personId = Long.parseLong(commands[1]); // people table
                long phoneId  = Long.parseLong(commands[2]); // phones table
                long homeId   = Long.parseLong(commands[3]); // phones table
                long mobileId = Long.parseLong(commands[4]); // phones table
                long workId   = Long.parseLong(commands[5]); // phones table
                long otherId  = Long.parseLong(commands[6]); // phones table
                Resources res = mVoiceDialerActivity.getResources();

                int count = 0;

                //
                // generate the best entry corresponding to what was said
                //

                // 'CALL JACK JONES AT HOME|MOBILE|WORK|OTHER'
                if (commands.length == 8) {
                    long spokenPhoneId =
                            "H".equalsIgnoreCase(commands[7]) ? homeId :
                            "M".equalsIgnoreCase(commands[7]) ? mobileId :
                            "W".equalsIgnoreCase(commands[7]) ? workId :
                            "O".equalsIgnoreCase(commands[7]) ? otherId :
                             VoiceContact.ID_UNDEFINED;
                    if (spokenPhoneId != VoiceContact.ID_UNDEFINED) {
                        addCallIntent(intents, ContentUris.withAppendedId(
                                Contacts.Phones.CONTENT_URI, spokenPhoneId),
                                literal, 0);
                        count++;
                    }
                }

                // 'CALL JACK JONES', with valid default phoneId
                else if (commands.length == 7) {
                    CharSequence phoneIdMsg =
                            phoneId == VoiceContact.ID_UNDEFINED ? null :
                            phoneId == homeId ? res.getText(R.string.at_home) :
                            phoneId == mobileId ? res.getText(R.string.on_mobile) :
                            phoneId == workId ? res.getText(R.string.at_work) :
                            phoneId == otherId ? res.getText(R.string.at_other) :
                            null;
                    if (phoneIdMsg != null) {
                        addCallIntent(intents, ContentUris.withAppendedId(
                                Contacts.Phones.CONTENT_URI, phoneId),
                                literal + phoneIdMsg, 0);
                        count++;
                    }
                }

                //
                // generate all other entries
                //

                // trim last two words, ie 'at home', etc
                String lit = literal;
                if (commands.length == 8) {
                    String[] words = literal.trim().split(" ");
                    StringBuffer sb = new StringBuffer();
                    for (int i = 0; i < words.length - 2; i++) {
                        if (i != 0) {
                            sb.append(' ");
                        }
                        sb.append(words[i]);
                    }
                    lit = sb.toString();
                }

                //  add 'CALL JACK JONES at home' using phoneId
                if (homeId != VoiceContact.ID_UNDEFINED) {
                    addCallIntent(intents, ContentUris.withAppendedId(
                            Contacts.Phones.CONTENT_URI, homeId),
                            lit + res.getText(R.string.at_home), 0);
                    count++;
                }

                //  add 'CALL JACK JONES on mobile' using mobileId
                if (mobileId != VoiceContact.ID_UNDEFINED) {
                    addCallIntent(intents, ContentUris.withAppendedId(
                            Contacts.Phones.CONTENT_URI, mobileId),
                            lit + res.getText(R.string.on_mobile), 0);
                    count++;
                }

                //  add 'CALL JACK JONES at work' using workId
                if (workId != VoiceContact.ID_UNDEFINED) {
                    addCallIntent(intents, ContentUris.withAppendedId(
                            Contacts.Phones.CONTENT_URI, workId),
                            lit + res.getText(R.string.at_work), 0);
                    count++;
                }

                //  add 'CALL JACK JONES at other' using otherId
                if (otherId != VoiceContact.ID_UNDEFINED) {
                    addCallIntent(intents, ContentUris.withAppendedId(
                            Contacts.Phones.CONTENT_URI, otherId),
                            lit + res.getText(R.string.at_other), 0);
                    count++;
                }

                //
                // if no other entries were generated, use the personId
                //

                // add 'CALL JACK JONES', with valid personId
                if (count == 0 && personId != VoiceContact.ID_UNDEFINED) {
                    addCallIntent(intents, ContentUris.withAppendedId(
                            Contacts.People.CONTENT_URI, personId), literal, 0);
                }
            }

            // "CALL VoiceMail"
            else if ("voicemail".equals(commands[0]) && commands.length == 1) {
                addCallIntent(intents, Uri.fromParts("voicemail", "x", null),
                        literal, Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
            }

            // "REDIAL"
            else if ("redial".equals(commands[0]) && commands.length == 1) {
                String number = VoiceContact.redialNumber(mVoiceDialerActivity);
                if (number != null) {
                    addCallIntent(intents, Uri.fromParts("tel", number, null), literal,
                            Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                }
            }

            // "Intent ..."
            else if ("Intent".equalsIgnoreCase(commands[0])) {
                for (int i = 1; i < commands.length; i++) {
                    try {
                        Intent intent = Intent.getIntent(commands[i]);
                        if (intent.getStringExtra(SENTENCE_EXTRA) == null) {
                            intent.putExtra(SENTENCE_EXTRA, literal);
                        }
                        addIntent(intents, intent);
                    } catch (URISyntaxException e) {
                        if (Config.LOGD) Log.d(TAG,
                                "onRecognitionSuccess: poorly formed URI in grammar\n" + e);
                    }
                }
            }
            
            // "OPEN ..."
            else if ("OPEN".equals(commands[0])) {
                PackageManager pm = mVoiceDialerActivity.getPackageManager();
                for (int i = 1; i < commands.length; i++) {
                    String cn = commands[i];
                    Intent intent = new Intent(Intent.ACTION_MAIN);
                    intent.addCategory("android.intent.category.VOICE_LAUNCH");
                    intent.setClassName(cn.substring(0, cn.lastIndexOf('.")), cn);
                    List<ResolveInfo> riList = pm.queryIntentActivities(intent, 0);
                    for (ResolveInfo ri : riList) {
                        String label = ri.loadLabel(pm).toString();
                        intent = new Intent(Intent.ACTION_MAIN);
                        intent.addCategory("android.intent.category.VOICE_LAUNCH");
                        intent.setClassName(cn.substring(0, cn.lastIndexOf('.")), cn);
                        intent.putExtra(SENTENCE_EXTRA, literal.split(" ")[0] + " " + label);
                        addIntent(intents, intent);
                    }
                }
            }

            // can't parse result
            else {
                if (Config.LOGD) Log.d(TAG, "onRecognitionSuccess: parse error");
            }

        }

        // log if requested
        if (mLogger != null) mLogger.logIntents(intents);

        // bail out if cancelled
        if (Thread.interrupted()) throw new InterruptedException();

        if (intents.size() == 0) {
            // TODO: strip HOME|MOBILE|WORK and try default here?
            mVoiceDialerActivity.onRecognitionFailure("No Intents generated");
        }
        else {
            mVoiceDialerActivity.onRecognitionSuccess(
                    intents.toArray(new Intent[intents.size()]));
        }
    
public voidrecognize(com.android.voicedialer.VoiceDialerActivity voiceDialerActivity, java.io.File micFile, java.io.File contactsFile, java.lang.String codec)
Start the recognition process.
  • Create and start the microphone.
  • Create a Recognizer.
  • Scan contacts and determine if the Grammar g2g file is stale.
  • If so, create and rebuild the Grammar,
  • Else create and load the Grammar from the file.
  • Start the Recognizer.
  • Feed the Recognizer audio until it provides a result.
  • Build a list of Intents corresponding to the results.
  • Stop the microphone.
  • Stop the Recognizer.

param
voiceDialerActivity VoiceDialerActivity instance.
param
logDir write log files to this directory.
param
micFile audio input from this file, or directory tree.
param
contactsFile file containing a list of contacts to use.
param
codec one of PCM/16bit/11KHz(default) or PCM/16bit/8KHz.

        InputStream mic = null;
        boolean recognizerStarted = false;
        try {
            if (Config.LOGD) Log.d(TAG, "start");
            
            mVoiceDialerActivity = voiceDialerActivity;
            
            // set up logger
            mLogger = null;
            if (RecognizerLogger.isEnabled(mVoiceDialerActivity)) {
                mLogger = new RecognizerLogger(mVoiceDialerActivity);
            }
            
            // start audio input
            if (Config.LOGD) Log.d(TAG, "start new MicrophoneInputStream");
            if (micFile != null) {
                mic = new FileInputStream(micFile);
                WaveHeader hdr = new WaveHeader();
                hdr.read(mic);
            } else {
                mic = new MicrophoneInputStream(SAMPLE_RATE, SAMPLE_RATE * 15);
            }
                    
            // notify UI
            voiceDialerActivity.onMicrophoneStart();

            // create a new recognizer
            if (Config.LOGD) Log.d(TAG, "start new Recognizer");
            if (mSrec == null) mSrec = new Recognizer(SREC_DIR + "/baseline11k.par");

            // fetch the contact list
            if (Config.LOGD) Log.d(TAG, "start getVoiceContacts");
            List<VoiceContact> contacts = contactsFile != null ?
                    VoiceContact.getVoiceContactsFromFile(contactsFile) :
                    VoiceContact.getVoiceContacts(mVoiceDialerActivity);
                    
            // log contacts if requested
            if (mLogger != null) mLogger.logContacts(contacts);
            
            // generate g2g grammar file name
            File g2g = mVoiceDialerActivity.getFileStreamPath("voicedialer." +
                    Integer.toHexString(contacts.hashCode()) + ".g2g");
            
            // rebuild g2g file if current one is out of date
            if (!g2g.exists()) {
                // clean up existing Grammar and old file
                deleteAllG2GFiles(mVoiceDialerActivity);
                if (mSrecGrammar != null) {
                    mSrecGrammar.destroy();
                    mSrecGrammar = null;
                }
                
                // load the empty Grammar
                if (Config.LOGD) Log.d(TAG, "start new Grammar");
                mSrecGrammar = mSrec.new Grammar(SREC_DIR + "/grammars/VoiceDialer.g2g");
                mSrecGrammar.setupRecognizer();

                // reset slots
                if (Config.LOGD) Log.d(TAG, "start grammar.resetAllSlots");
                mSrecGrammar.resetAllSlots();

                // add names to the grammar
                addNameEntriesToGrammar(contacts);
                
                // add 'open' entries to the grammar
                addOpenEntriesToGrammar();

                // compile the grammar
                if (Config.LOGD) Log.d(TAG, "start grammar.compile");
                mSrecGrammar.compile();

                // update g2g file
                if (Config.LOGD) Log.d(TAG, "start grammar.save " + g2g.getPath());
                g2g.getParentFile().mkdirs();
                mSrecGrammar.save(g2g.getPath());
            }
           
            // g2g file exists, but is not loaded
            else if (mSrecGrammar == null) {
                if (Config.LOGD) Log.d(TAG, "start new Grammar loading " + g2g);
                mSrecGrammar = mSrec.new Grammar(g2g.getPath());
                mSrecGrammar.setupRecognizer();
            }
            
            // start the recognition process
            if (Config.LOGD) Log.d(TAG, "start mSrec.start");
            mSrec.start();
            recognizerStarted = true;
            
            // log audio if requested
            if (mLogger != null) mic = mLogger.logInputStream(mic, SAMPLE_RATE);
            
            // recognize
            while (true) {
                if (Thread.interrupted()) throw new InterruptedException();
                int event = mSrec.advance();
                if (event != Recognizer.EVENT_INCOMPLETE &&
                        event != Recognizer.EVENT_NEED_MORE_AUDIO) {
                    if (Config.LOGD) Log.d(TAG, "start advance()=" +
                            Recognizer.eventToString(event));
                }
                switch (event) {
                case Recognizer.EVENT_INCOMPLETE:
                case Recognizer.EVENT_STARTED:
                case Recognizer.EVENT_START_OF_VOICING:
                case Recognizer.EVENT_END_OF_VOICING:
                    continue;
                case Recognizer.EVENT_RECOGNITION_RESULT:
                    onRecognitionSuccess();
                    break;
                case Recognizer.EVENT_NEED_MORE_AUDIO:
                    mSrec.putAudio(mic);
                    continue;
                default:
                    mVoiceDialerActivity.onRecognitionFailure(Recognizer.eventToString(event));
                    break;
                }
                break;
            }
        } catch (InterruptedException e) {
            if (Config.LOGD) Log.d(TAG, "start interrupted " + e);
        } catch (IOException e) {
            if (Config.LOGD) Log.d(TAG, "start new Srec failed " + e);
            mVoiceDialerActivity.onRecognitionError(e.toString());
        } finally {
            // stop microphone
            try {
                if (mic != null) mic.close();
            }
            catch (IOException ex) {
                if (Config.LOGD) Log.d(TAG, "start - mic.close failed - " + ex);
            }
            mic = null;

            // shut down recognizer
            if (Config.LOGD) Log.d(TAG, "start mSrec.stop");
            if (mSrec != null && recognizerStarted) mSrec.stop();
            
            // close logger
            try {
                if (mLogger != null) mLogger.close();
            }
            catch (IOException ex) {
                if (Config.LOGD) Log.d(TAG, "start - mLoggger.close failed - " + ex);
            }
            mLogger = null;
        }
        if (Config.LOGD) Log.d(TAG, "start bye");
    
private static java.lang.StringscrubName(java.lang.String name)
Reformat a raw name from the contact list into a form a {@link SrecEmbeddedGrammar} can digest.

param
name the raw name.
return
the reformatted name.

    
                                  
         
        // replace '&' with ' and '
        name = name.replace("&", " and ");
        
        // replace '@' with ' at '
        name = name.replace("@", " at ");
        
        // remove '(...)'
        while (true) {
            int i = name.indexOf('(");
            if (i == -1) break;
            int j = name.indexOf(')", i);
            if (j == -1) break;
            name = name.substring(0, i) + " " + name.substring(j + 1);
        }
        
        // map letters of Latin1 Supplement to basic ascii
        char[] nm = null;
        for (int i = name.length() - 1; i >= 0; i--) {
            char ch = name.charAt(i);
            if (ch < ' " || '~" < ch) {
                if (nm == null) nm = name.toCharArray();
                nm[i] = mLatin1Base <= ch && ch < mLatin1Base + mLatin1Letters.length ?
                    mLatin1Letters[ch - mLatin1Base] : ' ";
            }
        }
        if (nm != null) {
            name = new String(nm);
        }
        
        // if '.' followed by alnum, replace with ' dot '
        while (true) {
            int i = name.indexOf('.");
            if (i == -1 ||
                    i + 1 >= name.length() ||
                    !Character.isLetterOrDigit(name.charAt(i + 1))) break;
            name = name.substring(0, i) + " dot " + name.substring(i + 1);
        }
        
        // trim
        name = name.trim();

        // ensure at least one alphanumeric character, or the pron engine will fail
        for (int i = name.length() - 1; true; i--) {
            if (i < 0) return "";
            char ch = name.charAt(i);
            if (('a" <= ch && ch <= 'z") || ('A" <= ch && ch <= 'Z") || ('0" <= ch && ch <= '9")) {
                break;
            }
        }
        
        return name;