• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
RecognitionView(Context context, OnClickListener clickListener)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 
getView()115     public View getView() {
116         return mView;
117     }
118 
restoreState()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 
showInitializing()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 
showListening()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 
updateVoiceMeter(float rmsdB)155     public void updateVoiceMeter(float rmsdB) {
156         mSoundIndicator.setRmsdB(rmsdB);
157     }
158 
showError(final String message)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 
showWorking( final ByteArrayOutputStream waveBuffer, final int speechStartPosition, final int speechEndPosition)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 
prepareDialog(CharSequence text, Drawable image, CharSequence btnTxt)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      */
getAverageAbs(ShortBuffer buffer, int start, int i, int npw)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      */
showWave(ShortBuffer waveBuffer, int startPosition, int endPosition)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 
finish()347     public void finish() {
348         mUiHandler.post(new Runnable() {
349             @Override
350             public void run() {
351                 mSoundIndicator.stop();
352             }
353         });
354     }
355 }
356