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