/slidetext/src/com/google/marvin/slidetext/SlideTextView.java

http://eyes-free.googlecode.com/ · Java · 512 lines · 456 code · 39 blank · 17 comment · 67 complexity · 305a4de7eed471bf6416f159fbac4cdb MD5 · raw file

  1. package com.google.marvin.slidetext;
  2. import com.google.tts.TTSParams;
  3. import android.content.Context;
  4. import android.graphics.Canvas;
  5. import android.graphics.Color;
  6. import android.graphics.Paint;
  7. import android.graphics.Typeface;
  8. import android.hardware.SensorListener;
  9. import android.hardware.SensorManager;
  10. import android.os.Vibrator;
  11. import android.view.MotionEvent;
  12. import android.view.View;
  13. /**
  14. * Implements the user interface for doing slide texting.
  15. *
  16. * @author clchen@google.com (Charles L. Chen)
  17. */
  18. public class SlideTextView extends View {
  19. private static final int AE = 0;
  20. private static final int IM = 1;
  21. private static final int QU = 2;
  22. private static final int Y = 4;
  23. private static final int NONE = 5;
  24. private static final double deletionForce = 2.5;
  25. private static final int deletionCount = 1;
  26. private static final long[] PATTERN = {0, 1, 40, 41};
  27. private final double left = 0;
  28. private final double upleft = Math.PI * .25;
  29. private final double up = Math.PI * .5;
  30. private final double upright = Math.PI * .75;
  31. private final double downright = -Math.PI * .75;
  32. private final double down = -Math.PI * .5;
  33. private final double downleft = -Math.PI * .25;
  34. private final double right = Math.PI;
  35. private final double rightWrap = -Math.PI;
  36. private SlideText parent;
  37. private double downX;
  38. private double downY;
  39. private double lastX;
  40. private double lastY;
  41. private int currentValue;
  42. private boolean screenIsBeingTouched;
  43. private Vibrator vibe;
  44. private int currentWheel;
  45. private String currentCharacter;
  46. private String currentString;
  47. private SensorManager sensorManager;
  48. private int shakeCount = 0;
  49. private boolean lastDeletionShakePositive = false;
  50. private boolean screenVisible = true;
  51. private boolean justConfirmed;
  52. /**
  53. * Handles the sensor events for changes to readings and accuracy
  54. */
  55. private final SensorListener mListener = new SensorListener() {
  56. public void onSensorChanged(int sensor, float[] values) {
  57. if ((values[0] > deletionForce) && !lastDeletionShakePositive) {
  58. shakeCount++;
  59. lastDeletionShakePositive = true;
  60. } else if ((values[0] < -deletionForce) && lastDeletionShakePositive) {
  61. shakeCount++;
  62. lastDeletionShakePositive = false;
  63. }
  64. if (shakeCount > deletionCount) {
  65. backspace();
  66. shakeCount = 0;
  67. }
  68. }
  69. public void onAccuracyChanged(int arg0, int arg1) {
  70. // Ignore accelerometer accuracy for now
  71. }
  72. };
  73. public SlideTextView(Context context) {
  74. super(context);
  75. parent = (SlideText) context;
  76. downX = 0;
  77. downY = 0;
  78. lastX = 0;
  79. lastY = 0;
  80. currentValue = -1;
  81. screenIsBeingTouched = false;
  82. justConfirmed = false;
  83. currentWheel = NONE;
  84. currentCharacter = "";
  85. currentString = "";
  86. vibe = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
  87. screenVisible = true;
  88. shakeCount = 0;
  89. lastDeletionShakePositive = false;
  90. sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
  91. sensorManager.registerListener(mListener, SensorManager.SENSOR_ACCELEROMETER,
  92. SensorManager.SENSOR_DELAY_FASTEST);
  93. }
  94. private void confirmEntry() {
  95. screenIsBeingTouched = false;
  96. int prevVal = currentValue;
  97. currentValue = evalMotion(lastX, lastY);
  98. // Do some correction if the user lifts up on deadspace
  99. if (currentValue == -1) {
  100. currentValue = prevVal;
  101. }
  102. // The user never got a number that wasn't deadspace,
  103. // so assume 5.
  104. if (currentValue == -1) {
  105. currentValue = 5;
  106. }
  107. currentCharacter = getCharacter(currentWheel, currentValue);
  108. if (currentCharacter.equals("SPACE")) {
  109. currentString = currentString + " ";
  110. } else if (currentCharacter.equals("MODE")) {
  111. // Do nothing
  112. } else {
  113. currentString = currentString + currentCharacter;
  114. }
  115. speak(currentCharacter, 0, null);
  116. justConfirmed = true;
  117. invalidate();
  118. initiateMotion(lastX, lastY);
  119. }
  120. private void initiateMotion(double x, double y) {
  121. downX = x;
  122. downY = y;
  123. lastX = x;
  124. lastY = y;
  125. currentValue = -1;
  126. currentWheel = NONE;
  127. currentCharacter = "";
  128. }
  129. @Override
  130. public boolean onTouchEvent(MotionEvent event) {
  131. int action = event.getAction();
  132. float x = event.getX();
  133. float y = event.getY();
  134. if (action == MotionEvent.ACTION_DOWN) {
  135. initiateMotion(x, y);
  136. return true;
  137. } else if (action == MotionEvent.ACTION_UP) {
  138. confirmEntry();
  139. return true;
  140. } else {
  141. screenIsBeingTouched = true;
  142. lastX = x;
  143. lastY = y;
  144. int prevVal = currentValue;
  145. currentValue = evalMotion(x, y);
  146. // Do nothing since we want a deadzone here;
  147. // restore the state to the previous value.
  148. if (currentValue == -1) {
  149. currentValue = prevVal;
  150. return true;
  151. }
  152. // There is a wheel that is active
  153. if (currentValue != 5) {
  154. if (currentWheel == NONE) {
  155. currentWheel = getWheel(currentValue);
  156. }
  157. currentCharacter = getCharacter(currentWheel, currentValue);
  158. } else {
  159. currentCharacter = "";
  160. }
  161. invalidate();
  162. if (prevVal != currentValue) {
  163. if (currentCharacter.equals("")) {
  164. speak("[tock]", 0, null);
  165. } else {
  166. String[] params = new String[1];
  167. params[0] = TTSParams.VOICE_FEMALE.toString();
  168. speak(currentCharacter, 0, params);
  169. }
  170. vibe.vibrate(PATTERN, -1);
  171. }
  172. return true;
  173. }
  174. }
  175. public int getWheel(int value) {
  176. switch (value) {
  177. case 1:
  178. return AE;
  179. case 2:
  180. return IM;
  181. case 3:
  182. return QU;
  183. case 4:
  184. return Y;
  185. case 5:
  186. return NONE;
  187. case 6:
  188. return Y;
  189. case 7:
  190. return QU;
  191. case 8:
  192. return IM;
  193. case 9:
  194. return AE;
  195. default:
  196. return NONE;
  197. }
  198. }
  199. public String getCharacter(int wheel, int value) {
  200. switch (wheel) {
  201. case AE:
  202. switch (value) {
  203. case 1:
  204. return "A";
  205. case 2:
  206. return "B";
  207. case 3:
  208. return "C";
  209. case 4:
  210. return "H";
  211. case 5:
  212. return "";
  213. case 6:
  214. return "D";
  215. case 7:
  216. return "G";
  217. case 8:
  218. return "F";
  219. case 9:
  220. return "E";
  221. default:
  222. return "";
  223. }
  224. case IM:
  225. switch (value) {
  226. case 1:
  227. return "P";
  228. case 2:
  229. return "I";
  230. case 3:
  231. return "J";
  232. case 4:
  233. return "O";
  234. case 5:
  235. return "";
  236. case 6:
  237. return "K";
  238. case 7:
  239. return "N";
  240. case 8:
  241. return "M";
  242. case 9:
  243. return "L";
  244. default:
  245. return "";
  246. }
  247. case QU:
  248. switch (value) {
  249. case 1:
  250. return "W";
  251. case 2:
  252. return "X";
  253. case 3:
  254. return "Q";
  255. case 4:
  256. return "V";
  257. case 5:
  258. return "";
  259. case 6:
  260. return "R";
  261. case 7:
  262. return "U";
  263. case 8:
  264. return "T";
  265. case 9:
  266. return "S";
  267. default:
  268. return "";
  269. }
  270. case Y:
  271. switch (value) {
  272. case 1:
  273. return ",";
  274. case 2:
  275. return "!";
  276. case 3:
  277. return "MODE";
  278. case 4:
  279. return "SPACE";
  280. case 5:
  281. return "";
  282. case 6:
  283. return "Y";
  284. case 7:
  285. return ".";
  286. case 8:
  287. return "?";
  288. case 9:
  289. return "Z";
  290. default:
  291. return "";
  292. }
  293. default:
  294. return "";
  295. }
  296. }
  297. public int evalMotion(double x, double y) {
  298. float rTolerance = 25;
  299. double thetaTolerance = (Math.PI / 16);
  300. boolean movedFar = false;
  301. double r = Math.sqrt(((downX - x) * (downX - x)) + ((downY - y) * (downY - y)));
  302. if (r < rTolerance) {
  303. return 5;
  304. }
  305. if (r > 10 * rTolerance) {
  306. movedFar = true;
  307. }
  308. double theta = Math.atan2(downY - y, downX - x);
  309. if (Math.abs(theta - left) < thetaTolerance) {
  310. return 4;
  311. } else if (Math.abs(theta - upleft) < thetaTolerance) {
  312. return 1;
  313. } else if (Math.abs(theta - up) < thetaTolerance) {
  314. return 2;
  315. } else if (Math.abs(theta - upright) < thetaTolerance) {
  316. return 3;
  317. } else if (Math.abs(theta - downright) < thetaTolerance) {
  318. return 9;
  319. } else if (Math.abs(theta - down) < thetaTolerance) {
  320. return 8;
  321. } else if (Math.abs(theta - downleft) < thetaTolerance) {
  322. return 7;
  323. } else if ((theta > right - thetaTolerance) || (theta < rightWrap + thetaTolerance)) {
  324. return 6;
  325. }
  326. // Off by more than the threshold, so it doesn't count
  327. return -1;
  328. }
  329. @Override
  330. public void onDraw(Canvas canvas) {
  331. Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
  332. paint.setColor(Color.WHITE);
  333. paint.setTextAlign(Paint.Align.CENTER);
  334. paint.setTypeface(Typeface.DEFAULT_BOLD);
  335. int x = 5;
  336. int y = 50;
  337. paint.setTextSize(50);
  338. paint.setTextAlign(Paint.Align.LEFT);
  339. y -= paint.ascent() / 2;
  340. canvas.drawText(currentString, x, y, paint);
  341. if (screenIsBeingTouched) {
  342. int offset = 90;
  343. int regSize = 50;
  344. int selectedSize = regSize * 2;
  345. int x1 = (int) downX - offset;
  346. int y1 = (int) downY - offset;
  347. int x2 = (int) downX;
  348. int y2 = (int) downY - offset;
  349. int x3 = (int) downX + offset;
  350. int y3 = (int) downY - offset;
  351. int x4 = (int) downX - offset;
  352. int y4 = (int) downY;
  353. int x6 = (int) downX + offset;
  354. int y6 = (int) downY;
  355. int x7 = (int) downX - offset;
  356. int y7 = (int) downY + offset;
  357. int x8 = (int) downX;
  358. int y8 = (int) downY + offset;
  359. int x9 = (int) downX + offset;
  360. int y9 = (int) downY + offset;
  361. y1 -= paint.ascent() / 2;
  362. y2 -= paint.ascent() / 2;
  363. y3 -= paint.ascent() / 2;
  364. y4 -= paint.ascent() / 2;
  365. y6 -= paint.ascent() / 2;
  366. y7 -= paint.ascent() / 2;
  367. y8 -= paint.ascent() / 2;
  368. y9 -= paint.ascent() / 2;
  369. switch (currentWheel) {
  370. case AE:
  371. paint.setColor(Color.RED);
  372. drawCharacter("A", x1, y1, canvas, paint, currentCharacter.equals("A"));
  373. drawCharacter("B", x2, y2, canvas, paint, currentCharacter.equals("B"));
  374. drawCharacter("C", x3, y3, canvas, paint, currentCharacter.equals("C"));
  375. drawCharacter("H", x4, y4, canvas, paint, currentCharacter.equals("H"));
  376. drawCharacter("D", x6, y6, canvas, paint, currentCharacter.equals("D"));
  377. drawCharacter("G", x7, y7, canvas, paint, currentCharacter.equals("G"));
  378. drawCharacter("F", x8, y8, canvas, paint, currentCharacter.equals("F"));
  379. drawCharacter("E", x9, y9, canvas, paint, currentCharacter.equals("E"));
  380. break;
  381. case IM:
  382. paint.setColor(Color.BLUE);
  383. drawCharacter("P", x1, y1, canvas, paint, currentCharacter.equals("P"));
  384. drawCharacter("I", x2, y2, canvas, paint, currentCharacter.equals("I"));
  385. drawCharacter("J", x3, y3, canvas, paint, currentCharacter.equals("J"));
  386. drawCharacter("O", x4, y4, canvas, paint, currentCharacter.equals("O"));
  387. drawCharacter("K", x6, y6, canvas, paint, currentCharacter.equals("K"));
  388. drawCharacter("N", x7, y7, canvas, paint, currentCharacter.equals("N"));
  389. drawCharacter("M", x8, y8, canvas, paint, currentCharacter.equals("M"));
  390. drawCharacter("L", x9, y9, canvas, paint, currentCharacter.equals("L"));
  391. break;
  392. case QU:
  393. paint.setColor(Color.GREEN);
  394. drawCharacter("W", x1, y1, canvas, paint, currentCharacter.equals("W"));
  395. drawCharacter("X", x2, y2, canvas, paint, currentCharacter.equals("X"));
  396. drawCharacter("Q", x3, y3, canvas, paint, currentCharacter.equals("Q"));
  397. drawCharacter("V", x4, y4, canvas, paint, currentCharacter.equals("V"));
  398. drawCharacter("R", x6, y6, canvas, paint, currentCharacter.equals("R"));
  399. drawCharacter("U", x7, y7, canvas, paint, currentCharacter.equals("U"));
  400. drawCharacter("T", x8, y8, canvas, paint, currentCharacter.equals("T"));
  401. drawCharacter("S", x9, y9, canvas, paint, currentCharacter.equals("S"));
  402. break;
  403. case Y:
  404. paint.setColor(Color.YELLOW);
  405. drawCharacter(",", x1, y1, canvas, paint, currentCharacter.equals(","));
  406. drawCharacter("!", x2, y2, canvas, paint, currentCharacter.equals("!"));
  407. drawCharacter("MODE", x3, y3, canvas, paint, currentCharacter.equals("MODE"));
  408. drawCharacter("SPACE", x4, y4, canvas, paint, currentCharacter.equals("SPACE"));
  409. drawCharacter("Y", x6, y6, canvas, paint, currentCharacter.equals("Y"));
  410. drawCharacter(".", x7, y7, canvas, paint, currentCharacter.equals("."));
  411. drawCharacter("?", x8, y8, canvas, paint, currentCharacter.equals("?"));
  412. drawCharacter("Z", x9, y9, canvas, paint, currentCharacter.equals("Z"));
  413. break;
  414. default:
  415. paint.setColor(Color.RED);
  416. canvas.drawText("A", x1, y1, paint);
  417. canvas.drawText("E", x9, y9, paint);
  418. paint.setColor(Color.BLUE);
  419. canvas.drawText("I", x2, y2, paint);
  420. canvas.drawText("M", x8, y8, paint);
  421. paint.setColor(Color.GREEN);
  422. canvas.drawText("Q", x3, y3, paint);
  423. canvas.drawText("U", x7, y7, paint);
  424. paint.setColor(Color.YELLOW);
  425. canvas.drawText("Y", x6, y6, paint);
  426. canvas.drawText("SPACE", x4, y4, paint);
  427. break;
  428. }
  429. }
  430. }
  431. private void drawCharacter(String character, int x, int y, Canvas canvas, Paint paint,
  432. boolean isSelected) {
  433. int regSize = 50;
  434. int selectedSize = regSize * 2;
  435. if (isSelected) {
  436. paint.setTextSize(selectedSize);
  437. } else {
  438. paint.setTextSize(regSize);
  439. }
  440. canvas.drawText(character, x, y, paint);
  441. }
  442. public void backspace() {
  443. String deletedCharacter = "";
  444. if (currentString.length() > 0) {
  445. deletedCharacter = "" + currentString.charAt(currentString.length() - 1);
  446. currentString = currentString.substring(0, currentString.length() - 1);
  447. }
  448. if (!deletedCharacter.equals("")) {
  449. speak(deletedCharacter, 0, new String[] {TTSParams.VOICE_ROBOT.toString()});
  450. } else {
  451. speak("Nothing to delete", 0, null);
  452. }
  453. invalidate();
  454. }
  455. @Override
  456. protected void onWindowVisibilityChanged(int visibility) {
  457. if (visibility == View.VISIBLE) {
  458. screenVisible = true;
  459. } else {
  460. screenVisible = false;
  461. }
  462. }
  463. private void speak(String text, int queueMode, String[] params) {
  464. if (!screenVisible) {
  465. return;
  466. }
  467. if (justConfirmed) {
  468. queueMode = 1;
  469. justConfirmed = false;
  470. }
  471. parent.tts.speak(text, queueMode, params);
  472. }
  473. }