Home | History | Annotate | Download | only in voice
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.android.inputmethod.deprecated.voice;
     18 
     19 import com.android.inputmethod.latin.R;
     20 import com.android.inputmethod.latin.SubtypeSwitcher;
     21 import com.android.inputmethod.latin.Utils;
     22 
     23 import android.content.Context;
     24 import android.content.res.Resources;
     25 import android.graphics.Bitmap;
     26 import android.graphics.Canvas;
     27 import android.graphics.CornerPathEffect;
     28 import android.graphics.Paint;
     29 import android.graphics.Path;
     30 import android.graphics.PathEffect;
     31 import android.graphics.drawable.Drawable;
     32 import android.os.Handler;
     33 import android.util.Log;
     34 import android.view.LayoutInflater;
     35 import android.view.View;
     36 import android.view.View.OnClickListener;
     37 import android.widget.Button;
     38 import android.widget.ImageView;
     39 import android.widget.ProgressBar;
     40 import android.widget.TextView;
     41 
     42 import java.io.ByteArrayOutputStream;
     43 import java.nio.ByteBuffer;
     44 import java.nio.ByteOrder;
     45 import java.nio.ShortBuffer;
     46 import java.util.Locale;
     47 
     48 /**
     49  * The user interface for the "Speak now" and "working" states.
     50  * Displays a recognition dialog (with waveform, voice meter, etc.),
     51  * plays beeps, shows errors, etc.
     52  */
     53 public class RecognitionView {
     54     private static final String TAG = "RecognitionView";
     55 
     56     private Handler mUiHandler;  // Reference to UI thread
     57     private View mView;
     58     private Context mContext;
     59 
     60     private TextView mText;
     61     private ImageView mImage;
     62     private View mProgress;
     63     private SoundIndicator mSoundIndicator;
     64     private TextView mLanguage;
     65     private Button mButton;
     66 
     67     private Drawable mInitializing;
     68     private Drawable mError;
     69 
     70     private static final int INIT = 0;
     71     private static final int LISTENING = 1;
     72     private static final int WORKING = 2;
     73     private static final int READY = 3;
     74 
     75     private int mState = INIT;
     76 
     77     private final View mPopupLayout;
     78 
     79     private final Drawable mListeningBorder;
     80     private final Drawable mWorkingBorder;
     81     private final Drawable mErrorBorder;
     82 
     83     public RecognitionView(Context context, OnClickListener clickListener) {
     84         mUiHandler = new Handler();
     85 
     86         LayoutInflater inflater = (LayoutInflater) context.getSystemService(
     87                 Context.LAYOUT_INFLATER_SERVICE);
     88 
     89         mView = inflater.inflate(R.layout.recognition_status, null);
     90 
     91         mPopupLayout= mView.findViewById(R.id.popup_layout);
     92 
     93         // Pre-load volume level images
     94         Resources r = context.getResources();
     95 
     96         mListeningBorder = r.getDrawable(R.drawable.vs_dialog_red);
     97         mWorkingBorder = r.getDrawable(R.drawable.vs_dialog_blue);
     98         mErrorBorder = r.getDrawable(R.drawable.vs_dialog_yellow);
     99 
    100         mInitializing = r.getDrawable(R.drawable.mic_slash);
    101         mError = r.getDrawable(R.drawable.caution);
    102 
    103         mImage = (ImageView) mView.findViewById(R.id.image);
    104         mProgress = mView.findViewById(R.id.progress);
    105         mSoundIndicator = (SoundIndicator) mView.findViewById(R.id.sound_indicator);
    106 
    107         mButton = (Button) mView.findViewById(R.id.button);
    108         mButton.setOnClickListener(clickListener);
    109         mText = (TextView) mView.findViewById(R.id.text);
    110         mLanguage = (TextView) mView.findViewById(R.id.language);
    111 
    112         mContext = context;
    113     }
    114 
    115     public View getView() {
    116         return mView;
    117     }
    118 
    119     public void restoreState() {
    120         mUiHandler.post(new Runnable() {
    121             @Override
    122             public void run() {
    123                 // Restart the spinner
    124                 if (mState == WORKING) {
    125                     ((ProgressBar) mProgress).setIndeterminate(false);
    126                     ((ProgressBar) mProgress).setIndeterminate(true);
    127                 }
    128             }
    129         });
    130     }
    131 
    132     public void showInitializing() {
    133         mUiHandler.post(new Runnable() {
    134             @Override
    135             public void run() {
    136                 mState = INIT;
    137                 prepareDialog(mContext.getText(R.string.voice_initializing), mInitializing,
    138                         mContext.getText(R.string.cancel));
    139             }
    140           });
    141     }
    142 
    143     public void showListening() {
    144         Log.d(TAG, "#showListening");
    145         mUiHandler.post(new Runnable() {
    146             @Override
    147             public void run() {
    148                 mState = LISTENING;
    149                 prepareDialog(mContext.getText(R.string.voice_listening), null,
    150                         mContext.getText(R.string.cancel));
    151             }
    152           });
    153     }
    154 
    155     public void updateVoiceMeter(float rmsdB) {
    156         mSoundIndicator.setRmsdB(rmsdB);
    157     }
    158 
    159     public void showError(final String message) {
    160         mUiHandler.post(new Runnable() {
    161             @Override
    162             public void run() {
    163                 mState = READY;
    164                 prepareDialog(message, mError, mContext.getText(R.string.ok));
    165             }
    166         });
    167     }
    168 
    169     public void showWorking(
    170         final ByteArrayOutputStream waveBuffer,
    171         final int speechStartPosition,
    172         final int speechEndPosition) {
    173         mUiHandler.post(new Runnable() {
    174             @Override
    175             public void run() {
    176                 mState = WORKING;
    177                 prepareDialog(mContext.getText(R.string.voice_working), null, mContext
    178                         .getText(R.string.cancel));
    179                 final ShortBuffer buf = ByteBuffer.wrap(waveBuffer.toByteArray()).order(
    180                         ByteOrder.nativeOrder()).asShortBuffer();
    181                 buf.position(0);
    182                 waveBuffer.reset();
    183                 showWave(buf, speechStartPosition / 2, speechEndPosition / 2);
    184             }
    185         });
    186     }
    187 
    188     private void prepareDialog(CharSequence text, Drawable image,
    189             CharSequence btnTxt) {
    190 
    191         /*
    192          * The mic of INIT and of LISTENING has to be displayed in the same position. To accomplish
    193          * that, some text visibility are not set as GONE but as INVISIBLE.
    194          */
    195         switch (mState) {
    196             case INIT:
    197                 mText.setVisibility(View.INVISIBLE);
    198 
    199                 mProgress.setVisibility(View.GONE);
    200 
    201                 mImage.setVisibility(View.VISIBLE);
    202                 mImage.setImageResource(R.drawable.mic_slash);
    203 
    204                 mSoundIndicator.setVisibility(View.GONE);
    205                 mSoundIndicator.stop();
    206 
    207                 mLanguage.setVisibility(View.INVISIBLE);
    208 
    209                 mPopupLayout.setBackgroundDrawable(mListeningBorder);
    210                 break;
    211             case LISTENING:
    212                 mText.setVisibility(View.VISIBLE);
    213                 mText.setText(text);
    214 
    215                 mProgress.setVisibility(View.GONE);
    216 
    217                 mImage.setVisibility(View.GONE);
    218 
    219                 mSoundIndicator.setVisibility(View.VISIBLE);
    220                 mSoundIndicator.start();
    221 
    222                 Locale locale = SubtypeSwitcher.getInstance().getInputLocale();
    223 
    224                 mLanguage.setVisibility(View.VISIBLE);
    225                 mLanguage.setText(Utils.getFullDisplayName(locale, true));
    226 
    227                 mPopupLayout.setBackgroundDrawable(mListeningBorder);
    228                 break;
    229             case WORKING:
    230 
    231                 mText.setVisibility(View.VISIBLE);
    232                 mText.setText(text);
    233 
    234                 mProgress.setVisibility(View.VISIBLE);
    235 
    236                 mImage.setVisibility(View.VISIBLE);
    237 
    238                 mSoundIndicator.setVisibility(View.GONE);
    239                 mSoundIndicator.stop();
    240 
    241                 mLanguage.setVisibility(View.GONE);
    242 
    243                 mPopupLayout.setBackgroundDrawable(mWorkingBorder);
    244                 break;
    245             case READY:
    246                 mText.setVisibility(View.VISIBLE);
    247                 mText.setText(text);
    248 
    249                 mProgress.setVisibility(View.GONE);
    250 
    251                 mImage.setVisibility(View.VISIBLE);
    252                 mImage.setImageResource(R.drawable.caution);
    253 
    254                 mSoundIndicator.setVisibility(View.GONE);
    255                 mSoundIndicator.stop();
    256 
    257                 mLanguage.setVisibility(View.GONE);
    258 
    259                 mPopupLayout.setBackgroundDrawable(mErrorBorder);
    260                 break;
    261              default:
    262                  Log.w(TAG, "Unknown state " + mState);
    263         }
    264         mPopupLayout.requestLayout();
    265         mButton.setText(btnTxt);
    266     }
    267 
    268     /**
    269      * @return an average abs of the specified buffer.
    270      */
    271     private static int getAverageAbs(ShortBuffer buffer, int start, int i, int npw) {
    272         int from = start + i * npw;
    273         int end = from + npw;
    274         int total = 0;
    275         for (int x = from; x < end; x++) {
    276             total += Math.abs(buffer.get(x));
    277         }
    278         return total / npw;
    279     }
    280 
    281 
    282     /**
    283      * Shows waveform of input audio.
    284      *
    285      * Copied from version in VoiceSearch's RecognitionActivity.
    286      *
    287      * TODO: adjust stroke width based on the size of data.
    288      * TODO: use dip rather than pixels.
    289      */
    290     private void showWave(ShortBuffer waveBuffer, int startPosition, int endPosition) {
    291         final int w = ((View) mImage.getParent()).getWidth();
    292         final int h = ((View) mImage.getParent()).getHeight();
    293         if (w <= 0 || h <= 0) {
    294             // view is not visible this time. Skip drawing.
    295             return;
    296         }
    297         final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    298         final Canvas c = new Canvas(b);
    299         final Paint paint = new Paint();
    300         paint.setColor(0xFFFFFFFF); // 0xAARRGGBB
    301         paint.setAntiAlias(true);
    302         paint.setStyle(Paint.Style.STROKE);
    303         paint.setAlpha(80);
    304 
    305         final PathEffect effect = new CornerPathEffect(3);
    306         paint.setPathEffect(effect);
    307 
    308         final int numSamples = waveBuffer.remaining();
    309         int endIndex;
    310         if (endPosition == 0) {
    311             endIndex = numSamples;
    312         } else {
    313             endIndex = Math.min(endPosition, numSamples);
    314         }
    315 
    316         int startIndex = startPosition - 2000; // include 250ms before speech
    317         if (startIndex < 0) {
    318             startIndex = 0;
    319         }
    320         final int numSamplePerWave = 200;  // 8KHz 25ms = 200 samples
    321         final float scale = 10.0f / 65536.0f;
    322 
    323         final int count = (endIndex - startIndex) / numSamplePerWave;
    324         final float deltaX = 1.0f * w / count;
    325         int yMax = h / 2;
    326         Path path = new Path();
    327         c.translate(0, yMax);
    328         float x = 0;
    329         path.moveTo(x, 0);
    330         for (int i = 0; i < count; i++) {
    331             final int avabs = getAverageAbs(waveBuffer, startIndex, i , numSamplePerWave);
    332             int sign = ( (i & 01) == 0) ? -1 : 1;
    333             final float y = Math.min(yMax, avabs * h * scale) * sign;
    334             path.lineTo(x, y);
    335             x += deltaX;
    336             path.lineTo(x, y);
    337         }
    338         if (deltaX > 4) {
    339             paint.setStrokeWidth(2);
    340         } else {
    341             paint.setStrokeWidth(Math.max(0, (int) (deltaX -.05)));
    342         }
    343         c.drawPath(path, paint);
    344         mImage.setImageBitmap(b);
    345     }
    346 
    347     public void finish() {
    348         mUiHandler.post(new Runnable() {
    349             @Override
    350             public void run() {
    351                 mSoundIndicator.stop();
    352             }
    353         });
    354     }
    355 }
    356