/ocr/ocrservice/src/com/googlecode/eyesfree/ocr/intent/VoiceGestureView.java

http://eyes-free.googlecode.com/ · Java · 297 lines · 202 code · 62 blank · 33 comment · 23 complexity · 6737a539f2124211394a3870ff2dbf3f MD5 · raw file

  1. /*
  2. * Copyright (C) 2011 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.ocr.intent;
  17. import android.content.Context;
  18. import android.graphics.Canvas;
  19. import android.graphics.Color;
  20. import android.graphics.Paint;
  21. import android.graphics.Rect;
  22. import android.speech.tts.TextToSpeech;
  23. import android.util.AttributeSet;
  24. import android.view.GestureDetector;
  25. import android.view.GestureDetector.SimpleOnGestureListener;
  26. import android.view.MotionEvent;
  27. import android.view.View;
  28. import com.googlecode.eyesfree.ocr.R;
  29. import java.util.HashMap;
  30. import java.util.LinkedList;
  31. import java.util.Vector;
  32. /**
  33. * @author alanv@google.com (Alan Viverette)
  34. */
  35. public class VoiceGestureView extends View {
  36. private static final String TAG = "VoiceGestureView";
  37. private static final String PACKAGE = "com.googlecode.eyesfree.ocr";
  38. private static final String EARCON_CLICK = "[click]";
  39. private static final String EARCON_LOUD_BEEP = "[long_beep]";
  40. private static final String EARCON_DOUBLE_BEEP = "[double_beep]";
  41. private GestureDetector mDetector;
  42. private Paint mPaint;
  43. private HashMap<String, String> mParams;
  44. private TextToSpeech mTts;
  45. private LinkedList<String> mOldUtterances;
  46. private LinkedList<String> mNewUtterances;
  47. private String mCurrentUtterance;
  48. private boolean mTtsReady;
  49. private boolean mManualMode;
  50. public VoiceGestureView(Context context) {
  51. super(context);
  52. init();
  53. }
  54. public VoiceGestureView(Context context, AttributeSet attrs) {
  55. super(context, attrs);
  56. init();
  57. }
  58. public VoiceGestureView(Context context, AttributeSet attrs, int defStyle) {
  59. super(context, attrs, defStyle);
  60. init();
  61. }
  62. private void init() {
  63. Context context = getContext();
  64. mDetector = new GestureDetector(gestureListener);
  65. mPaint = new Paint();
  66. mParams = new HashMap<String, String>();
  67. mParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, TAG);
  68. mTts = new TextToSpeech(context, ttsInitListener);
  69. mOldUtterances = new LinkedList<String>();
  70. mNewUtterances = new LinkedList<String>();
  71. mCurrentUtterance = null;
  72. mManualMode = false;
  73. }
  74. @Override
  75. protected void onDetachedFromWindow() {
  76. super.onDetachedFromWindow();
  77. mTts.shutdown();
  78. }
  79. @Override
  80. protected void onDraw(Canvas canvas) {
  81. Vector<String> lines = new Vector<String>();
  82. lines.add("Queued: " + mNewUtterances.size());
  83. lines.add("Speaking: " + (mCurrentUtterance != null));
  84. lines.add("Spoken: " + mOldUtterances.size());
  85. final Paint p = new Paint();
  86. final int kLargeTextSize = 20;
  87. final int kSmallTextSize = 16;
  88. final int kTextBufferSize = 4;
  89. // TODO(andrewharp): Don't hardcode this, figure out the text
  90. // length.
  91. final int shadedWidth = 200;
  92. // Each block has one large header line followed by a buffer, then N
  93. // smaller lines each followed by a buffer, and then an additional
  94. // buffer.
  95. final int shadedHeight = kLargeTextSize + kTextBufferSize
  96. + (kSmallTextSize + kTextBufferSize) * lines.size() + kTextBufferSize;
  97. int startingYPos = 0;
  98. p.setColor(Color.BLACK);
  99. p.setAlpha(100);
  100. int yPos = startingYPos;
  101. int xPos = 0;
  102. canvas.drawRect(new Rect(xPos, yPos, xPos + shadedWidth, yPos + shadedHeight), p);
  103. // Header line.
  104. p.setAlpha(255);
  105. p.setAntiAlias(true);
  106. p.setColor(Color.CYAN);
  107. p.setTextSize(kLargeTextSize);
  108. yPos += kLargeTextSize + kTextBufferSize;
  109. canvas.drawText(TAG, xPos, yPos, p);
  110. mPaint.setColor(Color.WHITE);
  111. mPaint.setTextSize(kSmallTextSize);
  112. for (final String line : lines) {
  113. yPos += kSmallTextSize + kTextBufferSize;
  114. canvas.drawText(line, xPos, yPos, mPaint);
  115. }
  116. }
  117. public void shutdown() {
  118. mTts.shutdown();
  119. }
  120. /**
  121. * Adds an utterance to the queue. If the queue is empty and no utterance is
  122. * currently being spoken, plays the utterance immediately.
  123. *
  124. * @param utterance
  125. */
  126. public void addUtterance(String utterance) {
  127. synchronized (this) {
  128. mNewUtterances.addLast(utterance);
  129. // If the current utterance is null, advance to the one we added.
  130. if (mCurrentUtterance == null && !mManualMode) {
  131. changeUtterance(1);
  132. }
  133. }
  134. }
  135. @Override
  136. public boolean onTouchEvent(MotionEvent e) {
  137. if (mDetector.onTouchEvent(e))
  138. return true;
  139. return true;
  140. }
  141. private boolean onSingleTap() {
  142. if (mManualMode) {
  143. mManualMode = false;
  144. return changeUtterance(1);
  145. } else {
  146. mManualMode = true;
  147. return changeUtterance(0);
  148. }
  149. }
  150. private boolean onVerticalSwipe(float delta) {
  151. mManualMode = true;
  152. if (delta > 0) {
  153. return changeUtterance(1);
  154. } else {
  155. return changeUtterance(-1);
  156. }
  157. }
  158. private boolean changeUtterance(int direction) {
  159. boolean changed;
  160. synchronized (this) {
  161. if (!mTtsReady) {
  162. return false;
  163. }
  164. LinkedList<String> src;
  165. LinkedList<String> dst;
  166. if (direction < 0) {
  167. src = mOldUtterances;
  168. dst = mNewUtterances;
  169. } else if (direction > 0){
  170. src = mNewUtterances;
  171. dst = mOldUtterances;
  172. } else {
  173. src = null;
  174. dst = mOldUtterances;
  175. }
  176. if (mCurrentUtterance != null) {
  177. dst.addFirst(mCurrentUtterance);
  178. }
  179. if (src != null && !src.isEmpty()) {
  180. mCurrentUtterance = src.removeFirst();
  181. mTts.speak(mCurrentUtterance, TextToSpeech.QUEUE_FLUSH, null);
  182. mTts.speak(EARCON_CLICK, TextToSpeech.QUEUE_ADD, mParams);
  183. changed = true;
  184. } else {
  185. mCurrentUtterance = null;
  186. mTts.speak(EARCON_LOUD_BEEP, TextToSpeech.QUEUE_FLUSH, null);
  187. changed = false;
  188. mManualMode = false;
  189. }
  190. }
  191. postInvalidate();
  192. return changed;
  193. }
  194. private static final float X_TOLERANCE = 0.25f;
  195. private final SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() {
  196. @Override
  197. public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
  198. int width = getWidth();
  199. int height = getHeight();
  200. float dX = (e2.getX() - e1.getX()) / width;
  201. float dY = (e2.getY() - e1.getY()) / height;
  202. if (Math.abs(dX) > X_TOLERANCE) {
  203. return onVerticalSwipe(dX);
  204. }
  205. return false;
  206. }
  207. @Override
  208. public boolean onSingleTapUp(MotionEvent e) {
  209. return onSingleTap();
  210. }
  211. };
  212. private final TextToSpeech.OnInitListener ttsInitListener = new TextToSpeech.OnInitListener() {
  213. @Override
  214. public void onInit(int status) {
  215. synchronized (VoiceGestureView.this) {
  216. mTtsReady = true;
  217. mTts.setOnUtteranceCompletedListener(utteranceListener);
  218. mTts.addSpeech(EARCON_CLICK, PACKAGE, R.raw.click);
  219. mTts.addSpeech(EARCON_LOUD_BEEP, PACKAGE, R.raw.loud_beep);
  220. mTts.addSpeech(EARCON_DOUBLE_BEEP, PACKAGE, R.raw.double_beep);
  221. }
  222. }
  223. };
  224. private final TextToSpeech.OnUtteranceCompletedListener utteranceListener =
  225. new TextToSpeech.OnUtteranceCompletedListener() {
  226. @Override
  227. public void onUtteranceCompleted(String utteranceId) {
  228. // When we finish an utterance, immediately move to the next
  229. // one.
  230. if (!mManualMode) {
  231. changeUtterance(1);
  232. }
  233. }
  234. };
  235. }