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