001/*
002 * Copyright (c) 2013 Nu Echo Inc. All rights reserved.
003 */
004package com.nuecho.rivr.voicexml.turn.output;
005
006import static com.nuecho.rivr.core.util.Assert.*;
007import static com.nuecho.rivr.voicexml.rendering.voicexml.VoiceXmlDomUtil.*;
008import static com.nuecho.rivr.voicexml.turn.input.VoiceXmlEvent.*;
009
010import java.util.*;
011
012import javax.json.*;
013
014import org.w3c.dom.*;
015
016import com.nuecho.rivr.core.util.*;
017import com.nuecho.rivr.voicexml.dialogue.*;
018import com.nuecho.rivr.voicexml.rendering.voicexml.*;
019import com.nuecho.rivr.voicexml.turn.output.audio.*;
020import com.nuecho.rivr.voicexml.turn.output.grammar.*;
021import com.nuecho.rivr.voicexml.util.json.*;
022
023/**
024 * An {@link Interaction} is a {@link VoiceXmlOutputTurn} that represents a list
025 * of {@link Prompt} with an optional {@link FinalRecognitionWindow} or
026 * {@link FinalRecordingWindow} phase.
027 * <p>
028 * Each {@link Interaction.Prompt} represents a phase of the interaction with a
029 * sequence of {@link AudioItem} and optional speech/DTMF recognition
030 * configurations.
031 *
032 * @author Nu Echo Inc.
033 * @see Interaction.Prompt
034 * @see Interaction.FinalRecognitionWindow
035 * @see Interaction.FinalRecordingWindow
036 */
037public class Interaction extends VoiceXmlOutputTurn {
038    private static final String INTERACTION_TURN_TYPE = "interaction";
039
040    private static final String RECORDING_PROPERTY = "recording";
041    private static final String RECOGNITION_PROPERTY = "recognition";
042    private static final String PROMPTS_PROPERTY = "prompts";
043
044    private final List<Prompt> mPrompts;
045    private final FinalRecognitionWindow mFinalRecognitionWindow;
046    private final FinalRecordingWindow mFinalRecordingWindow;
047
048    /**
049     * @param name The name of this turn. Not empty.
050     * @param prompts The list of {@link Prompt}. Not null.
051     */
052    public Interaction(String name, List<Prompt> prompts) {
053        super(name);
054        Assert.notNull(prompts, "prompts");
055        mPrompts = new ArrayList<Prompt>(prompts);
056        mFinalRecognitionWindow = null;
057        mFinalRecordingWindow = null;
058    }
059
060    /**
061     * @param name The name of this turn. Not empty.
062     * @param prompts The list of {@link Prompt}. Not null.
063     * @param finalRecognitionWindow The final recognition phase configuration.
064     *            Not null.
065     */
066    public Interaction(String name, List<Prompt> prompts, FinalRecognitionWindow finalRecognitionWindow) {
067        super(name);
068        Assert.notNull(prompts, "prompts");
069        Assert.notNull(finalRecognitionWindow, "recognition");
070        mPrompts = new ArrayList<Prompt>(prompts);
071        mFinalRecognitionWindow = finalRecognitionWindow;
072        mFinalRecordingWindow = null;
073    }
074
075    /**
076     * @param name The name of this turn. Not empty.
077     * @param prompts The list of {@link Prompt}. Not null.
078     * @param finalRecordingWindow The final recording phase configuration. Not
079     *            null.
080     */
081    public Interaction(String name, List<Prompt> prompts, FinalRecordingWindow finalRecordingWindow) {
082        super(name);
083        Assert.notNull(prompts, "prompts");
084        Assert.notNull(finalRecordingWindow, "recording");
085        mPrompts = new ArrayList<Prompt>(prompts);
086        mFinalRecordingWindow = finalRecordingWindow;
087        mFinalRecognitionWindow = null;
088    }
089
090    public final List<Prompt> getPrompts() {
091        return Collections.unmodifiableList(mPrompts);
092    }
093
094    public final FinalRecognitionWindow getRecognition() {
095        return mFinalRecognitionWindow;
096    }
097
098    public final FinalRecordingWindow getRecording() {
099        return mFinalRecordingWindow;
100    }
101
102    @Override
103    protected final String getOuputTurnType() {
104        return INTERACTION_TURN_TYPE;
105    }
106
107    @Override
108    protected void addTurnProperties(JsonObjectBuilder builder) {
109        JsonUtils.add(builder, PROMPTS_PROPERTY, JsonUtils.toJson(mPrompts));
110        JsonUtils.add(builder, RECOGNITION_PROPERTY, mFinalRecognitionWindow);
111        JsonUtils.add(builder, RECORDING_PROPERTY, mFinalRecordingWindow);
112    }
113
114    @Override
115    protected void fillVoiceXmlDocument(Document document, Element formElement, VoiceXmlDialogueContext dialogueContext)
116            throws VoiceXmlDocumentRenderingException {
117        DtmfRecognition dtmfGlobalRecognition = factorizeGlobalDtmfRecognition();
118        SpeechRecognition speechGlobalRecognition = factorizeGlobalSpeechRecognition();
119
120        processDtmfRecognition(dtmfGlobalRecognition, formElement);
121        processSpeechRecognition(speechGlobalRecognition, formElement);
122
123        boolean hasAtLeastOneField = false;
124
125        Element formItemElement = null;
126        boolean recognitionMergedWithLastPrompt = false;
127        for (int interactionPromptIndex = 0; interactionPromptIndex < mPrompts.size(); interactionPromptIndex++) {
128            Prompt prompt = mPrompts.get(interactionPromptIndex);
129            DtmfRecognition dtmfRecognition = prompt.getDtmfRecognition();
130            SpeechRecognition speechRecognition = prompt.getSpeechRecognition();
131
132            boolean usingField;
133            boolean bargeIn = prompt.getDtmfRecognition() != null || prompt.getSpeechRecognition() != null;
134
135            if (dtmfRecognition == null && speechRecognition == null) {
136                formItemElement = DomUtils.appendNewElement(formElement, BLOCK_ELEMENT);
137                usingField = false;
138            } else {
139                formItemElement = DomUtils.appendNewElement(formElement, FIELD_ELEMENT);
140                addBargeIn(prompt, dtmfRecognition, speechRecognition, formItemElement);
141                usingField = true;
142            }
143
144            hasAtLeastOneField |= usingField;
145
146            String formItemName = PROMPT_FORM_ITEM_NAME_PREFIX + interactionPromptIndex;
147            formItemElement.setAttribute(NAME_ATTRIBUTE, formItemName);
148
149            processDtmfRecognition(getLocalDtmfRecognition(dtmfRecognition, dtmfGlobalRecognition), formItemElement);
150
151            processSpeechRecognition(getLocalSpeechRecognition(speechRecognition, speechGlobalRecognition),
152                                     formItemElement);
153
154            if (usingField) {
155                if (mFinalRecognitionWindow != null
156                    && interactionPromptIndex == mPrompts.size() - 1
157                    && same(mFinalRecognitionWindow.getDtmfRecognition(), prompt.getDtmfRecognition())
158                    && same(mFinalRecognitionWindow.getSpeechRecognition(), prompt.getSpeechRecognition())) {
159                    addDurationProperty(formItemElement, TIMEOUT_PROPERTY, mFinalRecognitionWindow.getNoInputTimeout());
160                    recognitionMergedWithLastPrompt = true;
161                } else {
162                    addDurationProperty(formItemElement, TIMEOUT_PROPERTY, Duration.ZERO);
163                }
164            }
165
166            renderPrompts(prompt, formItemElement, dialogueContext, bargeIn);
167
168            if (usingField && !recognitionMergedWithLastPrompt) {
169                Element noInputElement = DomUtils.appendNewElement(formItemElement, NOINPUT_ELEMENT);
170                createAssignation(noInputElement, formItemName, TRUE);
171                DomUtils.appendNewElement(noInputElement, REPROMPT_ELEMENT);
172            }
173        }
174
175        if (mFinalRecognitionWindow != null) {
176            if (!recognitionMergedWithLastPrompt) {
177                hasAtLeastOneField = true;
178                Element recognitionFormItemElement = DomUtils.appendNewElement(formElement, FIELD_ELEMENT);
179                recognitionFormItemElement.setAttribute(NAME_ATTRIBUTE, RECOGNITION_FORM_ITEM_NAME);
180                processDtmfRecognition(getLocalDtmfRecognition(mFinalRecognitionWindow.getDtmfRecognition(),
181                                                               dtmfGlobalRecognition),
182                                       recognitionFormItemElement);
183
184                processSpeechRecognition(getLocalSpeechRecognition(mFinalRecognitionWindow.getSpeechRecognition(),
185                                                                   speechGlobalRecognition),
186                                         recognitionFormItemElement);
187
188                addDurationProperty(recognitionFormItemElement,
189                                    TIMEOUT_PROPERTY,
190                                    mFinalRecognitionWindow.getNoInputTimeout());
191            }
192        } else if (mFinalRecordingWindow != null) {
193            Element recordingFormItemElement = DomUtils.appendNewElement(formElement, RECORD_ELEMENT);
194            recordingFormItemElement.setAttribute(NAME_ATTRIBUTE, RECORD_FORM_ITEM_NAME);
195
196            Recording recording = mFinalRecordingWindow.getRecording();
197            DtmfRecognition dtmfTermRecognition = recording.getDtmfTermRecognition();
198            if (dtmfTermRecognition != null) {
199                processDtmfRecognition(dtmfTermRecognition, recordingFormItemElement);
200            }
201
202            setBooleanAttribute(recordingFormItemElement, BEEP_ATTRIBUTE, recording.getBeep());
203            setBooleanAttribute(recordingFormItemElement, DTMFTERM_ATTRIBUTE, recording.getDtmfTerm());
204
205            addDurationProperty(recordingFormItemElement, TIMEOUT_PROPERTY, mFinalRecordingWindow.getNoInputTimeout());
206            setDurationAttribute(recordingFormItemElement, FINAL_SILENCE_ATTRIBUTE, recording.getFinalSilence());
207            setDurationAttribute(recordingFormItemElement, MAXTIME_ATTRIBUTE, recording.getMaximumTime());
208            setAttribute(recordingFormItemElement, TYPE_ATTRIBUTE, recording.getType());
209
210            Element filledElement = DomUtils.appendNewElement(recordingFormItemElement, FILLED_ELEMENT);
211
212            List<? extends AudioItem> acknowledgeAudioItems = mFinalRecordingWindow.getAcknowledgeAudioItems();
213            if (!acknowledgeAudioItems.isEmpty()) {
214                createPrompt(null, recordingFormItemElement, dialogueContext, false, acknowledgeAudioItems);
215            }
216
217            String clientSideAssignationDestination = recording.getClientSideAssignationDestination();
218            if (clientSideAssignationDestination != null) {
219                createAssignation(filledElement, clientSideAssignationDestination, RECORD_FORM_ITEM_NAME);
220            }
221
222            addRecordingResultHandlerScript(recording, filledElement);
223            createGotoSubmit(filledElement);
224
225            Element catchElement = DomUtils.appendNewElement(recordingFormItemElement, CATCH_ELEMENT);
226            catchElement.setAttribute(EVENT_ATTRIBUTE, CONNECTION_DISCONNECT);
227            addRecordingResultHandlerScript(recording, catchElement);
228            addEventHandlerScript(catchElement);
229            createGotoSubmit(catchElement);
230
231        } else {
232            if (hasAtLeastOneField) {
233                Element blockElement = DomUtils.appendNewElement(formElement, BLOCK_ELEMENT);
234                createGotoSubmit(blockElement);
235            } else {
236                createGotoSubmit(formItemElement);
237            }
238        }
239
240        if (hasAtLeastOneField) {
241            createFormLevelFilled(formElement);
242        }
243    }
244
245    private void addRecordingResultHandlerScript(Recording recording, Element parent) {
246        createScript(parent, RIVR_SCOPE_OBJECT
247                             + ".addRecordingResult(dialog."
248                             + RECORD_FORM_ITEM_NAME
249                             + ", dialog."
250                             + RECORD_FORM_ITEM_NAME
251                             + "$, "
252                             + recording.isPostAudioToServer()
253                             + ");");
254
255    }
256
257    private static void renderPrompts(Prompt prompt,
258                                      Element parentElement,
259                                      VoiceXmlDialogueContext voiceXmlDialogueContext,
260                                      boolean bargeIn) throws VoiceXmlDocumentRenderingException {
261        List<? extends AudioItem> audioItems = prompt.getAudioItems();
262        createPrompt(prompt.getLanguage(), parentElement, voiceXmlDialogueContext, bargeIn, audioItems);
263    }
264
265    private SpeechRecognition factorizeGlobalSpeechRecognition() {
266        List<SpeechRecognition> speechConfigurations = getSpeechRecognitions();
267        if (speechConfigurations.isEmpty()) return null;
268
269        SpeechRecognition speechGlobalRecognition = speechConfigurations.get(0).copy();
270        // unfortunately, we cannot use form-level grammars because of VoiceXML
271        // semantic mapping (section 3.1.6)
272        speechGlobalRecognition.setGrammarItems(new GrammarItem[0]);
273
274        for (SpeechRecognition speechConfiguration : speechConfigurations) {
275            SpeechRecognition configuration = speechConfiguration;
276            Assert.notNull(configuration, "configuration");
277
278            if (!same(configuration.getCompleteTimeout(), speechGlobalRecognition.getCompleteTimeout())) {
279                speechGlobalRecognition.setCompleteTimeout(null);
280            }
281
282            if (!same(configuration.getIncompleteTimeout(), speechGlobalRecognition.getIncompleteTimeout())) {
283                speechGlobalRecognition.setIncompleteTimeout(null);
284            }
285
286            if (!same(configuration.getConfidenceLevel(), speechGlobalRecognition.getConfidenceLevel())) {
287                speechGlobalRecognition.setConfidenceLevel(null);
288            }
289
290            if (!same(configuration.getMaxSpeechTimeout(), speechGlobalRecognition.getMaxSpeechTimeout())) {
291                speechGlobalRecognition.setMaxSpeechTimeout(null);
292            }
293
294            if (!same(configuration.getSensitivity(), speechGlobalRecognition.getSensitivity())) {
295                speechGlobalRecognition.setSensitivity(null);
296            }
297
298            if (!same(configuration.getSpeedVersusAccuracy(), speechGlobalRecognition.getSpeedVersusAccuracy())) {
299                speechGlobalRecognition.setSpeedVersusAccuracy(null);
300            }
301
302            if (!same(configuration.getMaxNBest(), speechGlobalRecognition.getMaxNBest())) {
303                speechGlobalRecognition.setMaxNBest(null);
304            }
305
306            factorizeParameters(speechGlobalRecognition, configuration);
307        }
308
309        return speechGlobalRecognition;
310    }
311
312    private DtmfRecognition factorizeGlobalDtmfRecognition() {
313        List<DtmfRecognition> dtmfRecognitions = getDtmfRecognitions();
314        if (dtmfRecognitions.isEmpty()) return null;
315
316        DtmfRecognition dtmfGlobalRecognition = dtmfRecognitions.get(0).copy();
317        // unfortunately, we cannot use form-level grammars because of VoiceXML
318        // semantic mapping (section 3.1.6)
319        dtmfGlobalRecognition.setGrammarItems(new GrammarItem[0]);
320
321        for (DtmfRecognition dtmfRecognition : dtmfRecognitions) {
322            DtmfRecognition configuration = dtmfRecognition;
323            Assert.notNull(configuration, "configuration");
324
325            if (!same(configuration.getInterDigitTimeout(), dtmfGlobalRecognition.getInterDigitTimeout())) {
326                dtmfGlobalRecognition.setInterDigitTimeout(null);
327            }
328
329            if (!same(configuration.getTermTimeout(), dtmfGlobalRecognition.getTermTimeout())) {
330                dtmfGlobalRecognition.setTermTimeout(null);
331            }
332
333            if (!same(configuration.getTermChar(), dtmfGlobalRecognition.getTermChar())) {
334                dtmfGlobalRecognition.setTermChar(null);
335            }
336
337            factorizeParameters(dtmfGlobalRecognition, configuration);
338        }
339
340        return dtmfGlobalRecognition;
341    }
342
343    private List<SpeechRecognition> getSpeechRecognitions() {
344        List<SpeechRecognition> configurations = new ArrayList<SpeechRecognition>();
345
346        for (Prompt prompt : mPrompts) {
347            SpeechRecognition speechRecognition = prompt.getSpeechRecognition();
348            if (speechRecognition != null) {
349                configurations.add(speechRecognition);
350            }
351
352        }
353
354        if (mFinalRecognitionWindow != null) {
355            SpeechRecognition speechRecognition = mFinalRecognitionWindow.getSpeechRecognition();
356            if (speechRecognition != null) {
357                configurations.add(speechRecognition);
358            }
359        }
360
361        return configurations;
362    }
363
364    private List<DtmfRecognition> getDtmfRecognitions() {
365        List<DtmfRecognition> configurations = new ArrayList<DtmfRecognition>();
366
367        for (Prompt prompt : getPrompts()) {
368            DtmfRecognition dtmfRecognition = prompt.getDtmfRecognition();
369            if (dtmfRecognition != null) {
370                configurations.add(dtmfRecognition);
371            }
372
373        }
374
375        if (mFinalRecognitionWindow != null) {
376            DtmfRecognition dtmfRecognition = mFinalRecognitionWindow.getDtmfRecognition();
377            if (dtmfRecognition != null) {
378                configurations.add(dtmfRecognition);
379            }
380        }
381
382        return configurations;
383    }
384
385    private static boolean same(Object object1, Object object2) {
386        if (object1 == null) return object2 == null;
387        if (object2 == null) return false;
388        return object1.equals(object2);
389    }
390
391    private static void factorizeParameters(Recognition globalRecognition, Recognition localRecognition) {
392        for (String propertyName : globalRecognition.getPropertyNames()) {
393            if (!same(localRecognition.getProperty(propertyName), globalRecognition.getProperty(propertyName))) {
394                globalRecognition.removeProperty(propertyName);
395            }
396        }
397    }
398
399    private static void addBargeIn(Prompt prompt,
400                                   DtmfRecognition dtmfRecognition,
401                                   SpeechRecognition speechRecognition,
402                                   Element formItemElement) {
403        BargeInType bargeInType = prompt.getBargeInType();
404        if (bargeInType != null) {
405            addProperty(formItemElement, BARGE_IN_TYPE_PROPERTY, bargeInType.name());
406        }
407
408        if (dtmfRecognition != null && speechRecognition != null) {
409            addProperty(formItemElement, INPUT_MODES_PROPERTY, DTMF_VOICE_INPUT_MODE);
410        } else if (dtmfRecognition == null) {
411            addProperty(formItemElement, INPUT_MODES_PROPERTY, VOICE_INPUT_MODE);
412        } else if (speechRecognition == null) {
413            addProperty(formItemElement, INPUT_MODES_PROPERTY, DTMF_INPUT_MODE);
414        }
415    }
416
417    private static DtmfRecognition getLocalDtmfRecognition(DtmfRecognition dtmfRecognition,
418                                                           DtmfRecognition globalDtmfRecognition) {
419        if (dtmfRecognition == null) return null;
420
421        DtmfRecognition localDtmfRecognition = dtmfRecognition.copy();
422
423        if (globalDtmfRecognition.getInterDigitTimeout() != null) {
424            localDtmfRecognition.setInterDigitTimeout(null);
425        }
426
427        if (globalDtmfRecognition.getTermTimeout() != null) {
428            localDtmfRecognition.setTermTimeout(null);
429        }
430
431        if (globalDtmfRecognition.getTermChar() != null) {
432            localDtmfRecognition.setTermChar(null);
433        }
434
435        removeGlobalParameters(globalDtmfRecognition, localDtmfRecognition);
436
437        return localDtmfRecognition;
438    }
439
440    private static SpeechRecognition getLocalSpeechRecognition(SpeechRecognition speechRecognition,
441                                                               SpeechRecognition globalSpeechRecognition) {
442        if (speechRecognition == null) return null;
443
444        SpeechRecognition localSpeechRecognition = speechRecognition.copy();
445
446        if (globalSpeechRecognition.getCompleteTimeout() != null) {
447            localSpeechRecognition.setCompleteTimeout(null);
448        }
449
450        if (globalSpeechRecognition.getIncompleteTimeout() != null) {
451            localSpeechRecognition.setIncompleteTimeout(null);
452        }
453
454        if (globalSpeechRecognition.getConfidenceLevel() != null) {
455            localSpeechRecognition.setConfidenceLevel(null);
456        }
457
458        if (globalSpeechRecognition.getIncompleteTimeout() != null) {
459            localSpeechRecognition.setIncompleteTimeout(null);
460        }
461
462        if (globalSpeechRecognition.getMaxNBest() != null) {
463            localSpeechRecognition.setMaxNBest(null);
464        }
465
466        if (globalSpeechRecognition.getMaxSpeechTimeout() != null) {
467            localSpeechRecognition.setMaxSpeechTimeout(null);
468        }
469
470        if (globalSpeechRecognition.getSensitivity() != null) {
471            localSpeechRecognition.setSensitivity(null);
472        }
473
474        if (globalSpeechRecognition.getSpeedVersusAccuracy() != null) {
475            localSpeechRecognition.setSpeedVersusAccuracy(null);
476        }
477
478        removeGlobalParameters(globalSpeechRecognition, localSpeechRecognition);
479
480        return localSpeechRecognition;
481    }
482
483    private static void removeGlobalParameters(Recognition globalRecognition, Recognition localRecognition) {
484        for (String propertyName : globalRecognition.getPropertyNames()) {
485            localRecognition.removeProperty(propertyName);
486        }
487    }
488
489    private void createFormLevelFilled(Element parent) throws VoiceXmlDocumentRenderingException {
490        Element filledElement = DomUtils.appendNewElement(parent, FILLED_ELEMENT);
491        filledElement.setAttribute(MODE_ATTRIBUTE, ANY_MODE);
492
493        if (mFinalRecognitionWindow != null) {
494            List<? extends AudioItem> acknowledgeAudioItems = mFinalRecognitionWindow.getAcknowledgeAudioItems();
495            if (!acknowledgeAudioItems.isEmpty()) {
496                processAudioItems(acknowledgeAudioItems, filledElement);
497            }
498        }
499
500        createScript(filledElement, RIVR_SCOPE_OBJECT + ".addRecognitionResult()");
501        createGotoSubmit(filledElement);
502    }
503
504    /**
505     * Barge-in types.
506     *
507     * @author Nu Echo Inc.
508     */
509    public enum BargeInType {
510        /**
511         * Normal speech-detection barge-in. Triggers only when speech is
512         * detected.
513         */
514        speech,
515
516        /**
517         * Hotword barge-in. Triggers only when grammar is matched.
518         */
519        hotword;
520    }
521
522    /**
523     * A {@link FinalRecognitionWindow} is an optional final phase of an
524     * {@link Interaction}.
525     * <p>
526     * It specifies a recognition configuration, and optionally, a no input
527     * timeout and a sequence of {@link AudioItem} that is played if a
528     * recognition is successful.
529     *
530     * @author Nu Echo Inc.
531     */
532    public static final class FinalRecognitionWindow implements JsonSerializable {
533
534        private static final String NO_INPUT_TIMEOUT_PROPERTY = "noInputTimeout";
535        private static final String SPEECH_RECOGNITION_PROPERTY = "speechRecognition";
536        private static final String DTMF_RECOGNITION_PROPERTY = "dtmfRecognition";
537        private static final String ACKNOWLEDGE_AUDIO_ITEMS_PROPERTY = "acknowledgeAudioItems";
538
539        private final SpeechRecognition mSpeechRecognition;
540        private final DtmfRecognition mDtmfRecognition;
541        private Duration mNoInputTimeout;
542        private List<AudioItem> mAcknowledgeAudioItems = new ArrayList<AudioItem>();
543
544        public FinalRecognitionWindow(DtmfRecognition dtmfRecognition,
545                                      SpeechRecognition speechRecognition,
546                                      Duration noInputTimeout) {
547            mSpeechRecognition = speechRecognition;
548            mDtmfRecognition = dtmfRecognition;
549            mNoInputTimeout = noInputTimeout;
550            assertInvariant();
551        }
552
553        public FinalRecognitionWindow(DtmfRecognition dtmfRecognition, SpeechRecognition speechRecognition) {
554            this(dtmfRecognition, speechRecognition, null);
555        }
556
557        public FinalRecognitionWindow(DtmfRecognition dtmfRecognition) {
558            this(dtmfRecognition, null, null);
559        }
560
561        public FinalRecognitionWindow(SpeechRecognition speechRecognition) {
562            this(null, speechRecognition, null);
563        }
564
565        public FinalRecognitionWindow(DtmfRecognition dtmfRecognition, Duration noInputTimeout) {
566            this(dtmfRecognition, null, noInputTimeout);
567        }
568
569        public FinalRecognitionWindow(SpeechRecognition speechRecognition, Duration noInputTimeout) {
570            this(null, speechRecognition, noInputTimeout);
571        }
572
573        public SpeechRecognition getSpeechRecognition() {
574            return mSpeechRecognition;
575        }
576
577        public DtmfRecognition getDtmfRecognition() {
578            return mDtmfRecognition;
579        }
580
581        public Duration getNoInputTimeout() {
582            return mNoInputTimeout;
583        }
584
585        public List<? extends AudioItem> getAcknowledgeAudioItems() {
586            return Collections.unmodifiableList(mAcknowledgeAudioItems);
587        }
588
589        public void setNoInputTimeout(Duration noInputTimeout) {
590            mNoInputTimeout = noInputTimeout;
591        }
592
593        public void setAcknowledgeAudioItems(List<? extends AudioItem> acknowledgeAudioItems) {
594            Assert.noNullValues(acknowledgeAudioItems, "acknowledgeAudioItems");
595            mAcknowledgeAudioItems = new ArrayList<AudioItem>(acknowledgeAudioItems);
596        }
597
598        public void setAcknowledgeAudioItems(AudioItem... acknowledgeAudioItems) {
599            setAcknowledgeAudioItems(asListChecked(acknowledgeAudioItems));
600        }
601
602        private void assertInvariant() {
603            Assert.ensure(mSpeechRecognition != null || mDtmfRecognition != null,
604                          "Must provide a non-null configuration for speech or DTMF");
605        }
606
607        @Override
608        public JsonValue asJson() {
609            JsonObjectBuilder builder = JsonUtils.createObjectBuilder();
610            JsonUtils.add(builder, ACKNOWLEDGE_AUDIO_ITEMS_PROPERTY, JsonUtils.toJson(mAcknowledgeAudioItems));
611            JsonUtils.add(builder, DTMF_RECOGNITION_PROPERTY, mDtmfRecognition);
612            JsonUtils.add(builder, SPEECH_RECOGNITION_PROPERTY, mSpeechRecognition);
613            JsonUtils.addDurationProperty(builder, NO_INPUT_TIMEOUT_PROPERTY, mNoInputTimeout);
614            return builder.build();
615        }
616
617        @Override
618        public int hashCode() {
619            final int prime = 31;
620            int result = 1;
621            result = prime * result + ((mAcknowledgeAudioItems == null) ? 0 : mAcknowledgeAudioItems.hashCode());
622            result = prime * result + ((mDtmfRecognition == null) ? 0 : mDtmfRecognition.hashCode());
623            result = prime * result + ((mNoInputTimeout == null) ? 0 : mNoInputTimeout.hashCode());
624            result = prime * result + ((mSpeechRecognition == null) ? 0 : mSpeechRecognition.hashCode());
625            return result;
626        }
627
628        @Override
629        public boolean equals(Object obj) {
630            if (this == obj) return true;
631            if (obj == null) return false;
632            if (getClass() != obj.getClass()) return false;
633            FinalRecognitionWindow other = (FinalRecognitionWindow) obj;
634            if (mAcknowledgeAudioItems == null) {
635                if (other.mAcknowledgeAudioItems != null) return false;
636            } else if (!mAcknowledgeAudioItems.equals(other.mAcknowledgeAudioItems)) return false;
637            if (mDtmfRecognition == null) {
638                if (other.mDtmfRecognition != null) return false;
639            } else if (!mDtmfRecognition.equals(other.mDtmfRecognition)) return false;
640            if (mNoInputTimeout == null) {
641                if (other.mNoInputTimeout != null) return false;
642            } else if (!mNoInputTimeout.equals(other.mNoInputTimeout)) return false;
643            if (mSpeechRecognition == null) {
644                if (other.mSpeechRecognition != null) return false;
645            } else if (!mSpeechRecognition.equals(other.mSpeechRecognition)) return false;
646            return true;
647        }
648
649        @Override
650        public String toString() {
651            return asJson().toString();
652        }
653    }
654
655    /**
656     * A {@link FinalRecordingWindow} is an optional final phase of an
657     * {@link Interaction}.
658     * <p>
659     * It specifies a recording configuration, and optionally, a no input
660     * timeout and a sequence of {@link AudioItem} that is played if a recording
661     * is successful.
662     *
663     * @author Nu Echo Inc.
664     */
665    public static final class FinalRecordingWindow implements JsonSerializable {
666        private static final String ACKNOWLEDGE_AUDIO_ITEMS_PROPERTY = "acknowledgeAudioItems";
667        @SuppressWarnings("hiding")
668        private static final String RECORDING_PROPERTY = "recording";
669        private static final String NO_INPUT_TIMEOUT_PROPERTY = "noInputTimeout";
670
671        private final Recording mRecording;
672        private Duration mNoInputTimeout;
673        private List<AudioItem> mAcknowledgeAudioItems = new ArrayList<AudioItem>();
674
675        public FinalRecordingWindow(Recording recording) {
676            this(recording, null);
677        }
678
679        public FinalRecordingWindow(Recording recording, Duration noInputTimeout) {
680            Assert.notNull(recording, "recording");
681            mRecording = recording;
682            mNoInputTimeout = noInputTimeout;
683        }
684
685        public Recording getRecording() {
686            return mRecording;
687        }
688
689        public Duration getNoInputTimeout() {
690            return mNoInputTimeout;
691        }
692
693        public List<? extends AudioItem> getAcknowledgeAudioItems() {
694            return Collections.unmodifiableList(mAcknowledgeAudioItems);
695        }
696
697        public void setAcknowledgeAudioItems(List<? extends AudioItem> acknowledgeAudioItems) {
698            Assert.noNullValues(acknowledgeAudioItems, "acknowledgeAudioItems");
699            mAcknowledgeAudioItems = new ArrayList<AudioItem>(acknowledgeAudioItems);
700        }
701
702        public void setAcknowledgeAudioItems(AudioItem... acknowledgeAudioItems) {
703            setAcknowledgeAudioItems(asListChecked(acknowledgeAudioItems));
704        }
705
706        public void setNoInputTimeout(Duration noInputTimeout) {
707            mNoInputTimeout = noInputTimeout;
708        }
709
710        @Override
711        public JsonValue asJson() {
712            JsonObjectBuilder builder = JsonUtils.createObjectBuilder();
713            JsonUtils.addDurationProperty(builder, NO_INPUT_TIMEOUT_PROPERTY, mNoInputTimeout);
714            JsonUtils.add(builder, RECORDING_PROPERTY, mRecording);
715            JsonUtils.add(builder, ACKNOWLEDGE_AUDIO_ITEMS_PROPERTY, JsonUtils.toJson(mAcknowledgeAudioItems));
716            return builder.build();
717        }
718
719        @Override
720        public int hashCode() {
721            final int prime = 31;
722            int result = 1;
723            result = prime * result + ((mAcknowledgeAudioItems == null) ? 0 : mAcknowledgeAudioItems.hashCode());
724            result = prime * result + ((mNoInputTimeout == null) ? 0 : mNoInputTimeout.hashCode());
725            result = prime * result + ((mRecording == null) ? 0 : mRecording.hashCode());
726            return result;
727        }
728
729        @Override
730        public boolean equals(Object obj) {
731            if (this == obj) return true;
732            if (obj == null) return false;
733            if (getClass() != obj.getClass()) return false;
734            FinalRecordingWindow other = (FinalRecordingWindow) obj;
735            if (mAcknowledgeAudioItems == null) {
736                if (other.mAcknowledgeAudioItems != null) return false;
737            } else if (!mAcknowledgeAudioItems.equals(other.mAcknowledgeAudioItems)) return false;
738            if (mNoInputTimeout == null) {
739                if (other.mNoInputTimeout != null) return false;
740            } else if (!mNoInputTimeout.equals(other.mNoInputTimeout)) return false;
741            if (mRecording == null) {
742                if (other.mRecording != null) return false;
743            } else if (!mRecording.equals(other.mRecording)) return false;
744            return true;
745        }
746
747        @Override
748        public String toString() {
749            return asJson().toString();
750        }
751    }
752
753    /**
754     * A {@link Prompt} represent a phase in an {@link Interaction} and is
755     * composed of a sequence of {@link AudioItem} and optionally a speech
756     * and/or a DTMF recognition configuration.
757     *
758     * @author Nu Echo Inc.
759     */
760    public static final class Prompt implements JsonSerializable {
761        private static final String SPEECH_RECOGNITION_PROPERTY = "speechRecognition";
762        private static final String DTMF_RECOGNITION_PROPERTY = "dtmfRecognition";
763        @SuppressWarnings("hiding")
764        private static final String BARGE_IN_TYPE_PROPERTY = "bargeInType";
765        private static final String AUDIO_ITEMS_PROPERTY = "audioItems";
766        private static final String LANGUAGE_PROPERTY = "language";
767
768        private final List<AudioItem> mAudioItems;
769        private final SpeechRecognition mSpeechRecognition;
770        private final DtmfRecognition mDtmfRecognition;
771
772        private String mLanguage;
773        private BargeInType mBargeInType;
774
775        /**
776         * @param speechRecognition The speech recognition configuration.
777         *            Optional.
778         * @param dtmfRecognition The DTMF recognition configuration. Optional.
779         * @param audioItems The list of {@link AudioItem}. Not null.
780         */
781        public Prompt(SpeechRecognition speechRecognition,
782                      DtmfRecognition dtmfRecognition,
783                      List<? extends AudioItem> audioItems) {
784            Assert.noNullValues(audioItems, "audioItems");
785            mAudioItems = new ArrayList<AudioItem>(audioItems);
786            mSpeechRecognition = speechRecognition;
787            mDtmfRecognition = dtmfRecognition;
788        }
789
790        /**
791         * @param speechRecognition The speech recognition configuration.
792         * @param dtmfRecognition The DTMF recognition configuration.
793         * @param audioItems The list of {@link AudioItem}. Not null.
794         */
795        public Prompt(SpeechRecognition speechRecognition, DtmfRecognition dtmfRecognition, AudioItem... audioItems) {
796            this(speechRecognition, dtmfRecognition, asListChecked(audioItems));
797        }
798
799        /**
800         * @param speechRecognition The speech recognition configuration.
801         * @param audioItems The list of {@link AudioItem}. Not null.
802         */
803        public Prompt(SpeechRecognition speechRecognition, List<? extends AudioItem> audioItems) {
804            this(speechRecognition, null, audioItems);
805        }
806
807        /**
808         * @param speechRecognition The speech recognition configuration.
809         * @param audioItems The list of {@link AudioItem}. Not null.
810         */
811        public Prompt(SpeechRecognition speechRecognition, AudioItem... audioItems) {
812            this(speechRecognition, null, audioItems);
813        }
814
815        /**
816         * @param dtmfRecognition The DTMF recognition configuration.
817         * @param audioItems The list of {@link AudioItem}. Not null.
818         */
819        public Prompt(DtmfRecognition dtmfRecognition, List<? extends AudioItem> audioItems) {
820            this(null, dtmfRecognition, audioItems);
821        }
822
823        /**
824         * @param dtmfRecognition The DTMF recognition configuration.
825         * @param audioItems The list of {@link AudioItem}. Not null.
826         */
827        public Prompt(DtmfRecognition dtmfRecognition, AudioItem... audioItems) {
828            this(null, dtmfRecognition, audioItems);
829        }
830
831        /**
832         * @param audioItems The list of {@link AudioItem}. Not null.
833         */
834        public Prompt(List<? extends AudioItem> audioItems) {
835            this(null, null, audioItems);
836        }
837
838        /**
839         * @param audioItems The list of {@link AudioItem}. Not null.
840         */
841        public Prompt(AudioItem... audioItems) {
842            this(null, null, audioItems);
843        }
844
845        /**
846         * @param language language code for this prompt. <code>null</code> if
847         *            language should be reset to platform-specific default
848         *            value for the prompts to be added.
849         */
850        public void setLanguage(String language) {
851            mLanguage = language;
852        }
853
854        /**
855         * @param bargeInType {@link BargeInType#speech} or
856         *            {@link BargeInType#hotword}. <code>null</code> if language
857         *            should be reset to platform-specific default value for the
858         *            prompts to be added.
859         */
860        public void setHotWordBargeIn(BargeInType bargeInType) {
861            mBargeInType = bargeInType;
862        }
863
864        public List<? extends AudioItem> getAudioItems() {
865            return Collections.unmodifiableList(mAudioItems);
866        }
867
868        public String getLanguage() {
869            return mLanguage;
870        }
871
872        public SpeechRecognition getSpeechRecognition() {
873            return mSpeechRecognition;
874        }
875
876        public DtmfRecognition getDtmfRecognition() {
877            return mDtmfRecognition;
878        }
879
880        public BargeInType getBargeInType() {
881            return mBargeInType;
882        }
883
884        @Override
885        public JsonValue asJson() {
886            JsonObjectBuilder builder = JsonUtils.createObjectBuilder();
887            JsonUtils.add(builder, LANGUAGE_PROPERTY, mLanguage);
888            JsonUtils.add(builder, BARGE_IN_TYPE_PROPERTY, mBargeInType == null ? null : mBargeInType.name());
889            JsonUtils.add(builder, AUDIO_ITEMS_PROPERTY, JsonUtils.toJson(mAudioItems));
890            JsonUtils.add(builder, DTMF_RECOGNITION_PROPERTY, mDtmfRecognition);
891            JsonUtils.add(builder, SPEECH_RECOGNITION_PROPERTY, mSpeechRecognition);
892            return builder.build();
893        }
894
895        @Override
896        public int hashCode() {
897            final int prime = 31;
898            int result = 1;
899            result = prime * result + ((mAudioItems == null) ? 0 : mAudioItems.hashCode());
900            result = prime * result + ((mBargeInType == null) ? 0 : mBargeInType.hashCode());
901            result = prime * result + ((mDtmfRecognition == null) ? 0 : mDtmfRecognition.hashCode());
902            result = prime * result + ((mLanguage == null) ? 0 : mLanguage.hashCode());
903            result = prime * result + ((mSpeechRecognition == null) ? 0 : mSpeechRecognition.hashCode());
904            return result;
905        }
906
907        @Override
908        public boolean equals(Object obj) {
909            if (this == obj) return true;
910            if (obj == null) return false;
911            if (getClass() != obj.getClass()) return false;
912            Prompt other = (Prompt) obj;
913            if (mAudioItems == null) {
914                if (other.mAudioItems != null) return false;
915            } else if (!mAudioItems.equals(other.mAudioItems)) return false;
916            if (mBargeInType != other.mBargeInType) return false;
917            if (mDtmfRecognition == null) {
918                if (other.mDtmfRecognition != null) return false;
919            } else if (!mDtmfRecognition.equals(other.mDtmfRecognition)) return false;
920            if (mLanguage == null) {
921                if (other.mLanguage != null) return false;
922            } else if (!mLanguage.equals(other.mLanguage)) return false;
923            if (mSpeechRecognition == null) {
924                if (other.mSpeechRecognition != null) return false;
925            } else if (!mSpeechRecognition.equals(other.mSpeechRecognition)) return false;
926            return true;
927        }
928
929        @Override
930        public String toString() {
931            return asJson().toString();
932        }
933    }
934
935    /**
936     * Builder used to ease the creation of instances of {@link Interaction}.
937     * <p>
938     * Building an {@link Interaction} implies the following steps:
939     * <ul>
940     * <li>Add some prompts
941     * <ul>
942     * <li>For each prompt, speech/DTMF recognition can be specified (thus
943     * enabling <i>barge-in</i> when the prompt is played).
944     * </ul>
945     * <li>Once all prompts are added, optionally specify either:
946     * <ul>
947     * <li>a final recognition window (speech or DTMF)
948     * <li>a final recording window
949     * </ul>
950     * </ul>
951     * <p>
952     * At any time, it is possible to change the current language used for
953     * prompts (relevant to speech synthesis) and the barge-in type, i.e.
954     * <i>speech</i> or <i>hotword</i>.
955     * <p>
956     * This can be translated to:
957     *
958     * <pre>
959     * Builder builder = new Builder();
960     * builder.addPrompt(...);
961     * builder.addPrompt(...);
962     * //... repeat as needed
963     * Interaction interaction = builder.build(...);
964     * </pre>
965     *
966     * @author Nu Echo Inc.
967     */
968    public static final class Builder {
969        private final String mName;
970        private final List<Prompt> mPrompts = new ArrayList<Prompt>();
971
972        private String mLanguage;
973        private BargeInType mBargeInType;
974
975        private boolean mBuilt;
976
977        public Builder(String name) {
978            mName = name;
979        }
980
981        /**
982         * Sets the barge-in type to either {@link BargeInType#speech} or
983         * {@link BargeInType#hotword} for the prompts that will be added using
984         * one of the <code>addPrompt(...)</code> methods.
985         * <p>
986         * Note: When a {@link Builder} is created, the default value for this
987         * flag is <code>null</code>, meaning the barge-in type will be
988         * platform-dependant.
989         *
990         * @param bargeInType
991         *            <ul>
992         *            <li>{@link BargeInType#speech}
993         *            <li>{@link BargeInType#hotword}
994         *            <li><code>null</code> reverts to platform default value
995         *            </ul>
996         * @return this builder
997         */
998        public Builder setBargeInType(BargeInType bargeInType) {
999            mBargeInType = bargeInType;
1000            return this;
1001        }
1002
1003        /**
1004         * Sets the language code for the prompts that will be added using one
1005         * of the <code>addPrompt(...)</code> methods.
1006         * <p>
1007         * Note: When a @{link Builder} is created, the default value for this
1008         * property is <code>null</code>, i.e. no explicit language code will be
1009         * generated in the VoiceXML thus relying on the platform-specific
1010         * default language code.
1011         *
1012         * @param language language code for this prompt. <code>null</code> if
1013         *            language should be reset to platform-specific default
1014         *            value for the prompts to be added.
1015         * @return this builder
1016         */
1017        public Builder setLanguage(String language) {
1018            mLanguage = language;
1019            return this;
1020        }
1021
1022        /**
1023         * Adds a prompt with DTMF recognition only.
1024         *
1025         * @param dtmfRecognition configuration for the DTMF recognition
1026         * @param audioItems audio items to be played during this prompt.
1027         * @return this builder
1028         */
1029        public Builder addPrompt(DtmfRecognition dtmfRecognition, AudioItem... audioItems) {
1030            return addPrompt(dtmfRecognition, null, asListChecked(audioItems));
1031        }
1032
1033        /**
1034         * Adds a prompt with DTMF recognition only.
1035         *
1036         * @param dtmfRecognition configuration for the DTMF recognition
1037         * @param audioItems audio items to be played during this prompt.
1038         * @return this builder
1039         */
1040        public Builder addPrompt(DtmfRecognition dtmfRecognition, List<? extends AudioItem> audioItems) {
1041            return addPrompt(dtmfRecognition, null, audioItems);
1042        }
1043
1044        /**
1045         * Adds a prompt with speech recognition only.
1046         *
1047         * @param speechRecognition configuration for the speech recognition
1048         * @param audioItems audio items to be played during this prompt.
1049         * @return this builder
1050         */
1051        public Builder addPrompt(SpeechRecognition speechRecognition, AudioItem... audioItems) {
1052            return addPrompt(null, speechRecognition, asListChecked(audioItems));
1053        }
1054
1055        /**
1056         * Adds a prompt with speech recognition only.
1057         *
1058         * @param speechRecognition configuration for the speech recognition
1059         * @param audioItems audio items to be played during this prompt.
1060         * @return this builder
1061         */
1062        public Builder addPrompt(SpeechRecognition speechRecognition, List<? extends AudioItem> audioItems) {
1063            return addPrompt(null, speechRecognition, audioItems);
1064        }
1065
1066        /**
1067         * Adds a prompt with both DTMF and speech recognition.
1068         *
1069         * @param speechRecognition configuration for the speech recognition or
1070         *            <code>null</code> to disable DTMF recognition.
1071         * @param dtmfRecognition configuration for the DTMF recognition or
1072         *            <code>null</code> to disable DTMF recognition.
1073         * @param audioItems audio items to be played during this prompt.
1074         * @return this builder
1075         */
1076        public Builder addPrompt(DtmfRecognition dtmfRecognition,
1077                                 SpeechRecognition speechRecognition,
1078                                 AudioItem... audioItems) {
1079            return addPrompt(dtmfRecognition, speechRecognition, asListChecked(audioItems));
1080        }
1081
1082        /**
1083         * Adds a prompt with both DTMF and speech recognition.
1084         *
1085         * @param speechRecognition configuration for the speech recognition or
1086         *            <code>null</code> to disable DTMF recognition.
1087         * @param dtmfRecognition configuration for the DTMF recognition or
1088         *            <code>null</code> to disable DTMF recognition.
1089         * @param audioItems audio items to be played during this prompt.
1090         * @return this builder
1091         */
1092        public Builder addPrompt(DtmfRecognition dtmfRecognition,
1093                                 SpeechRecognition speechRecognition,
1094                                 List<? extends AudioItem> audioItems) {
1095            Assert.noNullValues(audioItems, "audioItems");
1096            Prompt prompt = new Prompt(speechRecognition, dtmfRecognition, audioItems);
1097            prompt.setLanguage(mLanguage);
1098            prompt.setHotWordBargeIn(mBargeInType);
1099            mPrompts.add(prompt);
1100            return this;
1101        }
1102
1103        /**
1104         * Adds a prompt without any DTMF nor speech recognition (no barge-in).
1105         *
1106         * @param audioItems audio items to be played during this prompt.
1107         * @return this builder
1108         */
1109        public Builder addPrompt(AudioItem... audioItems) {
1110            return addPrompt(asListChecked(audioItems));
1111        }
1112
1113        /**
1114         * Adds a prompt without any DTMF nor speech recognition (no barge-in).
1115         *
1116         * @param audioItems audio items to be played during this prompt.
1117         * @return this builder
1118         */
1119        public Builder addPrompt(List<? extends AudioItem> audioItems) {
1120            Assert.noNullValues(audioItems, "audioItems");
1121            Prompt prompt = new Prompt(audioItems);
1122            prompt.setLanguage(mLanguage);
1123            prompt.setHotWordBargeIn(mBargeInType);
1124            mPrompts.add(prompt);
1125            return this;
1126        }
1127
1128        /**
1129         * Builds the interaction. This method adds a DTMF recognition window
1130         * after the prompts.
1131         *
1132         * @param dtmfRecognition configuration for the DTMF recognition
1133         * @param noinputTimeout timeout value before a <code>noinput</code> is
1134         *            generated.
1135         * @param acknowledgeAudioItems audioItems to be played upon recognition
1136         * @return the interaction
1137         */
1138        public Interaction build(DtmfRecognition dtmfRecognition,
1139                                 Duration noinputTimeout,
1140                                 AudioItem... acknowledgeAudioItems) {
1141            return build(dtmfRecognition, null, noinputTimeout, asListChecked(acknowledgeAudioItems));
1142        }
1143
1144        /**
1145         * Builds the interaction. This method adds a speech recognition window
1146         * after the prompts.
1147         *
1148         * @param speechRecognition configuration for the speech recognition
1149         * @param noinputTimeout timeout value before a <code>noinput</code> is
1150         *            generated.
1151         * @param acknowledgeAudioItems audioItems to be played upon recognition
1152         * @return the interaction
1153         */
1154        public Interaction build(SpeechRecognition speechRecognition,
1155                                 Duration noinputTimeout,
1156                                 AudioItem... acknowledgeAudioItems) {
1157            return build(null, speechRecognition, noinputTimeout, asListChecked(acknowledgeAudioItems));
1158        }
1159
1160        /**
1161         * Builds the interaction. This method adds a DTMF recognition window
1162         * after the prompts.
1163         *
1164         * @param dtmfRecognition configuration for the DTMF recognition
1165         * @param noinputTimeout timeout value before a <code>noinput</code> is
1166         *            generated.
1167         * @param acknowledgeAudioItems audioItems to be played upon recognition
1168         * @return the interaction
1169         */
1170        public Interaction build(DtmfRecognition dtmfRecognition,
1171                                 Duration noinputTimeout,
1172                                 List<? extends AudioItem> acknowledgeAudioItems) {
1173            return build(dtmfRecognition, null, noinputTimeout, acknowledgeAudioItems);
1174
1175        }
1176
1177        /**
1178         * Builds the interaction. This method adds a speech recognition window
1179         * after the prompts.
1180         *
1181         * @param speechRecognition configuration for the speech recognition
1182         * @param noinputTimeout timeout value before a <code>noinput</code> is
1183         *            generated.
1184         * @param acknowledgeAudioItems audioItems to be played upon recognition
1185         * @return the interaction
1186         */
1187        public Interaction build(SpeechRecognition speechRecognition,
1188                                 Duration noinputTimeout,
1189                                 List<? extends AudioItem> acknowledgeAudioItems) {
1190            return build(null, speechRecognition, noinputTimeout, acknowledgeAudioItems);
1191
1192        }
1193
1194        /**
1195         * Builds the interaction. This method adds a speech and DTMF
1196         * recognition window after the prompts.
1197         *
1198         * @param speechRecognition configuration for the speech recognition
1199         * @param dtmfRecognition configuration for the DTMF recognition
1200         * @param noinputTimeout timeout value before a <code>noinput</code> is
1201         *            generated.
1202         * @param acknowledgeAudioItems audioItems to be played upon recognition
1203         * @return the interaction
1204         */
1205        public Interaction build(DtmfRecognition dtmfRecognition,
1206                                 SpeechRecognition speechRecognition,
1207                                 Duration noinputTimeout,
1208                                 AudioItem... acknowledgeAudioItems) {
1209            return build(dtmfRecognition, speechRecognition, noinputTimeout, asListChecked(acknowledgeAudioItems));
1210        }
1211
1212        /**
1213         * Builds the interaction. This method adds a speech and DTMF
1214         * recognition window after the prompts.
1215         *
1216         * @param speechRecognition configuration for the speech recognition
1217         * @param dtmfRecognition configuration for the DTMF recognition
1218         * @param noinputTimeout timeout value before a <code>noinput</code> is
1219         *            generated.
1220         * @param acknowledgeAudioItems audioItems to be played upon recognition
1221         * @return the interaction
1222         */
1223        public Interaction build(DtmfRecognition dtmfRecognition,
1224                                 SpeechRecognition speechRecognition,
1225                                 Duration noinputTimeout,
1226                                 List<? extends AudioItem> acknowledgeAudioItems) {
1227            Assert.ensure(dtmfRecognition != null || speechRecognition != null,
1228                          "Must provide at least one recognition configuration (speech or DTMF)");
1229
1230            checkBuilt();
1231            FinalRecognitionWindow finalRecognitionWindow = new FinalRecognitionWindow(dtmfRecognition,
1232                                                                                       speechRecognition,
1233                                                                                       noinputTimeout);
1234
1235            if (acknowledgeAudioItems != null) {
1236                finalRecognitionWindow.setAcknowledgeAudioItems(acknowledgeAudioItems);
1237            }
1238
1239            return new Interaction(mName, mPrompts, finalRecognitionWindow);
1240
1241        }
1242
1243        /**
1244         * Builds the interaction. This method adds a recording window after the
1245         * prompts.
1246         *
1247         * @param recording configuration for the recording
1248         * @param noinputTimeout timeout value before a <code>noinput</code> is
1249         *            generated.
1250         * @param acknowledgeAudioItems audioItems to be played upon recording
1251         *            completion
1252         * @return the interaction
1253         */
1254        public Interaction build(Recording recording, Duration noinputTimeout, AudioItem... acknowledgeAudioItems) {
1255            return build(recording, noinputTimeout, asListChecked(acknowledgeAudioItems));
1256        }
1257
1258        /**
1259         * Builds the interaction. This method adds a recording window after the
1260         * prompts.
1261         *
1262         * @param recording configuration for the recording
1263         * @param noinputTimeout timeout value before a <code>noinput</code> is
1264         *            generated.
1265         * @param acknowledgeAudioItems audioItems to be played upon recording
1266         *            completion
1267         * @return the interaction
1268         */
1269        public Interaction build(Recording recording,
1270                                 Duration noinputTimeout,
1271                                 List<? extends AudioItem> acknowledgeAudioItems) {
1272            Assert.notNull(recording, "recording");
1273            Assert.notNull(acknowledgeAudioItems, "acknowledgeAudioItems");
1274
1275            checkBuilt();
1276            FinalRecordingWindow finalRecordingWindow = new FinalRecordingWindow(recording, noinputTimeout);
1277
1278            if (acknowledgeAudioItems != null) {
1279                finalRecordingWindow.setAcknowledgeAudioItems(acknowledgeAudioItems);
1280            }
1281
1282            return new Interaction(mName, mPrompts, finalRecordingWindow);
1283        }
1284
1285        /**
1286         * Builds the interaction. This method does not allow any recording nor
1287         * recognition after the prompts.
1288         *
1289         * @return the interaction
1290         */
1291        public Interaction build() {
1292            checkBuilt();
1293            return new Interaction(mName, mPrompts);
1294        }
1295
1296        private void checkBuilt() {
1297            if (mBuilt) throw new IllegalStateException("build() method was already called.");
1298            mBuilt = true;
1299        }
1300    }
1301
1302}