diff options
Diffstat (limited to 'java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java')
-rw-r--r-- | java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java new file mode 100644 index 000000000..52c73ce90 --- /dev/null +++ b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package com.android.inputmethod.deprecated.voice; + +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.SubtypeSwitcher; + +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; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; +import java.util.Locale; + +/** + * 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 { + @SuppressWarnings("unused") + private static final String TAG = "RecognitionView"; + + private Handler mUiHandler; // Reference to UI thread + private View mView; + private Context mContext; + + private TextView mText; + private ImageView mImage; + private View mProgress; + private SoundIndicator mSoundIndicator; + private TextView mLanguage; + private Button mButton; + + private Drawable mInitializing; + private Drawable mError; + + 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; + + private final View mPopupLayout; + + private final Drawable mListeningBorder; + private final Drawable mWorkingBorder; + private final Drawable mErrorBorder; + + public RecognitionView(Context context, OnClickListener clickListener) { + mUiHandler = new Handler(); + + LayoutInflater inflater = (LayoutInflater) context.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + mView = inflater.inflate(R.layout.recognition_status, null); + + mPopupLayout= mView.findViewById(R.id.popup_layout); + + // Pre-load volume level images + Resources r = context.getResources(); + + mListeningBorder = r.getDrawable(R.drawable.vs_dialog_red); + mWorkingBorder = r.getDrawable(R.drawable.vs_dialog_blue); + mErrorBorder = r.getDrawable(R.drawable.vs_dialog_yellow); + + mInitializing = r.getDrawable(R.drawable.mic_slash); + mError = r.getDrawable(R.drawable.caution); + + mImage = (ImageView) mView.findViewById(R.id.image); + mProgress = mView.findViewById(R.id.progress); + mSoundIndicator = (SoundIndicator) mView.findViewById(R.id.sound_indicator); + + mButton = (Button) mView.findViewById(R.id.button); + mButton.setOnClickListener(clickListener); + mText = (TextView) mView.findViewById(R.id.text); + mLanguage = (TextView) mView.findViewById(R.id.language); + + mContext = context; + } + + public View getView() { + return mView; + } + + public void restoreState() { + mUiHandler.post(new Runnable() { + @Override + public void run() { + // Restart the spinner + if (mState == WORKING) { + ((ProgressBar) mProgress).setIndeterminate(false); + ((ProgressBar) mProgress).setIndeterminate(true); + } + } + }); + } + + public void showInitializing() { + mUiHandler.post(new Runnable() { + @Override + public void run() { + mState = INIT; + prepareDialog(mContext.getText(R.string.voice_initializing), mInitializing, + mContext.getText(R.string.cancel)); + } + }); + } + + public void showListening() { + Log.d(TAG, "#showListening"); + mUiHandler.post(new Runnable() { + @Override + public void run() { + mState = LISTENING; + prepareDialog(mContext.getText(R.string.voice_listening), null, + mContext.getText(R.string.cancel)); + } + }); + } + + public void updateVoiceMeter(float rmsdB) { + mSoundIndicator.setRmsdB(rmsdB); + } + + public void showError(final String message) { + mUiHandler.post(new Runnable() { + @Override + public void run() { + mState = READY; + prepareDialog(message, mError, mContext.getText(R.string.ok)); + } + }); + } + + public void showWorking( + final ByteArrayOutputStream waveBuffer, + final int speechStartPosition, + final int speechEndPosition) { + mUiHandler.post(new Runnable() { + @Override + public void run() { + mState = WORKING; + prepareDialog(mContext.getText(R.string.voice_working), null, mContext + .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); + } + }); + } + + private void prepareDialog(CharSequence text, Drawable image, + CharSequence btnTxt) { + + /* + * 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. + */ + switch (mState) { + case INIT: + mText.setVisibility(View.INVISIBLE); + + mProgress.setVisibility(View.GONE); + + mImage.setVisibility(View.VISIBLE); + mImage.setImageResource(R.drawable.mic_slash); + + mSoundIndicator.setVisibility(View.GONE); + mSoundIndicator.stop(); + + mLanguage.setVisibility(View.INVISIBLE); + + 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(); + + Locale locale = SubtypeSwitcher.getInstance().getInputLocale(); + + mLanguage.setVisibility(View.VISIBLE); + mLanguage.setText(SubtypeSwitcher.getFullDisplayName(locale, true)); + + 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(); + + mLanguage.setVisibility(View.GONE); + + 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(); + + mLanguage.setVisibility(View.GONE); + + mPopupLayout.setBackgroundDrawable(mErrorBorder); + break; + default: + Log.w(TAG, "Unknown state " + mState); + } + mPopupLayout.requestLayout(); + mButton.setText(btnTxt); + } + + /** + * @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(); + final int h = ((View) mImage.getParent()).getHeight(); + 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); + paint.setAlpha(80); + + 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; + int yMax = h / 2; + 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) { + paint.setStrokeWidth(2); + } else { + paint.setStrokeWidth(Math.max(0, (int) (deltaX -.05))); + } + c.drawPath(path, paint); + mImage.setImageBitmap(b); + } + + public void finish() { + mUiHandler.post(new Runnable() { + @Override + public void run() { + mSoundIndicator.stop(); + } + }); + } +} |