/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2016, Telestax Inc and individual contributors * by the @authors tag. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.restcomm.media.control.mgcp.pkg.au.pc; import java.util.Map; import org.restcomm.media.control.mgcp.pkg.au.Playlist; import org.restcomm.media.control.mgcp.pkg.au.SignalParameters; import org.restcomm.media.spi.dtmf.DtmfDetector; import org.restcomm.media.spi.dtmf.DtmfDetectorListener; import com.google.common.base.Optional; /** * @author Henrique Rosa (henrique.rosa@telestax.com) * */ public class PlayCollectContext { // Signal options private final Map<String, String> parameters; // Playlists private final Playlist initialPrompt; private final Playlist reprompt; private final Playlist noDigitsReprompt; private final Playlist failureAnnouncement; private final Playlist successAnnouncement; // Runtime data private final StringBuilder collectedDigits; private long lastCollectedDigitOn; private char lastTone; private int attempt; private int returnCode; public PlayCollectContext(DtmfDetector detector, DtmfDetectorListener detectorListener, Map<String, String> parameters) { // Signal Options this.parameters = parameters; // Playlists this.initialPrompt = new Playlist(getInitialPromptSegments(), 1); this.reprompt = new Playlist(getRepromptSegments(), 1); this.noDigitsReprompt = new Playlist(getNoDigitsRepromptSegments(), 1); this.failureAnnouncement = new Playlist(getFailureAnnouncementSegments(), 1); this.successAnnouncement = new Playlist(getSuccessAnnouncementSegments(), 1); // Runtime Data this.collectedDigits = new StringBuilder(""); this.lastCollectedDigitOn = 0L; this.lastTone = ' '; this.returnCode = 0; this.attempt = 1; } /* * Signal Options */ private String getParameter(String name) { return this.parameters.get(name); } /** * The initial announcement prompting the user to either enter DTMF digits or to speak. * <p> * Consists of one or more audio segments.<br> * If not specified (the default), the event immediately begins digit collection or recording. * </p> * * @return The array of audio prompts. Array will be empty if none is specified. */ private String[] getInitialPromptSegments() { String value = Optional.fromNullable(getParameter(SignalParameters.INITIAL_PROMPT.symbol())).or(""); return value.isEmpty() ? new String[0] : value.split(","); } public Playlist getInitialPrompt() { return initialPrompt; } /** * Played after the user has made an error such as entering an invalid digit pattern or not speaking. * <p> * Consists of one or more audio segments. <b>Defaults to the Initial Prompt.</b> * </p> * * @return The array of audio prompts. Array will be empty if none is specified. */ private String[] getRepromptSegments() { String segments = Optional.fromNullable(getParameter(SignalParameters.REPROMPT.symbol())).or(""); return segments.isEmpty() ? getInitialPromptSegments() : segments.split(","); } public Playlist getReprompt() { return reprompt; } /** * Played after the user has failed to enter a valid digit pattern during a PlayCollect event. * <p> * Consists of one or more audio segments. <b>Defaults to the Reprompt.</b> * </p> * * @return The array of audio prompts. Array will be empty if none is specified. */ private String[] getNoDigitsRepromptSegments() { String segments = Optional.fromNullable(getParameter(SignalParameters.NO_DIGITS_REPROMPT.symbol())).or(""); return segments.isEmpty() ? getRepromptSegments() : segments.split(","); } public Playlist getNoDigitsReprompt() { return noDigitsReprompt; } /** * Played when all data entry attempts have failed. * <p> * Consists of one or more audio segments. No default. * </p> * * @return The array of audio prompts. Array will be empty if none is specified. */ private String[] getFailureAnnouncementSegments() { String value = Optional.fromNullable(getParameter(SignalParameters.FAILURE_ANNOUNCEMENT.symbol())).or(""); return value.isEmpty() ? new String[0] : value.split(","); } public Playlist getFailureAnnouncement() { return failureAnnouncement; } public boolean hasFailureAnnouncement() { return !this.failureAnnouncement.isEmpty(); } /** * Played when all data entry attempts have succeeded. * <p> * Consists of one or more audio segments. No default. * </p> * * @return The array of audio prompts. Array will be empty if none is specified. */ private String[] getSuccessAnnouncementSegments() { String value = Optional.fromNullable(getParameter(SignalParameters.SUCCESS_ANNOUNCEMENT.symbol())).or(""); return value.isEmpty() ? new String[0] : value.split(","); } public Playlist getSuccessAnnouncement() { return successAnnouncement; } public boolean hasSuccessAnnouncement() { return !this.successAnnouncement.isEmpty(); } /** * If set to true, initial prompt is not interruptible by either voice or digits. * <p> * <b>Defaults to false.</b> Valid values are the text strings "true" and "false". * </p> * * @return */ public boolean getNonInterruptibleAudio() { String value = Optional.fromNullable(getParameter(SignalParameters.NON_INTERRUPTIBLE_PLAY.symbol())).or("false"); return Boolean.parseBoolean(value); } /** * If set to true, clears the digit buffer before playing the initial prompt. * <p> * <b>Defaults to false.</b> Valid values are the text strings "true" and "false". * </p> * * @return */ public boolean getClearDigitBuffer() { String value = Optional.fromNullable(getParameter(SignalParameters.CLEAR_DIGIT_BUFFER.symbol())).or("false"); return Boolean.parseBoolean(value); } /** * The minimum number of digits to collect. * <p> * <b>Defaults to one.</b> This parameter should not be specified if the Digit Pattern parameter is present. * </p> * * @return */ public int getMinimumDigits() { String value = Optional.fromNullable(getParameter(SignalParameters.MINIMUM_NUM_DIGITS.symbol())).or("1"); return Integer.parseInt(value); } /** * The maximum number of digits to collect. * <p> * <b>Defaults to one.</b> This parameter should not be specified if the Digit Pattern parameter is present. * </p> * * @return */ public int getMaximumDigits() { String value = Optional.fromNullable(getParameter(SignalParameters.MAXIMUM_NUM_DIGITS.symbol())).or("1"); return Integer.parseInt(value); } /** * A legal digit map as described in <a href="https://tools.ietf.org/html/rfc2885#section-7.1.14">section 7.1.14</a> of the * MEGACO protocol using the DTMF mappings associated with the Megaco DTMF Detection Package described in the Megaco * protocol document. * <p> * <b>This parameter should not be specified if one or both of the Minimum # Of Digits parameter and the Maximum Number Of * Digits parameter is present.</b> * </p> * * @return The digit pattern or an empty String if not specified. */ public String getDigitPattern() { String pattern = Optional.fromNullable(getParameter(SignalParameters.DIGIT_PATTERN.symbol())).or(""); if (!pattern.isEmpty()) { // Replace pattern to comply with MEGACO digitMap pattern = pattern.replace(".", "*").replace("x", "\\d"); } return pattern; } public boolean hasDigitPattern() { return !Optional.fromNullable(getParameter(SignalParameters.DIGIT_PATTERN.symbol())).or("").isEmpty(); } /** * The amount of time allowed for the user to enter the first digit. * <p> * Specified in units of 100 milliseconds. <b>Defaults to 50 (5 seconds).</b> * </p> * * @return */ public int getFirstDigitTimer() { String value = Optional.fromNullable(getParameter(SignalParameters.FIRST_DIGIT_TIMER.symbol())).or("50"); return Integer.parseInt(value) * 100; } /** * The amount of time allowed for the user to enter each subsequent digit. * <p> * Specified units of 100 milliseconds seconds. <b>Defaults to 30 (3 seconds).</b> * </p> * * @return */ public int getInterDigitTimer() { String value = Optional.fromNullable(getParameter(SignalParameters.INTER_DIGIT_TIMER.symbol())).or("30"); return Integer.parseInt(value) * 100; } /** * The amount of time to wait for a user to enter a final digit once the maximum expected amount of digits have been * entered. * <p> * Typically this timer is used to wait for a terminating key in applications where a specific key has been defined to * terminate input. * </p> * <p> * Specified in units of 100 milliseconds. </b>If not specified, this timer is not activated.</b> * </p> * * @return */ public int getExtraDigitTimer() { String value = Optional.fromNullable(getParameter(SignalParameters.EXTRA_DIGIT_TIMER.symbol())).or(""); return Integer.parseInt(value) * 100; } /** * Defines a key sequence consisting of a command key optionally followed by zero or more keys. This key sequence has the * following action: discard any digits collected or recording in progress, replay the prompt, and resume digit collection * or recording. * <p> * <b>No default.</b> An application that defines more than one command key sequence, will typically use the same command * key for all command key sequences. * <p> * If more than one command key sequence is defined, then all key sequences must consist of a command key plus at least one * other key. * </p> * * @return */ public char getRestartKey() { String value = Optional.fromNullable(getParameter(SignalParameters.RESTART_KEY.symbol())).or(""); return value.isEmpty() ? ' ' : value.charAt(0); } /** * Defines a key sequence consisting of a command key optionally followed by zero or more keys. This key sequence has the * following action: discard any digits collected or recordings in progress and resume digit collection or recording. * <p> * <b>No default.</b> * </p> * An application that defines more than one command key sequence, will typically use the same command key for all command * key sequences. * </p> * <p> * If more than one command key sequence is defined, then all key sequences must consist of a command key plus at least one * other key. * </p> * * @return */ public char getReinputKey() { String value = Optional.fromNullable(getParameter(SignalParameters.REINPUT_KEY.symbol())).or(""); return value.isEmpty() ? ' ' : value.charAt(0); } /** * Defines a key sequence consisting of a command key optionally followed by zero or more keys. This key sequence has the * following action: terminate the current event and any queued event and return the terminating key sequence to the call * processing agent. * <p> * <b> No default.</b> An application that defines more than one command key sequence, will typically use the same command * key for all command key sequences. * <p> * If more than one command key sequence is defined, then all key sequences must consist of a command key plus at least one * other key. * </p> * * @return */ public char getReturnKey() { String value = Optional.fromNullable(getParameter(SignalParameters.RETURN_KEY.symbol())).or(""); return value.isEmpty() ? ' ' : value.charAt(0); } /** * Defines a key with the following action. Stop playing the current announcement and resume playing at the beginning of the * first, last, previous, next, or the current segment of the announcement. * <p> * <b>No default. The actions for the position key are fst, lst, prv, nxt, and cur.</b> * </p> * * @return */ public char getPositionKey() { String value = Optional.fromNullable(getParameter(SignalParameters.POSITION_KEY.symbol())).or(""); return value.isEmpty() ? ' ' : value.charAt(0); } /** * Defines a key with the following action. Terminate playback of the announcement. * <p> * <b>No default.</b> * </p> * * @return */ public char getStopKey() { String value = Optional.fromNullable(getParameter(SignalParameters.STOP_KEY.symbol())).or(""); return value.isEmpty() ? ' ' : value.charAt(0); } /** * Defines a set of keys that are acceptable as the first digit collected. This set of keys can be specified to interrupt a * playing announcement or to not interrupt a playing announcement. * <p> * <b>The default key set is 0-9. The default behavior is to interrupt a playing announcement when a Start Input Key is * pressed.</b> * </p> * <p> * This behavior can be overidden for the initial prompt only by using the ni (Non-Interruptible Play) parameter. * Specification is a list of keys with no separators, e.g. 123456789#. * </p> * * @return */ public String getStartInputKeys() { return Optional.fromNullable(getParameter(SignalParameters.START_INPUT_KEY.symbol())).or("0123456789"); } /** * Specifies a key that signals the end of digit collection or voice recording. * <p> * <b>The default end input key is the # key.</b> To specify that no End Input Key be used the parameter is set to the * string "null". * <p> * <b>The default behavior not to return the End Input Key in the digits returned to the call agent.</b> This behavior can * be overridden by the Include End Input Key (eik) parameter. * </p> * * @return */ public char getEndInputKey() { String value = Optional.fromNullable(getParameter(SignalParameters.END_INPUT_KEY.symbol())).or(""); return value.isEmpty() ? '#' : value.charAt(0); } /** * By default the End Input Key is not included in the collected digits returned to the call agent. If this parameter is set * to "true" then the End Input Key will be returned with the collected digits returned to the call agent. * <p> * <b>Default is "false".</b> * </p> * * @return */ public boolean getIncludeEndInputKey() { String value = Optional.fromNullable(getParameter(SignalParameters.INCLUDE_END_INPUT_KEY.symbol())).or("false"); return Boolean.parseBoolean(value); } /** * The number of attempts the user needed to enter a valid digit pattern or to make a recording. * <p> * <b>Defaults to 1.</b> Also used as a return parameter to indicate the number of attempts the user made. * </p> * * @return */ public int getNumberOfAttempts() { String value = Optional.fromNullable(getParameter(SignalParameters.NUMBER_OF_ATTEMPTS.symbol())).or("1"); return Integer.parseInt(value); } /* * Runtime Data */ public void collectDigit(char digit) { this.collectedDigits.append(digit); this.lastCollectedDigitOn = System.currentTimeMillis(); } public void clearCollectedDigits() { this.collectedDigits.setLength(0); } public String getCollectedDigits() { return collectedDigits.toString(); } public int countCollectedDigits() { return collectedDigits.length(); } public long getLastCollectedDigitOn() { return lastCollectedDigitOn; } public char getLastTone() { return lastTone; } public void setLastTone(char lastTone) { this.lastTone = lastTone; } public int getAttempt() { return attempt; } public boolean hasMoreAttempts() { return this.attempt < getNumberOfAttempts(); } public int getReturnCode() { return returnCode; } protected void setReturnCode(int returnCode) { this.returnCode = returnCode; } /** * Resets the collected digits and increments the attempts counter. */ protected void newAttempt() { this.attempt++; this.collectedDigits.setLength(0); this.lastTone = ' '; this.initialPrompt.rewind(); this.reprompt.rewind(); this.noDigitsReprompt.rewind(); this.successAnnouncement.rewind(); this.failureAnnouncement.rewind(); } }