PageRenderTime 42ms CodeModel.GetById 9ms app.highlight 28ms RepoModel.GetById 1ms app.codeStats 1ms

/ime/latinime/src/com/googlecode/eyesfree/inputmethod/voice/RecognitionView.java

http://eyes-free.googlecode.com/
Java | 321 lines | 243 code | 41 blank | 37 comment | 19 complexity | 91aead04312d70b48a8900b3db4fd3e6 MD5 | raw file
  1/*
  2 * Copyright (C) 2009 Google Inc.
  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
 17package com.googlecode.eyesfree.inputmethod.voice;
 18
 19import android.content.ContentResolver;
 20import android.content.Context;
 21import android.content.res.Resources;
 22import android.graphics.Bitmap;
 23import android.graphics.Canvas;
 24import android.graphics.CornerPathEffect;
 25import android.graphics.Paint;
 26import android.graphics.Path;
 27import android.graphics.PathEffect;
 28import android.graphics.drawable.Drawable;
 29import android.os.Handler;
 30import android.util.TypedValue;
 31import android.view.LayoutInflater;
 32import android.view.View;
 33import android.view.View.OnClickListener;
 34import android.view.ViewGroup.MarginLayoutParams;
 35import android.widget.ImageView;
 36import android.widget.ProgressBar;
 37import android.widget.TextView;
 38
 39import com.googlecode.eyesfree.inputmethod.latin.R;
 40
 41import java.io.ByteArrayOutputStream;
 42import java.nio.ByteBuffer;
 43import java.nio.ByteOrder;
 44import java.nio.ShortBuffer;
 45import java.util.ArrayList;
 46import java.util.List;
 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 */
 53public class RecognitionView {
 54    private Handler mUiHandler;  // Reference to UI thread
 55    private View mView;
 56    private Context mContext;
 57
 58    private ImageView mImage;
 59    private TextView mText;
 60    private View mButton;
 61    private TextView mButtonText;
 62    private View mProgress;
 63
 64    private Drawable mInitializing;
 65    private Drawable mError;
 66    private List<Drawable> mSpeakNow;
 67
 68    private float mVolume = 0.0f;
 69    private int mLevel = 0;
 70
 71    private enum State {LISTENING, WORKING, READY}
 72    private State mState = State.READY;
 73
 74    private float mMinMicrophoneLevel;
 75    private float mMaxMicrophoneLevel;
 76
 77    /** Updates the microphone icon to show user their volume.*/
 78    private Runnable mUpdateVolumeRunnable = new Runnable() {
 79        public void run() {
 80            if (mState != State.LISTENING) {
 81                return;
 82            }
 83
 84            final float min = mMinMicrophoneLevel;
 85            final float max = mMaxMicrophoneLevel;
 86            final int maxLevel = mSpeakNow.size() - 1;
 87
 88            int index = (int) ((mVolume - min) / (max - min) * maxLevel);
 89            final int level = Math.min(Math.max(0, index), maxLevel);
 90
 91            if (level != mLevel) {
 92                mImage.setImageDrawable(mSpeakNow.get(level));
 93                mLevel = level;
 94            }
 95            mUiHandler.postDelayed(mUpdateVolumeRunnable, 50);
 96        }
 97      };
 98
 99    public RecognitionView(Context context, OnClickListener clickListener) {
100        mUiHandler = new Handler();
101
102        LayoutInflater inflater = (LayoutInflater) context.getSystemService(
103            Context.LAYOUT_INFLATER_SERVICE);
104        mView = inflater.inflate(R.layout.recognition_status, null);
105        ContentResolver cr = context.getContentResolver();
106        mMinMicrophoneLevel = SettingsUtil.getSettingsFloat(
107                cr, SettingsUtil.LATIN_IME_MIN_MICROPHONE_LEVEL, 15.f);
108        mMaxMicrophoneLevel = SettingsUtil.getSettingsFloat(
109                cr, SettingsUtil.LATIN_IME_MAX_MICROPHONE_LEVEL, 30.f);
110
111        // Pre-load volume level images
112        Resources r = context.getResources();
113
114        mSpeakNow = new ArrayList<Drawable>();
115        mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level0));
116        mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level1));
117        mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level2));
118        mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level3));
119        mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level4));
120        mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level5));
121        mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level6));
122
123        mInitializing = r.getDrawable(R.drawable.mic_slash);
124        mError = r.getDrawable(R.drawable.caution);
125
126        mImage = (ImageView) mView.findViewById(R.id.image);
127        mButton = mView.findViewById(R.id.button);
128        mButton.setOnClickListener(clickListener);
129        mText = (TextView) mView.findViewById(R.id.text);
130        mButtonText = (TextView) mView.findViewById(R.id.button_text);
131        mProgress = mView.findViewById(R.id.progress);
132
133        mContext = context;
134    }
135
136    public View getView() {
137        return mView;
138    }
139
140    public void restoreState() {
141        mUiHandler.post(new Runnable() {
142            public void run() {
143                // Restart the spinner
144                if (mState == State.WORKING) {
145                    ((ProgressBar)mProgress).setIndeterminate(false);
146                    ((ProgressBar)mProgress).setIndeterminate(true);
147                }
148            }
149        });
150    }
151
152    public void showInitializing() {
153        mUiHandler.post(new Runnable() {
154            public void run() {
155                prepareDialog(false, mContext.getText(R.string.voice_initializing), mInitializing,
156                        mContext.getText(R.string.cancel)); 
157            }
158          });
159    }
160
161    public void showListening() {
162        mUiHandler.post(new Runnable() {
163            public void run() {
164                mState = State.LISTENING;
165                prepareDialog(false, mContext.getText(R.string.voice_listening), mSpeakNow.get(0),
166                        mContext.getText(R.string.cancel));
167            }
168          });
169        mUiHandler.postDelayed(mUpdateVolumeRunnable, 50);
170    }
171
172    public void updateVoiceMeter(final float rmsdB) {
173        mVolume = rmsdB;
174    }
175
176    public void showError(final String message) {
177        mUiHandler.post(new Runnable() {
178            public void run() {
179                mState = State.READY;
180                prepareDialog(false, message, mError, mContext.getText(R.string.ok));
181            }
182          });
183    }
184
185    public void showWorking(
186        final ByteArrayOutputStream waveBuffer,
187        final int speechStartPosition,
188        final int speechEndPosition) {
189
190        mUiHandler.post(new Runnable() {
191            public void run() {
192                mState = State.WORKING;
193                prepareDialog(true, mContext.getText(R.string.voice_working), null, mContext
194                        .getText(R.string.cancel));
195                final ShortBuffer buf = ByteBuffer.wrap(waveBuffer.toByteArray()).order(
196                        ByteOrder.nativeOrder()).asShortBuffer();
197                buf.position(0);
198                waveBuffer.reset();
199                showWave(buf, speechStartPosition / 2, speechEndPosition / 2);
200            }
201          });
202    }
203    
204    private void prepareDialog(boolean spinVisible, CharSequence text, Drawable image,
205            CharSequence btnTxt) {
206        if (spinVisible) {
207            mProgress.setVisibility(View.VISIBLE);
208            mImage.setVisibility(View.GONE);
209        } else {
210            mProgress.setVisibility(View.GONE);
211            mImage.setImageDrawable(image);
212            mImage.setVisibility(View.VISIBLE);
213        }
214        mText.setText(text);
215        mButtonText.setText(btnTxt);
216    }
217
218    /**
219     * @return an average abs of the specified buffer.
220     */
221    private static int getAverageAbs(ShortBuffer buffer, int start, int i, int npw) {
222        int from = start + i * npw;
223        int end = from + npw;
224        int total = 0;
225        for (int x = from; x < end; x++) {
226            total += Math.abs(buffer.get(x));
227        }
228        return total / npw;
229    }
230
231
232    /**
233     * Shows waveform of input audio.
234     *
235     * Copied from version in VoiceSearch's RecognitionActivity.
236     *
237     * TODO: adjust stroke width based on the size of data.
238     * TODO: use dip rather than pixels.
239     */
240    private void showWave(ShortBuffer waveBuffer, int startPosition, int endPosition) {
241        final int w = ((View) mImage.getParent()).getWidth();
242        final int h = mImage.getHeight();
243        if (w <= 0 || h <= 0) {
244            // view is not visible this time. Skip drawing.
245            return;
246        }
247        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
248        final Canvas c = new Canvas(b);
249        final Paint paint = new Paint();
250        paint.setColor(0xFFFFFFFF); // 0xAARRGGBB
251        paint.setAntiAlias(true);
252        paint.setStyle(Paint.Style.STROKE);
253        paint.setAlpha(0x90);
254
255        final PathEffect effect = new CornerPathEffect(3);
256        paint.setPathEffect(effect);
257
258        final int numSamples = waveBuffer.remaining();
259        int endIndex;
260        if (endPosition == 0) {
261            endIndex = numSamples;
262        } else {
263            endIndex = Math.min(endPosition, numSamples);
264        }
265
266        int startIndex = startPosition - 2000; // include 250ms before speech
267        if (startIndex < 0) {
268            startIndex = 0;
269        }
270        final int numSamplePerWave = 200;  // 8KHz 25ms = 200 samples
271        final float scale = 10.0f / 65536.0f;
272
273        final int count = (endIndex - startIndex) / numSamplePerWave;
274        final float deltaX = 1.0f * w / count;
275        int yMax = h / 2 - 8;
276        Path path = new Path();
277        c.translate(0, yMax);
278        float x = 0;
279        path.moveTo(x, 0);
280        for (int i = 0; i < count; i++) {
281            final int avabs = getAverageAbs(waveBuffer, startIndex, i , numSamplePerWave);
282            int sign = ( (i & 01) == 0) ? -1 : 1;
283            final float y = Math.min(yMax, avabs * h * scale) * sign;
284            path.lineTo(x, y);
285            x += deltaX;
286            path.lineTo(x, y);
287        }
288        if (deltaX > 4) {
289            paint.setStrokeWidth(3);
290        } else {
291            paint.setStrokeWidth(Math.max(1, (int) (deltaX -.05)));
292        }
293        c.drawPath(path, paint);
294        mImage.setImageBitmap(b);
295        mImage.setVisibility(View.VISIBLE);
296        MarginLayoutParams mProgressParams = (MarginLayoutParams)mProgress.getLayoutParams();
297        mProgressParams.topMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
298                -h , mContext.getResources().getDisplayMetrics());
299
300        // Tweak the padding manually to fill out the whole view horizontally.
301        // TODO: Do this in the xml layout instead.
302        ((View) mImage.getParent()).setPadding(4, ((View) mImage.getParent()).getPaddingTop(), 3,
303                ((View) mImage.getParent()).getPaddingBottom());
304        mProgress.setLayoutParams(mProgressParams);
305    }
306
307
308    public void finish() {
309        mUiHandler.post(new Runnable() {
310            public void run() {
311                mState = State.READY;
312                exitWorking();
313            }
314          });
315    }
316
317    private void exitWorking() {
318        mProgress.setVisibility(View.GONE);
319        mImage.setVisibility(View.VISIBLE);
320    }
321}