/*
* Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.mobileconnectors.lex.interactionkit.ui;
import android.content.Context;
import android.util.Log;
import android.view.View;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.mobileconnectors.lex.interactionkit.InteractionClient;
import com.amazonaws.mobileconnectors.lex.interactionkit.Response;
import com.amazonaws.mobileconnectors.lex.interactionkit.config.InteractionConfig;
import com.amazonaws.mobileconnectors.lex.interactionkit.continuations.LexServiceContinuation;
import com.amazonaws.mobileconnectors.lex.interactionkit.exceptions.InvalidParameterException;
import com.amazonaws.mobileconnectors.lex.interactionkit.exceptions.MaxSpeechTimeOutException;
import com.amazonaws.mobileconnectors.lex.interactionkit.exceptions.NoSpeechTimeOutException;
import com.amazonaws.mobileconnectors.lex.interactionkit.listeners.AudioPlaybackListener;
import com.amazonaws.mobileconnectors.lex.interactionkit.listeners.InteractionListener;
import com.amazonaws.mobileconnectors.lex.interactionkit.listeners.MicrophoneListener;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.lexrts.model.DialogState;
import java.util.HashMap;
import java.util.Map;
/**
* This class helps to easily interface the {@link InteractiveVoiceView} view's
* with the Amazon Lex client. It manages the client states and delivers results
* through separate callbacks. This implementation of the adapter offers only
* voice for voice out operations only.
*/
public class InteractiveVoiceViewAdapter
implements InteractionListener, AudioPlaybackListener, MicrophoneListener,
View.OnClickListener {
private final static String TAG = "Lex";
private static final String INTERACTION_VOICE_VIEW_USER_AGENT = "VOICE_BUTTON";
private final Context context;
private final InteractiveVoiceView micButton;
private InteractiveVoiceView.InteractiveVoiceListener voiceListener;
protected int state;
private Regions awsRegion;
private AWSCredentialsProvider credentialsProvider;
private InteractionConfig interactionConfig;
private InteractionClient lexInteractionClient;
private LexServiceContinuation continuation;
private boolean shouldInitialize;
private Map<String, String> sessionAttributes;
private final ClientConfiguration clientConfiguration;
// Dialog states.
protected final static int STATE_NOT_READY = 0;
protected final static int STATE_READY = 1;
protected final static int STATE_LISTENING = 2;
protected final static int STATE_AUDIO_PLAYBACK = 3;
protected final static int STATE_AWAITING_RESPONSE = 4;
private InteractiveVoiceViewAdapter(Context context,
InteractiveVoiceView micButton) {
this.context = context;
this.micButton = micButton;
this.micButton.setOnClickListener(this);
this.shouldInitialize = true;
this.voiceListener = null;
this.state = STATE_READY;
// for setting the user agent
clientConfiguration = new ClientConfiguration();
clientConfiguration.setUserAgent(INTERACTION_VOICE_VIEW_USER_AGENT);
}
public static InteractiveVoiceViewAdapter getInstance(Context context,
InteractiveVoiceView micButton) {
return new InteractiveVoiceViewAdapter(context, micButton);
}
@Override
public void onAudioPlaybackStarted() {
state = STATE_AUDIO_PLAYBACK;
}
@Override
public void onAudioPlayBackCompleted() {
if (state != STATE_NOT_READY) {
if (this.continuation != null) {
state = STATE_LISTENING;
continuation.continueWithCurrentMode();
} else {
// Cannot continue, must start new dialog.
state = STATE_READY;
micButton.animateNone();
}
}
}
@Override
public void onAudioPlaybackError(Exception e) {
// Audio playback failed.
Log.e(TAG, "InteractiveVoiceViewAdapter: Audio playback failed", e);
state = STATE_READY;
}
@Override
public void onReadyForFulfillment(Response response) {
// The request is ready for fulfillment, the bot is ready for a new dialog.
state = STATE_READY;
continuation = null;
if (voiceListener != null) {
voiceListener.dialogReadyForFulfillment(response.getSlots(), response.getIntentName());
}
}
@Override
public void promptUserToRespond(Response response,
LexServiceContinuation continuation) {
micButton.animateNone();
if (response == null) {
Log.e(TAG, "InteractiveVoiceViewAdapter: Received null response from Amazon Lex bot");
}
if (DialogState.Fulfilled.toString().equals(response.getDialogState())) {
// The request has been fulfilled, the bot is ready for a new dialog.
state = STATE_READY;
this.continuation = null;
} else {
this.continuation = continuation;
}
if (voiceListener != null) {
voiceListener.onResponse(response);
}
}
@Override
public void onInteractionError(Response response, Exception e) {
Log.e(TAG, "InteractiveVoiceViewAdapter: Interaction error", e);
micButton.animateNone();
if (state != STATE_AUDIO_PLAYBACK) {
state = STATE_READY;
}
continuation = null;
if (voiceListener != null) {
if (response != null) {
voiceListener.onError(response.getTextResponse(), e);
} else {
voiceListener.onError("Error from Bot", e);
}
}
}
@Override
public void readyForRecording() {
// noop.
}
@Override
public void startedRecording() {
//noop.
}
@Override
public void onRecordingEnd() {
if (state == STATE_NOT_READY) {
return;
}
if (state == STATE_LISTENING) {
state = STATE_AWAITING_RESPONSE;
micButton.animateWaitSpinner();
} else {
state = STATE_READY;
}
}
@Override
public void onSoundLevelChanged(double soundLevel) {
if (state == STATE_LISTENING) {
micButton.animateSoundLevel((float) soundLevel);
}
}
@Override
public void onMicrophoneError(Exception e) {
micButton.animateNone();
if (e instanceof NoSpeechTimeOutException) {
Log.e(TAG, "InteractiveVoiceViewAdapter: Failed to detect speech", e);
state = STATE_READY;
} else if (e instanceof MaxSpeechTimeOutException) {
Log.e(TAG, "InteractiveVoiceViewAdapter: Speech time out", e);
}
}
@Override
public void onClick(View v) {
// Return if not ready.
switch (state) {
case STATE_READY:
if (shouldInitialize) {
init();
}
if (sessionAttributes == null) {
sessionAttributes = new HashMap<String, String>();
}
startListening(sessionAttributes);
break;
case STATE_LISTENING:
case STATE_AUDIO_PLAYBACK:
case STATE_AWAITING_RESPONSE:
lexInteractionClient.cancel();
state = STATE_READY;
break;
}
}
/**
* Validates app details.
* <p>
* <b>Override this method</b> to implement custom attribute verification.
* </p>
*/
protected void validateAppData() {
if (interactionConfig == null) {
throw new InvalidParameterException(
"Interaction config is not set");
} else {
if (interactionConfig.getBotName() == null || interactionConfig.getBotName().isEmpty()) {
throw new InvalidParameterException(
"Bot name is not set");
}
if (interactionConfig.getBotAlias() == null || interactionConfig.getBotAlias().isEmpty()) {
throw new InvalidParameterException(
"Bot alias is not set");
}
}
if (awsRegion == null) {
throw new InvalidParameterException(
"AWS Region is not set");
}
}
/**
* Creates the interaction client, uses default interaction config setting
* {@link com.amazonaws.mobileconnectors.lex.interactionkit.config.InteractionConfig}
* .
*/
protected void createInteractionClient() {
// Release system resources assigned to an earlier instance of the client.
if (lexInteractionClient != null) {
lexInteractionClient.cancel();
}
lexInteractionClient = new InteractionClient(
context,
credentialsProvider,
awsRegion,
interactionConfig,
clientConfiguration);
lexInteractionClient.setAudioPlaybackListener(this);
lexInteractionClient.setInteractionListener(this);
lexInteractionClient.setMicrophoneListener(this);
}
/**
* Invoke Amazon Lex client to start a new audio in audio request.
*
* @param sessionParameters The session parameters to be used for this
* request.
*/
private void startListening(Map<String, String> sessionParameters) {
state = STATE_LISTENING;
lexInteractionClient.audioInForAudioOut(sessionParameters);
}
/**
* Initializes this adapter. This will terminate any ongoing transactions with the current
* instance of the client and creates a new client.
*/
private void init() {
state = STATE_READY;
validateAppData();
createInteractionClient();
micButton.setOnClickListener(this);
shouldInitialize = false;
}
/**
* Cancel current dialog.
*/
protected void cancel() {
reset();
}
/**
* Reset client.
*/
protected void reset() {
if (lexInteractionClient != null) {
lexInteractionClient.cancel();
}
state = STATE_READY;
sessionAttributes = null;
}
/**
* Assign a listener for the voice interactions with the Amazon Lex bot.
*
* @param voiceListener
*/
protected void setVoiceListener(InteractiveVoiceView.InteractiveVoiceListener voiceListener) {
this.voiceListener = voiceListener;
}
/**
* Set credentials provider.
* @param credentialsProvider
*/
public void setCredentialProvider(AWSCredentialsProvider credentialsProvider) {
this.credentialsProvider = credentialsProvider;
shouldInitialize = true;
}
/**
* Set interaction config.
* @param interactionConfig The interaction config.
*/
public void setInteractionConfig(InteractionConfig interactionConfig) {
this.interactionConfig = interactionConfig;
shouldInitialize = true;
}
/**
* Set session attributes, these will be picked for the next dialog transaction.
* @param sessionAttributes
*/
public void setSessionAttributes(Map<String, String> sessionAttributes) {
this.sessionAttributes = sessionAttributes;
}
/**
* Set the AWS region where the Amazon Lex bot has been setup.
*
* @param awsRegion
*/
public void setAwsRegion(String awsRegion) {
this.awsRegion = Regions.fromName(awsRegion) ;
}
/**
* Get the AWS regions set for this adapter.
* @return The AWS region, {@link String}.
*/
public String getAwsRegion() {
return awsRegion == null ? null : awsRegion.getName();
}
}