2010-01-16 20:21:23 +00:00
|
|
|
/*
|
2011-05-19 06:03:03 +01:00
|
|
|
* Copyright (C) 2009 The Android Open Source Project
|
2010-01-28 18:09:44 +00:00
|
|
|
*
|
2010-01-16 20:21:23 +00:00
|
|
|
* 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
|
2010-01-28 18:09:44 +00:00
|
|
|
*
|
2010-01-16 20:21:23 +00:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2010-01-28 18:09:44 +00:00
|
|
|
*
|
2010-01-16 20:21:23 +00:00
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.android.inputmethod.voice;
|
|
|
|
|
2011-01-24 10:36:15 +00:00
|
|
|
import com.android.inputmethod.latin.R;
|
|
|
|
import com.android.inputmethod.latin.SubtypeSwitcher;
|
|
|
|
|
2010-01-16 20:21:23 +00:00
|
|
|
import android.content.Context;
|
|
|
|
import android.content.res.Resources;
|
|
|
|
import android.graphics.Bitmap;
|
|
|
|
import android.graphics.Canvas;
|
|
|
|
import android.graphics.CornerPathEffect;
|
|
|
|
import android.graphics.Paint;
|
|
|
|
import android.graphics.Path;
|
|
|
|
import android.graphics.PathEffect;
|
|
|
|
import android.graphics.drawable.Drawable;
|
|
|
|
import android.os.Handler;
|
2011-01-18 15:49:17 +00:00
|
|
|
import android.util.Log;
|
2010-01-16 20:21:23 +00:00
|
|
|
import android.view.LayoutInflater;
|
|
|
|
import android.view.View;
|
|
|
|
import android.view.View.OnClickListener;
|
2011-01-18 15:49:17 +00:00
|
|
|
import android.widget.Button;
|
2010-01-16 20:21:23 +00:00
|
|
|
import android.widget.ImageView;
|
2010-03-07 15:27:05 +00:00
|
|
|
import android.widget.ProgressBar;
|
2010-01-16 20:21:23 +00:00
|
|
|
import android.widget.TextView;
|
|
|
|
|
2010-11-29 08:57:48 +00:00
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
import java.nio.ByteOrder;
|
|
|
|
import java.nio.ShortBuffer;
|
2011-01-24 10:36:15 +00:00
|
|
|
import java.util.Locale;
|
2010-01-16 20:21:23 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The user interface for the "Speak now" and "working" states.
|
|
|
|
* Displays a recognition dialog (with waveform, voice meter, etc.),
|
|
|
|
* plays beeps, shows errors, etc.
|
|
|
|
*/
|
|
|
|
public class RecognitionView {
|
2010-12-02 09:46:21 +00:00
|
|
|
@SuppressWarnings("unused")
|
2010-01-16 20:21:23 +00:00
|
|
|
private static final String TAG = "RecognitionView";
|
|
|
|
|
|
|
|
private Handler mUiHandler; // Reference to UI thread
|
|
|
|
private View mView;
|
|
|
|
private Context mContext;
|
|
|
|
|
|
|
|
private TextView mText;
|
2011-01-18 15:49:17 +00:00
|
|
|
private ImageView mImage;
|
2010-01-16 20:21:23 +00:00
|
|
|
private View mProgress;
|
2011-01-18 15:49:17 +00:00
|
|
|
private SoundIndicator mSoundIndicator;
|
2011-01-24 10:36:15 +00:00
|
|
|
private TextView mLanguage;
|
2011-01-18 15:49:17 +00:00
|
|
|
private Button mButton;
|
2010-01-16 20:21:23 +00:00
|
|
|
|
|
|
|
private Drawable mInitializing;
|
|
|
|
private Drawable mError;
|
|
|
|
|
2011-01-18 15:49:17 +00:00
|
|
|
private static final int INIT = 0;
|
|
|
|
private static final int LISTENING = 1;
|
|
|
|
private static final int WORKING = 2;
|
|
|
|
private static final int READY = 3;
|
|
|
|
|
|
|
|
private int mState = INIT;
|
2010-01-16 20:21:23 +00:00
|
|
|
|
2011-01-18 15:49:17 +00:00
|
|
|
private final View mPopupLayout;
|
2010-01-16 20:21:23 +00:00
|
|
|
|
2011-01-18 15:49:17 +00:00
|
|
|
private final Drawable mListeningBorder;
|
|
|
|
private final Drawable mWorkingBorder;
|
|
|
|
private final Drawable mErrorBorder;
|
2010-01-16 20:21:23 +00:00
|
|
|
|
|
|
|
public RecognitionView(Context context, OnClickListener clickListener) {
|
|
|
|
mUiHandler = new Handler();
|
|
|
|
|
2011-01-18 15:49:17 +00:00
|
|
|
LayoutInflater inflater = (LayoutInflater) context.getSystemService(
|
|
|
|
Context.LAYOUT_INFLATER_SERVICE);
|
|
|
|
|
|
|
|
mView = inflater.inflate(R.layout.recognition_status, null);
|
|
|
|
|
|
|
|
mPopupLayout= mView.findViewById(R.id.popup_layout);
|
2010-01-16 20:21:23 +00:00
|
|
|
|
|
|
|
// Pre-load volume level images
|
|
|
|
Resources r = context.getResources();
|
|
|
|
|
2011-01-18 15:49:17 +00:00
|
|
|
mListeningBorder = r.getDrawable(R.drawable.vs_dialog_red);
|
|
|
|
mWorkingBorder = r.getDrawable(R.drawable.vs_dialog_blue);
|
|
|
|
mErrorBorder = r.getDrawable(R.drawable.vs_dialog_yellow);
|
2010-01-16 20:21:23 +00:00
|
|
|
|
|
|
|
mInitializing = r.getDrawable(R.drawable.mic_slash);
|
|
|
|
mError = r.getDrawable(R.drawable.caution);
|
2010-01-28 18:09:44 +00:00
|
|
|
|
2010-01-16 20:21:23 +00:00
|
|
|
mImage = (ImageView) mView.findViewById(R.id.image);
|
2011-01-18 15:49:17 +00:00
|
|
|
mProgress = mView.findViewById(R.id.progress);
|
|
|
|
mSoundIndicator = (SoundIndicator) mView.findViewById(R.id.sound_indicator);
|
|
|
|
|
|
|
|
mButton = (Button) mView.findViewById(R.id.button);
|
2010-01-16 20:21:23 +00:00
|
|
|
mButton.setOnClickListener(clickListener);
|
|
|
|
mText = (TextView) mView.findViewById(R.id.text);
|
2011-01-24 10:36:15 +00:00
|
|
|
mLanguage = (TextView) mView.findViewById(R.id.language);
|
2010-01-16 20:21:23 +00:00
|
|
|
|
|
|
|
mContext = context;
|
|
|
|
}
|
|
|
|
|
|
|
|
public View getView() {
|
|
|
|
return mView;
|
|
|
|
}
|
|
|
|
|
2010-03-07 15:27:05 +00:00
|
|
|
public void restoreState() {
|
|
|
|
mUiHandler.post(new Runnable() {
|
2010-12-02 09:46:21 +00:00
|
|
|
@Override
|
2010-03-07 15:27:05 +00:00
|
|
|
public void run() {
|
|
|
|
// Restart the spinner
|
2011-01-18 15:49:17 +00:00
|
|
|
if (mState == WORKING) {
|
|
|
|
((ProgressBar) mProgress).setIndeterminate(false);
|
|
|
|
((ProgressBar) mProgress).setIndeterminate(true);
|
2010-03-07 15:27:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2010-01-16 20:21:23 +00:00
|
|
|
public void showInitializing() {
|
|
|
|
mUiHandler.post(new Runnable() {
|
2010-12-02 09:46:21 +00:00
|
|
|
@Override
|
2010-01-16 20:21:23 +00:00
|
|
|
public void run() {
|
2011-01-18 15:49:17 +00:00
|
|
|
mState = INIT;
|
|
|
|
prepareDialog(mContext.getText(R.string.voice_initializing), mInitializing,
|
|
|
|
mContext.getText(R.string.cancel));
|
2010-01-16 20:21:23 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public void showListening() {
|
2011-01-18 15:49:17 +00:00
|
|
|
Log.d(TAG, "#showListening");
|
2010-01-16 20:21:23 +00:00
|
|
|
mUiHandler.post(new Runnable() {
|
2010-12-02 09:46:21 +00:00
|
|
|
@Override
|
2010-01-16 20:21:23 +00:00
|
|
|
public void run() {
|
2011-01-18 15:49:17 +00:00
|
|
|
mState = LISTENING;
|
|
|
|
prepareDialog(mContext.getText(R.string.voice_listening), null,
|
2010-02-11 10:01:25 +00:00
|
|
|
mContext.getText(R.string.cancel));
|
2010-01-16 20:21:23 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2011-01-18 15:49:17 +00:00
|
|
|
public void updateVoiceMeter(float rmsdB) {
|
|
|
|
mSoundIndicator.setRmsdB(rmsdB);
|
2010-01-16 20:21:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void showError(final String message) {
|
|
|
|
mUiHandler.post(new Runnable() {
|
2010-12-02 09:46:21 +00:00
|
|
|
@Override
|
2010-01-16 20:21:23 +00:00
|
|
|
public void run() {
|
2011-01-18 15:49:17 +00:00
|
|
|
mState = READY;
|
|
|
|
prepareDialog(message, mError, mContext.getText(R.string.ok));
|
2010-01-16 20:21:23 +00:00
|
|
|
}
|
2011-01-18 15:49:17 +00:00
|
|
|
});
|
2010-01-16 20:21:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void showWorking(
|
|
|
|
final ByteArrayOutputStream waveBuffer,
|
|
|
|
final int speechStartPosition,
|
|
|
|
final int speechEndPosition) {
|
|
|
|
mUiHandler.post(new Runnable() {
|
2010-12-02 09:46:21 +00:00
|
|
|
@Override
|
2010-01-16 20:21:23 +00:00
|
|
|
public void run() {
|
2011-01-18 15:49:17 +00:00
|
|
|
mState = WORKING;
|
|
|
|
prepareDialog(mContext.getText(R.string.voice_working), null, mContext
|
2010-02-11 10:01:25 +00:00
|
|
|
.getText(R.string.cancel));
|
|
|
|
final ShortBuffer buf = ByteBuffer.wrap(waveBuffer.toByteArray()).order(
|
|
|
|
ByteOrder.nativeOrder()).asShortBuffer();
|
|
|
|
buf.position(0);
|
|
|
|
waveBuffer.reset();
|
|
|
|
showWave(buf, speechStartPosition / 2, speechEndPosition / 2);
|
2010-01-16 20:21:23 +00:00
|
|
|
}
|
2011-01-18 15:49:17 +00:00
|
|
|
});
|
2010-01-16 20:21:23 +00:00
|
|
|
}
|
2010-02-11 10:01:25 +00:00
|
|
|
|
2011-01-18 15:49:17 +00:00
|
|
|
private void prepareDialog(CharSequence text, Drawable image,
|
2010-02-11 10:01:25 +00:00
|
|
|
CharSequence btnTxt) {
|
2011-01-24 10:36:15 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The mic of INIT and of LISTENING has to be displayed in the same position. To accomplish
|
|
|
|
* that, some text visibility are not set as GONE but as INVISIBLE.
|
|
|
|
*/
|
2011-01-18 15:49:17 +00:00
|
|
|
switch (mState) {
|
|
|
|
case INIT:
|
2011-01-24 10:36:15 +00:00
|
|
|
mText.setVisibility(View.INVISIBLE);
|
2011-01-18 15:49:17 +00:00
|
|
|
|
|
|
|
mProgress.setVisibility(View.GONE);
|
|
|
|
|
|
|
|
mImage.setVisibility(View.VISIBLE);
|
|
|
|
mImage.setImageResource(R.drawable.mic_slash);
|
|
|
|
|
|
|
|
mSoundIndicator.setVisibility(View.GONE);
|
|
|
|
mSoundIndicator.stop();
|
|
|
|
|
2011-01-24 10:36:15 +00:00
|
|
|
mLanguage.setVisibility(View.INVISIBLE);
|
|
|
|
|
2011-01-18 15:49:17 +00:00
|
|
|
mPopupLayout.setBackgroundDrawable(mListeningBorder);
|
|
|
|
break;
|
|
|
|
case LISTENING:
|
|
|
|
mText.setVisibility(View.VISIBLE);
|
|
|
|
mText.setText(text);
|
|
|
|
|
|
|
|
mProgress.setVisibility(View.GONE);
|
|
|
|
|
|
|
|
mImage.setVisibility(View.GONE);
|
|
|
|
|
|
|
|
mSoundIndicator.setVisibility(View.VISIBLE);
|
|
|
|
mSoundIndicator.start();
|
|
|
|
|
2011-01-24 10:36:15 +00:00
|
|
|
Locale locale = SubtypeSwitcher.getInstance().getInputLocale();
|
|
|
|
|
|
|
|
mLanguage.setVisibility(View.VISIBLE);
|
|
|
|
mLanguage.setText(SubtypeSwitcher.getFullDisplayName(locale, true));
|
|
|
|
|
2011-01-18 15:49:17 +00:00
|
|
|
mPopupLayout.setBackgroundDrawable(mListeningBorder);
|
|
|
|
break;
|
|
|
|
case WORKING:
|
|
|
|
|
|
|
|
mText.setVisibility(View.VISIBLE);
|
|
|
|
mText.setText(text);
|
|
|
|
|
|
|
|
mProgress.setVisibility(View.VISIBLE);
|
|
|
|
|
|
|
|
mImage.setVisibility(View.VISIBLE);
|
|
|
|
|
|
|
|
mSoundIndicator.setVisibility(View.GONE);
|
|
|
|
mSoundIndicator.stop();
|
|
|
|
|
2011-01-24 10:36:15 +00:00
|
|
|
mLanguage.setVisibility(View.GONE);
|
|
|
|
|
2011-01-18 15:49:17 +00:00
|
|
|
mPopupLayout.setBackgroundDrawable(mWorkingBorder);
|
|
|
|
break;
|
|
|
|
case READY:
|
|
|
|
mText.setVisibility(View.VISIBLE);
|
|
|
|
mText.setText(text);
|
|
|
|
|
|
|
|
mProgress.setVisibility(View.GONE);
|
|
|
|
|
|
|
|
mImage.setVisibility(View.VISIBLE);
|
|
|
|
mImage.setImageResource(R.drawable.caution);
|
|
|
|
|
|
|
|
mSoundIndicator.setVisibility(View.GONE);
|
|
|
|
mSoundIndicator.stop();
|
|
|
|
|
2011-01-24 10:36:15 +00:00
|
|
|
mLanguage.setVisibility(View.GONE);
|
|
|
|
|
2011-01-18 15:49:17 +00:00
|
|
|
mPopupLayout.setBackgroundDrawable(mErrorBorder);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
Log.w(TAG, "Unknown state " + mState);
|
2010-02-11 10:01:25 +00:00
|
|
|
}
|
2011-01-18 15:49:17 +00:00
|
|
|
mPopupLayout.requestLayout();
|
|
|
|
mButton.setText(btnTxt);
|
2010-02-11 10:01:25 +00:00
|
|
|
}
|
2010-01-28 18:09:44 +00:00
|
|
|
|
2010-01-16 20:21:23 +00:00
|
|
|
/**
|
|
|
|
* @return an average abs of the specified buffer.
|
|
|
|
*/
|
|
|
|
private static int getAverageAbs(ShortBuffer buffer, int start, int i, int npw) {
|
|
|
|
int from = start + i * npw;
|
|
|
|
int end = from + npw;
|
|
|
|
int total = 0;
|
|
|
|
for (int x = from; x < end; x++) {
|
|
|
|
total += Math.abs(buffer.get(x));
|
|
|
|
}
|
|
|
|
return total / npw;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shows waveform of input audio.
|
|
|
|
*
|
|
|
|
* Copied from version in VoiceSearch's RecognitionActivity.
|
|
|
|
*
|
|
|
|
* TODO: adjust stroke width based on the size of data.
|
|
|
|
* TODO: use dip rather than pixels.
|
|
|
|
*/
|
|
|
|
private void showWave(ShortBuffer waveBuffer, int startPosition, int endPosition) {
|
|
|
|
final int w = ((View) mImage.getParent()).getWidth();
|
2011-01-18 15:49:17 +00:00
|
|
|
final int h = ((View) mImage.getParent()).getHeight();
|
2010-01-16 20:21:23 +00:00
|
|
|
if (w <= 0 || h <= 0) {
|
|
|
|
// view is not visible this time. Skip drawing.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
|
|
|
|
final Canvas c = new Canvas(b);
|
|
|
|
final Paint paint = new Paint();
|
|
|
|
paint.setColor(0xFFFFFFFF); // 0xAARRGGBB
|
|
|
|
paint.setAntiAlias(true);
|
|
|
|
paint.setStyle(Paint.Style.STROKE);
|
2011-01-18 15:49:17 +00:00
|
|
|
paint.setAlpha(80);
|
2010-01-16 20:21:23 +00:00
|
|
|
|
|
|
|
final PathEffect effect = new CornerPathEffect(3);
|
|
|
|
paint.setPathEffect(effect);
|
|
|
|
|
|
|
|
final int numSamples = waveBuffer.remaining();
|
|
|
|
int endIndex;
|
|
|
|
if (endPosition == 0) {
|
|
|
|
endIndex = numSamples;
|
|
|
|
} else {
|
|
|
|
endIndex = Math.min(endPosition, numSamples);
|
|
|
|
}
|
|
|
|
|
|
|
|
int startIndex = startPosition - 2000; // include 250ms before speech
|
|
|
|
if (startIndex < 0) {
|
|
|
|
startIndex = 0;
|
|
|
|
}
|
|
|
|
final int numSamplePerWave = 200; // 8KHz 25ms = 200 samples
|
|
|
|
final float scale = 10.0f / 65536.0f;
|
|
|
|
|
|
|
|
final int count = (endIndex - startIndex) / numSamplePerWave;
|
|
|
|
final float deltaX = 1.0f * w / count;
|
2011-01-18 15:49:17 +00:00
|
|
|
int yMax = h / 2;
|
2010-01-16 20:21:23 +00:00
|
|
|
Path path = new Path();
|
|
|
|
c.translate(0, yMax);
|
|
|
|
float x = 0;
|
|
|
|
path.moveTo(x, 0);
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
final int avabs = getAverageAbs(waveBuffer, startIndex, i , numSamplePerWave);
|
|
|
|
int sign = ( (i & 01) == 0) ? -1 : 1;
|
|
|
|
final float y = Math.min(yMax, avabs * h * scale) * sign;
|
|
|
|
path.lineTo(x, y);
|
|
|
|
x += deltaX;
|
|
|
|
path.lineTo(x, y);
|
|
|
|
}
|
|
|
|
if (deltaX > 4) {
|
2011-01-18 15:49:17 +00:00
|
|
|
paint.setStrokeWidth(2);
|
2010-01-16 20:21:23 +00:00
|
|
|
} else {
|
2011-01-18 15:49:17 +00:00
|
|
|
paint.setStrokeWidth(Math.max(0, (int) (deltaX -.05)));
|
2010-01-16 20:21:23 +00:00
|
|
|
}
|
|
|
|
c.drawPath(path, paint);
|
|
|
|
mImage.setImageBitmap(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void finish() {
|
|
|
|
mUiHandler.post(new Runnable() {
|
2010-12-02 09:46:21 +00:00
|
|
|
@Override
|
2010-01-16 20:21:23 +00:00
|
|
|
public void run() {
|
2011-01-18 15:49:17 +00:00
|
|
|
mSoundIndicator.stop();
|
2010-01-16 20:21:23 +00:00
|
|
|
}
|
2011-01-18 15:49:17 +00:00
|
|
|
});
|
2010-01-16 20:21:23 +00:00
|
|
|
}
|
|
|
|
}
|