/shell/src/com/google/marvin/shell/AppChooserView.java

http://eyes-free.googlecode.com/ · Java · 966 lines · 801 code · 103 blank · 62 comment · 137 complexity · 92673ea6f3dd5e2a0b8d2f93416609c2 MD5 · raw file

  1. /*
  2. * Copyright (C) 2008 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.google.marvin.shell;
  17. import android.content.ActivityNotFoundException;
  18. import android.content.Context;
  19. import android.content.Intent;
  20. import android.graphics.Canvas;
  21. import android.graphics.Color;
  22. import android.graphics.Paint;
  23. import android.graphics.Typeface;
  24. import android.net.Uri;
  25. import android.speech.tts.TextToSpeech;
  26. import android.util.AttributeSet;
  27. import android.view.KeyCharacterMap;
  28. import android.view.KeyEvent;
  29. import android.view.MotionEvent;
  30. import android.view.View;
  31. import android.view.accessibility.AccessibilityManager;
  32. import android.widget.TextView;
  33. import com.google.marvin.shell.ShakeDetector.ShakeListener;
  34. import com.googlecode.eyesfree.utils.FeedbackController;
  35. import com.googlecode.eyesfree.utils.compat.MotionEventCompatUtils;
  36. import java.lang.reflect.InvocationTargetException;
  37. import java.lang.reflect.Method;
  38. import java.util.ArrayList;
  39. import java.util.Collections;
  40. /**
  41. * Allows the user to select the application they wish to start by moving
  42. * through the list of applications.
  43. *
  44. * @author clchen@google.com (Charles L. Chen)
  45. */
  46. public class AppChooserView extends TextView {
  47. // BEGIN OF WORKAROUND FOR BACKWARDS COMPATIBILITY (up to Donut)
  48. private static Method MotionEvent_getX;
  49. private static Method MotionEvent_getY;
  50. private static Method AccessibilityManager_isTouchExplorationEnabled;
  51. static {
  52. initCompatibility();
  53. }
  54. private static void initCompatibility() {
  55. try {
  56. MotionEvent_getX = MotionEvent.class.getMethod("getX", new Class[] { Integer.TYPE });
  57. MotionEvent_getY = MotionEvent.class.getMethod("getY", new Class[] { Integer.TYPE });
  58. AccessibilityManager_isTouchExplorationEnabled = AccessibilityManager.class.getMethod(
  59. "isTouchExplorationEnabled");
  60. /* success, this is a newer device */
  61. } catch (NoSuchMethodException nsme) {
  62. /* failure, must be older device */
  63. }
  64. }
  65. private static float getX(MotionEvent event, int x) {
  66. try {
  67. Object retobj = MotionEvent_getX.invoke(event, x);
  68. return (Float) retobj;
  69. } catch (IllegalAccessException ie) {
  70. System.err.println("unexpected " + ie);
  71. } catch (IllegalArgumentException e) {
  72. // TODO Auto-generated catch block
  73. e.printStackTrace();
  74. } catch (InvocationTargetException e) {
  75. // TODO Auto-generated catch block
  76. e.printStackTrace();
  77. }
  78. return -1;
  79. }
  80. private static float getY(MotionEvent event, int y) {
  81. try {
  82. Object retobj = MotionEvent_getY.invoke(event, y);
  83. return (Float) retobj;
  84. } catch (IllegalAccessException ie) {
  85. System.err.println("unexpected " + ie);
  86. } catch (IllegalArgumentException e) {
  87. // TODO Auto-generated catch block
  88. e.printStackTrace();
  89. } catch (InvocationTargetException e) {
  90. // TODO Auto-generated catch block
  91. e.printStackTrace();
  92. }
  93. return -1;
  94. }
  95. private static boolean isTouchExplorationEnabled(AccessibilityManager am) {
  96. try {
  97. if (AccessibilityManager_isTouchExplorationEnabled != null) {
  98. Object retobj = AccessibilityManager_isTouchExplorationEnabled.invoke(am);
  99. return (Boolean) retobj;
  100. }
  101. } catch (IllegalAccessException ie) {
  102. System.err.println("unexpected " + ie);
  103. } catch (IllegalArgumentException e) {
  104. // TODO Auto-generated catch block
  105. e.printStackTrace();
  106. } catch (InvocationTargetException e) {
  107. // TODO Auto-generated catch block
  108. e.printStackTrace();
  109. }
  110. return false;
  111. }
  112. // END OF WORKAROUND FOR DONUT COMPATIBILITY
  113. private static final char alphabet[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
  114. 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
  115. private static final int AE = 0;
  116. private static final int IM = 1;
  117. private static final int QU = 2;
  118. private static final int Y = 4;
  119. private static final int NONE = 5;
  120. private final double left = 0;
  121. private final double upleft = Math.PI * .25;
  122. private final double up = Math.PI * .5;
  123. private final double upright = Math.PI * .75;
  124. private final double downright = -Math.PI * .75;
  125. private final double down = -Math.PI * .5;
  126. private final double downleft = -Math.PI * .25;
  127. private final double right = Math.PI;
  128. private final double rightWrap = -Math.PI;
  129. private MarvinShell parent;
  130. private ArrayList<AppInfo> appList;
  131. private int appListIndex;
  132. private double downX;
  133. private double downY;
  134. private double lastX;
  135. private double lastY;
  136. private int currentValue;
  137. private boolean screenIsBeingTouched;
  138. private boolean gestureHadSecondPoint;
  139. private int currentWheel;
  140. private String currentCharacter;
  141. private String currentString;
  142. private int trackballTimeout = 500;
  143. private boolean trackballEnabled = true;
  144. private boolean inDPadMode = false;
  145. private float p2DownX;
  146. private float p2DownY;
  147. private ShakeDetector shakeDetector;
  148. private long backKeyTimeDown;
  149. private long lastTapTime = 0;
  150. private long doubleTapWindow = 700;
  151. private AccessibilityManager accessibilityManager;
  152. private FeedbackController feedbackController;
  153. // Change this to true to enable shake to erase
  154. private static final boolean useShake = false;
  155. public AppChooserView(Context context, AttributeSet attrs) {
  156. super(context, attrs);
  157. parent = ((MarvinShell) context);
  158. feedbackController = FeedbackController.getInstance(context);
  159. accessibilityManager = (AccessibilityManager) context.getSystemService(
  160. Context.ACCESSIBILITY_SERVICE);
  161. // Build app list here
  162. appListIndex = 0;
  163. currentString = "";
  164. setClickable(true);
  165. setFocusable(true);
  166. setFocusableInTouchMode(true);
  167. requestFocus();
  168. }
  169. public void setAppList(ArrayList<AppInfo> installedApps) {
  170. appList = installedApps;
  171. }
  172. @Override
  173. public boolean onTrackballEvent(MotionEvent event) {
  174. if (trackballEnabled == false) {
  175. return true;
  176. }
  177. // Log.i("Motion", Float.toString(event.getY()));
  178. if (event.getAction() == MotionEvent.ACTION_DOWN) {
  179. startActionHandler();
  180. return true;
  181. } else if (event.getY() > .16) {
  182. trackballEnabled = false;
  183. (new Thread(new trackballTimeout())).start();
  184. nextApp();
  185. currentString = "";
  186. } else if (event.getY() < -.16) {
  187. trackballEnabled = false;
  188. (new Thread(new trackballTimeout())).start();
  189. prevApp();
  190. currentString = "";
  191. }
  192. return true;
  193. }
  194. class trackballTimeout implements Runnable {
  195. @Override
  196. public void run() {
  197. try {
  198. Thread.sleep(trackballTimeout);
  199. trackballEnabled = true;
  200. } catch (InterruptedException e) {
  201. e.printStackTrace();
  202. }
  203. }
  204. }
  205. public void nextApp() {
  206. appListIndex++;
  207. if (appListIndex >= appList.size()) {
  208. appListIndex = 0;
  209. }
  210. feedbackController.playVibration(R.array.pattern_app_chooser);
  211. speakCurrentApp(true);
  212. }
  213. public void prevApp() {
  214. appListIndex--;
  215. if (appListIndex < 0) {
  216. appListIndex = appList.size() - 1;
  217. }
  218. feedbackController.playVibration(R.array.pattern_app_chooser);
  219. speakCurrentApp(true);
  220. }
  221. private void jumpToFirstMatchingApp() {
  222. int index = 0;
  223. boolean foundMatch = false;
  224. while (!foundMatch && (index < appList.size())) {
  225. String title = appList.get(index).getTitle().toLowerCase();
  226. if (title.startsWith(currentString.toLowerCase())) {
  227. appListIndex = index - 1;
  228. foundMatch = true;
  229. }
  230. index++;
  231. }
  232. if (!foundMatch) {
  233. parent.tts.playEarcon(parent.getString(R.string.earcon_tock), 0, null);
  234. if (currentString.length() > 0) {
  235. currentString = currentString.substring(0, currentString.length() - 1);
  236. }
  237. speakCurrentApp(true);
  238. return;
  239. } else {
  240. nextApp();
  241. }
  242. }
  243. public void resetListState() {
  244. appListIndex = 0;
  245. currentString = "";
  246. }
  247. public void speakCurrentApp(boolean interrupt) {
  248. String name = appList.get(appListIndex).getTitle();
  249. if (interrupt) {
  250. parent.tts.speak(name, TextToSpeech.QUEUE_FLUSH, null);
  251. } else {
  252. parent.tts.speak(name, TextToSpeech.QUEUE_ADD, null);
  253. }
  254. invalidate();
  255. }
  256. public void addApplication(AppInfo app) {
  257. synchronized (appList) {
  258. appList.add(app);
  259. Collections.sort(appList);
  260. }
  261. appListIndex = 0;
  262. currentString = "";
  263. }
  264. public void removePackage(String packageName) {
  265. synchronized (appList) {
  266. for (int i = 0; i < appList.size(); ++i) {
  267. if (appList.get(i).getPackageName().equals(packageName)) {
  268. appList.remove(i);
  269. i--;
  270. }
  271. }
  272. }
  273. appListIndex = 0;
  274. currentString = "";
  275. }
  276. public boolean applicationExists(AppInfo app) {
  277. return appList.contains(app);
  278. }
  279. public void startActionHandler() {
  280. currentString = "";
  281. // Launch app here
  282. parent.onAppSelected(appList.get(appListIndex));
  283. }
  284. @Override
  285. public boolean onKeyDown(int keyCode, KeyEvent event) {
  286. String input = "";
  287. switch (keyCode) {
  288. case KeyEvent.KEYCODE_MENU:
  289. return false;
  290. case KeyEvent.KEYCODE_DPAD_DOWN:
  291. nextApp();
  292. currentString = "";
  293. return true;
  294. case KeyEvent.KEYCODE_DPAD_UP:
  295. prevApp();
  296. currentString = "";
  297. return true;
  298. case KeyEvent.KEYCODE_DPAD_CENTER:
  299. case KeyEvent.KEYCODE_ENTER:
  300. startActionHandler();
  301. return true;
  302. case KeyEvent.KEYCODE_SEARCH:
  303. startActionHandler();
  304. return true;
  305. case KeyEvent.KEYCODE_CALL:
  306. startActionHandler();
  307. return true;
  308. case KeyEvent.KEYCODE_BACK:
  309. if (backKeyTimeDown == -1) {
  310. backKeyTimeDown = System.currentTimeMillis();
  311. class QuitCommandWatcher implements Runnable {
  312. @Override
  313. public void run() {
  314. try {
  315. Thread.sleep(3000);
  316. if ((backKeyTimeDown > 0)
  317. && (System.currentTimeMillis() - backKeyTimeDown > 2500)) {
  318. parent.startActivity(parent.getSystemHomeIntent());
  319. parent.finish();
  320. }
  321. } catch (InterruptedException e) {
  322. e.printStackTrace();
  323. }
  324. }
  325. }
  326. new Thread(new QuitCommandWatcher()).start();
  327. }
  328. return true;
  329. case KeyEvent.KEYCODE_DEL:
  330. backspace();
  331. return true;
  332. default:
  333. KeyCharacterMap keyMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
  334. input = keyMap.getMatch(keyCode, alphabet) + "";
  335. }
  336. if (input.length() > 0) {
  337. currentString = currentString + input;
  338. jumpToFirstMatchingApp();
  339. }
  340. return false;
  341. }
  342. @Override
  343. public boolean onKeyUp(int keyCode, KeyEvent event) {
  344. switch (keyCode) {
  345. case KeyEvent.KEYCODE_BACK:
  346. backKeyTimeDown = -1;
  347. parent.switchToMainView();
  348. return true;
  349. }
  350. return false;
  351. }
  352. private void confirmEntry() {
  353. screenIsBeingTouched = false;
  354. int prevVal = currentValue;
  355. currentValue = evalMotion(lastX, lastY);
  356. // Do some correction if the user lifts up on deadspace
  357. if (currentValue == -1) {
  358. currentValue = prevVal;
  359. }
  360. // The user never got a number that wasn't deadspace,
  361. // so assume 5.
  362. if (currentValue == -1) {
  363. currentValue = 5;
  364. }
  365. currentCharacter = getCharacter(currentWheel, currentValue);
  366. if (currentCharacter.equals("<-")) {
  367. currentCharacter = "";
  368. backspace();
  369. } else if (currentCharacter.equals("TAP")) {
  370. if (lastTapTime + doubleTapWindow > System.currentTimeMillis()) {
  371. startActionHandler();
  372. return;
  373. } else {
  374. lastTapTime = System.currentTimeMillis();
  375. }
  376. } else {
  377. if (currentCharacter.equals("SPACE")) {
  378. currentString = currentString + " ";
  379. }
  380. /*
  381. * else if (currentCharacter.equals("MODE")) { // Do nothing }
  382. */
  383. else {
  384. currentString = currentString + currentCharacter;
  385. }
  386. parent.tts.speak(currentCharacter, 0, null);
  387. jumpToFirstMatchingApp();
  388. }
  389. invalidate();
  390. initiateMotion(lastX, lastY);
  391. }
  392. private void initiateMotion(double x, double y) {
  393. downX = x;
  394. downY = y;
  395. lastX = x;
  396. lastY = y;
  397. currentValue = -1;
  398. currentWheel = NONE;
  399. currentCharacter = "";
  400. }
  401. public boolean onHoverEvent(MotionEvent event) {
  402. return onTouchEvent(event);
  403. }
  404. @Override
  405. public boolean onTouchEvent(MotionEvent event) {
  406. boolean touchExploring = isTouchExplorationEnabled(accessibilityManager);
  407. int action = event.getAction();
  408. float x = event.getX();
  409. float y = event.getY();
  410. int secondFingerId = 1;
  411. if (touchExploring) {
  412. secondFingerId = 0;
  413. }
  414. // Treat the screen as a dpad
  415. if ((action == 261) || (touchExploring && action == MotionEvent.ACTION_DOWN)) {
  416. // 261 == ACTION_POINTER_2_DOWN - using number for Android 1.6
  417. // compat
  418. // Track the starting location of the second touch point.
  419. inDPadMode = false;
  420. gestureHadSecondPoint = true;
  421. screenIsBeingTouched = false;
  422. currentWheel = NONE;
  423. p2DownX = getX(event, secondFingerId);
  424. p2DownY = getY(event, secondFingerId);
  425. feedbackController.playVibration(R.array.pattern_app_chooser);
  426. invalidate();
  427. } else if ((action == 262) || (touchExploring && action == MotionEvent.ACTION_UP)) {
  428. // 262 == MotionEvent.ACTION_POINTER_2_UP - using number for Android
  429. // 1.6 compat
  430. // Second touch point has lifted, so process the gesture.
  431. float p2DeltaX = getX(event, secondFingerId) - p2DownX;
  432. float p2DeltaY = getY(event, secondFingerId) - p2DownY;
  433. if (Math.abs(p2DeltaX) > Math.abs(p2DeltaY)) {
  434. if (p2DeltaX < -200) {
  435. backspace();
  436. }
  437. } else {
  438. if (p2DeltaY < -100) {
  439. prevApp();
  440. } else if (p2DeltaY > 100) {
  441. nextApp();
  442. }
  443. }
  444. }
  445. if ((action == MotionEvent.ACTION_DOWN)
  446. || (action == MotionEventCompatUtils.ACTION_HOVER_ENTER)) {
  447. initiateMotion(x, y);
  448. return true;
  449. } else if ((action == MotionEvent.ACTION_UP)
  450. || (action == MotionEventCompatUtils.ACTION_HOVER_EXIT)) {
  451. if (gestureHadSecondPoint == false) {
  452. if (inDPadMode == false) {
  453. confirmEntry();
  454. } else {
  455. inDPadMode = false;
  456. }
  457. } else {
  458. // Full multi-touch gesture completed, so reset the state.
  459. gestureHadSecondPoint = false;
  460. }
  461. return true;
  462. } else {
  463. if (gestureHadSecondPoint == false) {
  464. screenIsBeingTouched = true;
  465. lastX = x;
  466. lastY = y;
  467. int prevVal = currentValue;
  468. currentValue = evalMotion(x, y);
  469. // Do nothing since we want a deadzone here;
  470. // restore the state to the previous value.
  471. if (currentValue == -1) {
  472. currentValue = prevVal;
  473. return true;
  474. }
  475. // There is a wheel that is active
  476. if (currentValue != 5) {
  477. if (currentWheel == NONE) {
  478. currentWheel = getWheel(currentValue);
  479. }
  480. currentCharacter = getCharacter(currentWheel, currentValue);
  481. } else {
  482. currentCharacter = "";
  483. }
  484. invalidate();
  485. if (prevVal != currentValue) {
  486. if (currentCharacter.equals("")) {
  487. parent.tts.playEarcon(parent.getString(R.string.earcon_tock), 0, null);
  488. } else {
  489. if (currentCharacter.equals(".")) {
  490. parent.tts.speak(parent.getString(R.string.period), 0, null);
  491. } else if (currentCharacter.equals("!")) {
  492. parent.tts.speak(parent.getString(R.string.exclamation_point), 0, null);
  493. } else if (currentCharacter.equals("?")) {
  494. parent.tts.speak(parent.getString(R.string.question_mark), 0, null);
  495. } else if (currentCharacter.equals(",")) {
  496. parent.tts.speak(parent.getString(R.string.comma), 0, null);
  497. } else if (currentCharacter.equals("<-")) {
  498. parent.tts.speak(parent.getString(R.string.backspace), 0, null);
  499. } else {
  500. parent.tts.speak(currentCharacter, 0, null);
  501. }
  502. }
  503. }
  504. feedbackController.playVibration(R.array.pattern_app_chooser);
  505. }
  506. }
  507. return true;
  508. }
  509. public int getWheel(int value) {
  510. switch (value) {
  511. case 1:
  512. return AE;
  513. case 2:
  514. return IM;
  515. case 3:
  516. return QU;
  517. case 4:
  518. return Y;
  519. case 5:
  520. return NONE;
  521. case 6:
  522. return Y;
  523. case 7:
  524. return QU;
  525. case 8:
  526. return IM;
  527. case 9:
  528. return AE;
  529. default:
  530. return NONE;
  531. }
  532. }
  533. public String getCharacter(int wheel, int value) {
  534. switch (wheel) {
  535. case AE:
  536. switch (value) {
  537. case 1:
  538. return "A";
  539. case 2:
  540. return "B";
  541. case 3:
  542. return "C";
  543. case 4:
  544. return "H";
  545. case 5:
  546. return "";
  547. case 6:
  548. return "D";
  549. case 7:
  550. return "G";
  551. case 8:
  552. return "F";
  553. case 9:
  554. return "E";
  555. default:
  556. return "";
  557. }
  558. case IM:
  559. switch (value) {
  560. case 1:
  561. return "P";
  562. case 2:
  563. return "I";
  564. case 3:
  565. return "J";
  566. case 4:
  567. return "O";
  568. case 5:
  569. return "";
  570. case 6:
  571. return "K";
  572. case 7:
  573. return "N";
  574. case 8:
  575. return "M";
  576. case 9:
  577. return "L";
  578. default:
  579. return "";
  580. }
  581. case QU:
  582. switch (value) {
  583. case 1:
  584. return "W";
  585. case 2:
  586. return "X";
  587. case 3:
  588. return "Q";
  589. case 4:
  590. return "V";
  591. case 5:
  592. return "";
  593. case 6:
  594. return "R";
  595. case 7:
  596. return "U";
  597. case 8:
  598. return "T";
  599. case 9:
  600. return "S";
  601. default:
  602. return "";
  603. }
  604. case Y:
  605. switch (value) {
  606. case 1:
  607. return ",";
  608. case 2:
  609. return "!";
  610. case 3:
  611. return "SPACE"; // return "MODE";
  612. case 4:
  613. return "<-";
  614. case 5:
  615. return "";
  616. case 6:
  617. return "Y";
  618. case 7:
  619. return ".";
  620. case 8:
  621. return "?";
  622. case 9:
  623. return "Z";
  624. default:
  625. return "";
  626. }
  627. default:
  628. return "TAP";
  629. }
  630. }
  631. public int evalMotion(double x, double y) {
  632. float rTolerance = 25;
  633. double thetaTolerance = (Math.PI / 16);
  634. double r = Math.sqrt(((downX - x) * (downX - x)) + ((downY - y) * (downY - y)));
  635. if (r < rTolerance) {
  636. return 5;
  637. }
  638. double theta = Math.atan2(downY - y, downX - x);
  639. if (Math.abs(theta - left) < thetaTolerance) {
  640. return 4;
  641. } else if (Math.abs(theta - upleft) < thetaTolerance) {
  642. return 1;
  643. } else if (Math.abs(theta - up) < thetaTolerance) {
  644. return 2;
  645. } else if (Math.abs(theta - upright) < thetaTolerance) {
  646. return 3;
  647. } else if (Math.abs(theta - downright) < thetaTolerance) {
  648. return 9;
  649. } else if (Math.abs(theta - down) < thetaTolerance) {
  650. return 8;
  651. } else if (Math.abs(theta - downleft) < thetaTolerance) {
  652. return 7;
  653. } else if ((theta > right - thetaTolerance) || (theta < rightWrap + thetaTolerance)) {
  654. return 6;
  655. }
  656. // Off by more than the threshold, so it doesn't count
  657. return -1;
  658. }
  659. @Override
  660. public void onDraw(Canvas canvas) {
  661. Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
  662. paint.setColor(Color.WHITE);
  663. paint.setTextAlign(Paint.Align.CENTER);
  664. paint.setTypeface(Typeface.DEFAULT_BOLD);
  665. int x = 5;
  666. int y = 50;
  667. paint.setTextSize(50);
  668. paint.setTextAlign(Paint.Align.LEFT);
  669. y -= paint.ascent() / 2;
  670. canvas.drawText(currentString, x, y, paint);
  671. String currentTitle = appList.get(appListIndex).getTitle();
  672. if (currentTitle.length() > 0) {
  673. y = 140;
  674. paint.setTextSize(45);
  675. String[] lines = currentTitle.split(" ");
  676. for (int i = 0; i < lines.length; i++) {
  677. canvas.drawText(lines[i], x, y + (i * 45), paint);
  678. }
  679. }
  680. paint.setTextSize(50);
  681. if (!screenIsBeingTouched) {
  682. x = 5;
  683. y = getHeight() - 40;
  684. paint.setTextSize(20);
  685. paint.setTextAlign(Paint.Align.LEFT);
  686. y -= paint.ascent() / 2;
  687. canvas.drawText("Scroll apps with trackball.", x, y, paint);
  688. x = 5;
  689. y = getHeight() - 20;
  690. paint.setTextSize(20);
  691. paint.setTextAlign(Paint.Align.LEFT);
  692. y -= paint.ascent() / 2;
  693. canvas.drawText("Press CALL to launch app.", x, y, paint);
  694. } else {
  695. int offset = 90;
  696. int x1 = (int) downX - offset;
  697. int y1 = (int) downY - offset;
  698. int x2 = (int) downX;
  699. int y2 = (int) downY - offset;
  700. int x3 = (int) downX + offset;
  701. int y3 = (int) downY - offset;
  702. int x4 = (int) downX - offset;
  703. int y4 = (int) downY;
  704. int x6 = (int) downX + offset;
  705. int y6 = (int) downY;
  706. int x7 = (int) downX - offset;
  707. int y7 = (int) downY + offset;
  708. int x8 = (int) downX;
  709. int y8 = (int) downY + offset;
  710. int x9 = (int) downX + offset;
  711. int y9 = (int) downY + offset;
  712. y1 -= paint.ascent() / 2;
  713. y2 -= paint.ascent() / 2;
  714. y3 -= paint.ascent() / 2;
  715. y4 -= paint.ascent() / 2;
  716. y6 -= paint.ascent() / 2;
  717. y7 -= paint.ascent() / 2;
  718. y8 -= paint.ascent() / 2;
  719. y9 -= paint.ascent() / 2;
  720. switch (currentWheel) {
  721. case AE:
  722. paint.setColor(Color.RED);
  723. drawCharacter("A", x1, y1, canvas, paint, currentCharacter.equals("A"));
  724. drawCharacter("B", x2, y2, canvas, paint, currentCharacter.equals("B"));
  725. drawCharacter("C", x3, y3, canvas, paint, currentCharacter.equals("C"));
  726. drawCharacter("H", x4, y4, canvas, paint, currentCharacter.equals("H"));
  727. drawCharacter("D", x6, y6, canvas, paint, currentCharacter.equals("D"));
  728. drawCharacter("G", x7, y7, canvas, paint, currentCharacter.equals("G"));
  729. drawCharacter("F", x8, y8, canvas, paint, currentCharacter.equals("F"));
  730. drawCharacter("E", x9, y9, canvas, paint, currentCharacter.equals("E"));
  731. break;
  732. case IM:
  733. paint.setColor(Color.BLUE);
  734. drawCharacter("P", x1, y1, canvas, paint, currentCharacter.equals("P"));
  735. drawCharacter("I", x2, y2, canvas, paint, currentCharacter.equals("I"));
  736. drawCharacter("J", x3, y3, canvas, paint, currentCharacter.equals("J"));
  737. drawCharacter("O", x4, y4, canvas, paint, currentCharacter.equals("O"));
  738. drawCharacter("K", x6, y6, canvas, paint, currentCharacter.equals("K"));
  739. drawCharacter("N", x7, y7, canvas, paint, currentCharacter.equals("N"));
  740. drawCharacter("M", x8, y8, canvas, paint, currentCharacter.equals("M"));
  741. drawCharacter("L", x9, y9, canvas, paint, currentCharacter.equals("L"));
  742. break;
  743. case QU:
  744. paint.setColor(Color.GREEN);
  745. drawCharacter("W", x1, y1, canvas, paint, currentCharacter.equals("W"));
  746. drawCharacter("X", x2, y2, canvas, paint, currentCharacter.equals("X"));
  747. drawCharacter("Q", x3, y3, canvas, paint, currentCharacter.equals("Q"));
  748. drawCharacter("V", x4, y4, canvas, paint, currentCharacter.equals("V"));
  749. drawCharacter("R", x6, y6, canvas, paint, currentCharacter.equals("R"));
  750. drawCharacter("U", x7, y7, canvas, paint, currentCharacter.equals("U"));
  751. drawCharacter("T", x8, y8, canvas, paint, currentCharacter.equals("T"));
  752. drawCharacter("S", x9, y9, canvas, paint, currentCharacter.equals("S"));
  753. break;
  754. case Y:
  755. paint.setColor(Color.YELLOW);
  756. drawCharacter(",", x1, y1, canvas, paint, currentCharacter.equals(","));
  757. drawCharacter("!", x2, y2, canvas, paint, currentCharacter.equals("!"));
  758. drawCharacter("SPACE", x3, y3, canvas, paint, currentCharacter.equals("SPACE"));
  759. drawCharacter("<-", x4, y4, canvas, paint, currentCharacter.equals("<-"));
  760. drawCharacter("Y", x6, y6, canvas, paint, currentCharacter.equals("Y"));
  761. drawCharacter(".", x7, y7, canvas, paint, currentCharacter.equals("."));
  762. drawCharacter("?", x8, y8, canvas, paint, currentCharacter.equals("?"));
  763. drawCharacter("Z", x9, y9, canvas, paint, currentCharacter.equals("Z"));
  764. break;
  765. default:
  766. paint.setColor(Color.RED);
  767. canvas.drawText("A", x1, y1, paint);
  768. canvas.drawText("E", x9, y9, paint);
  769. paint.setColor(Color.BLUE);
  770. canvas.drawText("I", x2, y2, paint);
  771. canvas.drawText("M", x8, y8, paint);
  772. paint.setColor(Color.GREEN);
  773. canvas.drawText("Q", x3, y3, paint);
  774. canvas.drawText("U", x7, y7, paint);
  775. paint.setColor(Color.YELLOW);
  776. canvas.drawText("Y", x6, y6, paint);
  777. canvas.drawText("<-", x4, y4, paint);
  778. break;
  779. }
  780. }
  781. }
  782. private void drawCharacter(
  783. String character, int x, int y, Canvas canvas, Paint paint, boolean isSelected) {
  784. int regSize = 50;
  785. int selectedSize = regSize * 2;
  786. if (isSelected) {
  787. paint.setTextSize(selectedSize);
  788. } else {
  789. paint.setTextSize(regSize);
  790. }
  791. canvas.drawText(character, x, y, paint);
  792. }
  793. public void backspace() {
  794. String deletedCharacter = "";
  795. if (currentString.length() > 0) {
  796. deletedCharacter = "" + currentString.charAt(currentString.length() - 1);
  797. currentString = currentString.substring(0, currentString.length() - 1);
  798. }
  799. if (!deletedCharacter.equals("")) {
  800. // parent.tts.speak(deletedCharacter, 0, new String[]
  801. // {TTSParams.VOICE_ROBOT.toString()});
  802. parent.tts.speak(parent.getString(R.string.deleted, deletedCharacter), 0, null);
  803. } else {
  804. parent.tts.playEarcon(parent.getString(R.string.earcon_tock), 0, null);
  805. parent.tts.playEarcon(parent.getString(R.string.earcon_tock), 1, null);
  806. }
  807. if (currentString.length() > 0) {
  808. jumpToFirstMatchingApp();
  809. }
  810. invalidate();
  811. }
  812. @Override
  813. protected void onWindowVisibilityChanged(int visibility) {
  814. if (shakeDetector != null) {
  815. shakeDetector.shutdown();
  816. shakeDetector = null;
  817. }
  818. if (visibility == View.VISIBLE) {
  819. shakeDetector = new ShakeDetector(parent, new ShakeListener() {
  820. @Override
  821. public void onShakeDetected() {
  822. if (useShake) {
  823. backspace();
  824. }
  825. }
  826. });
  827. }
  828. super.onWindowVisibilityChanged(visibility);
  829. }
  830. public void uninstallCurrentApp() {
  831. String targetPackageName = appList.get(appListIndex).getPackageName();
  832. Intent i = new Intent();
  833. i.setAction("android.intent.action.DELETE");
  834. i.setData(Uri.parse("package:" + targetPackageName));
  835. parent.startActivity(i);
  836. }
  837. public void showCurrentAppInfo() {
  838. String targetPackageName = appList.get(appListIndex).getPackageName();
  839. Intent i = new Intent();
  840. try {
  841. // android.settings.APPLICATION_DETAILS_SETTINGS is the correct, SDK
  842. // blessed way of doing this - but it is only available starting in
  843. // GINGERBREAD.
  844. i.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
  845. i.setData(Uri.parse("package:" + targetPackageName));
  846. parent.startActivity(i);
  847. } catch (ActivityNotFoundException e){
  848. try {
  849. // If it isn't possible to use android.settings.APPLICATION_DETAILS_SETTINGS,
  850. // try it again with the "pkg" magic key. See ManageApplications.APP_PKG_NAME in:
  851. // src/com/android/settings/ManageApplications.java
  852. // under http://android.git.kernel.org/?p=platform/packages/apps/Settings.git
  853. i.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
  854. i.putExtra("pkg", targetPackageName);
  855. parent.startActivity(i);
  856. } catch (ActivityNotFoundException e2) {
  857. }
  858. }
  859. }
  860. public String getCurrentAppTitle() {
  861. return appList.get(appListIndex).getTitle();
  862. }
  863. }