ai.api.ui.AIButton Maven / Gradle / Ivy
package ai.api.ui;
/***********************************************************************************************************************
*
* API.AI Android SDK - client-side libraries for API.AI
* =================================================
*
* Copyright (C) 2014 by Speaktoit, Inc. (https://www.speaktoit.com)
* https://www.api.ai
*
***********************************************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License 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.
*
***********************************************************************************************************************/
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import ai.api.AIConfiguration;
import ai.api.AIListener;
import ai.api.AIService;
import ai.api.R;
import ai.api.model.AIError;
import ai.api.model.AIResponse;
public class AIButton extends SoundLevelButton implements AIListener {
public interface AIButtonListener{
public void onResult(final AIResponse result);
public void onError(final AIError error);
}
private static final String TAG = AIButton.class.getName();
protected static final int[] STATE_WAITING = {R.attr.state_waiting};
protected static final int[] STATE_SPEAKING = {R.attr.state_speaking};
private float animationStage = 0;
private boolean animationSecondPhase = false;
private final WaitingAnimation animation = new WaitingAnimation();
private AIService aiService;
private AIButtonListener resultsListener;
private final ExecutorService eventsExecutor = Executors.newSingleThreadExecutor();
@Override
public void onResult(final AIResponse result) {
post(new Runnable() {
@Override
public void run() {
changeState(MicState.normal);
}
});
if (resultsListener != null) {
resultsListener.onResult(result);
}
}
@Override
public void onError(final AIError error) {
post(new Runnable() {
@Override
public void run() {
changeState(MicState.normal);
}
});
if (resultsListener != null) {
resultsListener.onError(error);
}
}
@Override
public void onAudioLevel(final float level) {
setSoundLevel(level);
}
@Override
public void onListeningStarted() {
post(new Runnable() {
@Override
public void run() {
changeState(MicState.listening);
}
});
}
@Override
public void onListeningFinished() {
post(new Runnable() {
@Override
public void run() {
changeState(MicState.busy);
}
});
}
public enum MicState {
normal,
busy, // transitive state with disabled mic
listening, // state with sound indicator
speaking;
public static MicState fromAttrs(final TypedArray viewAttrs) {
if (viewAttrs.getBoolean(R.styleable.SoundLevelButton_state_listening, false))
return listening;
if (viewAttrs.getBoolean(R.styleable.SoundLevelButton_state_waiting, false))
return busy;
if (viewAttrs.getBoolean(R.styleable.SoundLevelButton_state_speaking, false))
return speaking;
return normal;
}
}
private MicState currentState = MicState.normal;
public AIButton(final Context context) {
super(context);
init(context, null);
}
public AIButton(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public AIButton(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void init(final Context context, final AttributeSet attrs) {
if (attrs != null) {
final TypedArray viewAttrs = context.obtainStyledAttributes(attrs, R.styleable.SoundLevelButton);
try {
currentState = MicState.fromAttrs(viewAttrs);
} finally {
viewAttrs.recycle();
}
}
}
public void initialize(final AIConfiguration config) {
aiService = AIService.getService(getContext(), config);
aiService.setListener(this);
}
public void setResultsListener(final AIButtonListener resultsListener) {
this.resultsListener = resultsListener;
}
@Override
protected void onClick(final View v) {
super.onClick(v);
if (aiService != null) {
if (currentState != MicState.normal) {
eventsExecutor.submit(new Runnable() {
@Override
public void run() {
aiService.cancel();
}
});
} else {
eventsExecutor.submit(new Runnable() {
@Override
public void run() {
aiService.startListening();
}
});
}
}
}
@Override
public int[] onCreateDrawableState(final int extraSpace) {
final int[] state = super.onCreateDrawableState(extraSpace + 1);
if (currentState != null) {
switch (currentState) {
case normal:
break;
case busy:
mergeDrawableStates(state, STATE_WAITING);
break;
case listening:
mergeDrawableStates(state, STATE_LISTENING);
break;
case speaking:
mergeDrawableStates(state, STATE_SPEAKING);
break;
}
}
return state;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (!isInEditMode()) {
if (aiService != null) {
aiService.pause();
}
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!isInEditMode()) {
if (aiService != null) {
aiService.resume();
}
}
}
private void changeState(final MicState toState) {
switch (toState) {
case normal:
stopProcessingAnimation();
setDrawSoundLevel(false);
break;
case busy:
startProcessingAnimation();
setDrawSoundLevel(false);
break;
case listening:
stopProcessingAnimation();
setDrawSoundLevel(true);
break;
case speaking:
stopProcessingAnimation();
setDrawSoundLevel(false);
break;
}
currentState = toState;
refreshDrawableState();
}
private void startProcessingAnimation() {
setDrawCenter(true);
animationSecondPhase = false;
startAnimation(animation);
}
private void stopProcessingAnimation() {
setDrawCenter(false);
clearAnimation();
animationStage = 0;
animationSecondPhase = false;
postInvalidate();
}
@Override
protected String getDebugState() {
return super.getDebugState() + "\nst:" + currentState;
}
private class WaitingAnimation extends Animation {
protected WaitingAnimation() {
super();
setDuration(1500);
this.setRepeatCount(INFINITE);
this.setRepeatMode(RESTART);
this.setInterpolator(new LinearInterpolator());
}
@Override
protected void applyTransformation(final float interpolatedTime, final Transformation t) {
animationStage = interpolatedTime;
invalidate();
}
}
@Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
if (animationStage > 0 || animationSecondPhase) {
final float center = getWidth() / 2f;
final float radius = getMinRadius() * 1.25f;
final RectF size = new RectF(center - radius, center - radius, center + radius, center + radius);
final Paint paint = new Paint();
paint.setColor(getResources().getColor(R.color.icon_orange_color));
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(dpToPixels(this.getContext(), 4));
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setAntiAlias(true);
final float startingAngle;
final float sweepAngle;
if (animationStage < 0.5 && !animationSecondPhase) {
startingAngle = 0;
sweepAngle = animationStage * 360;
} else {
startingAngle = (animationStage - 0.5f) * 360;
sweepAngle = 180;
animationSecondPhase = true;
}
canvas.drawArc(size, 270f + startingAngle, sweepAngle, false, paint);
}
}
private static int dpToPixels(final Context context, final float dp) {
final Resources r = context.getResources();
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics()));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy