Skip to content
Snippets Groups Projects
RecognitionView.java 11.33 KiB
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * 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 {
    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();
            }
        });
    }
}