/talkingdialer/src/com/google/marvin/talkingdialer/ContactsView.java

http://eyes-free.googlecode.com/ · Java · 982 lines · 815 code · 98 blank · 69 comment · 147 complexity · ada9f2e180642291e185e31b9df437e5 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.talkingdialer;
  17. import android.content.Context;
  18. import android.content.Intent;
  19. import android.database.Cursor;
  20. import android.database.CursorIndexOutOfBoundsException;
  21. import android.database.StaleDataException;
  22. import android.graphics.Canvas;
  23. import android.graphics.Color;
  24. import android.graphics.Paint;
  25. import android.graphics.Typeface;
  26. import android.net.Uri;
  27. import android.os.Vibrator;
  28. import android.provider.ContactsContract.CommonDataKinds.Phone;
  29. import android.speech.tts.TextToSpeech;
  30. import android.text.TextUtils;
  31. import android.util.Log;
  32. import android.view.KeyEvent;
  33. import android.view.MotionEvent;
  34. import android.widget.TextView;
  35. import com.google.marvin.talkingdialer.ShakeDetector.ShakeListener;
  36. import com.googlecode.eyesfree.utils.compat.MotionEventCompatUtils;
  37. import java.lang.reflect.InvocationTargetException;
  38. import java.lang.reflect.Method;
  39. import java.util.ArrayList;
  40. /**
  41. * Allows the user to select the contact they wish to call by moving through
  42. * their phonebook. The contact name being spoken is actually the contact's
  43. * ringtone. By setting the ringtone to an audio file that is the same as the
  44. * contact's name, the user gets a talking caller ID feature automatically
  45. * without needing any additional code.
  46. *
  47. * @author clchen@google.com (Charles L. Chen)
  48. */
  49. public class ContactsView extends TextView {
  50. // BEGIN OF WORKAROUND FOR DONUT COMPATIBILITY
  51. private static Method MotionEvent_getX;
  52. private static Method MotionEvent_getY;
  53. static {
  54. initCompatibility();
  55. }
  56. private static void initCompatibility() {
  57. try {
  58. MotionEvent_getX = MotionEvent.class.getMethod("getX", new Class[] { Integer.TYPE });
  59. MotionEvent_getY = MotionEvent.class.getMethod("getY", new Class[] { Integer.TYPE });
  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. // END OF WORKAROUND FOR DONUT COMPATIBILITY
  96. private static final long[] PATTERN = { 0, 1, 40, 41 };
  97. private static final int NAME = 0;
  98. private static final int NUMBER = 1;
  99. private static final int TYPE = 2;
  100. private static final int PERSON_ID = 3;
  101. private static final int AE = 0;
  102. private static final int IM = 1;
  103. private static final int QU = 2;
  104. private static final int Y = 4;
  105. private static final int NONE = 5;
  106. private static final int LONG_PRESS_THRESHOLD = 2000;
  107. private final double left = 0;
  108. private final double upleft = Math.PI * .25;
  109. private final double up = Math.PI * .5;
  110. private final double upright = Math.PI * .75;
  111. private final double downright = -Math.PI * .75;
  112. private final double down = -Math.PI * .5;
  113. private final double downleft = -Math.PI * .25;
  114. private final double right = Math.PI;
  115. private final double rightWrap = -Math.PI;
  116. // An array specifying which columns to return.
  117. private static final String[] PROJECTION = new String[] {
  118. Phone.DISPLAY_NAME, Phone.NUMBER, Phone.TYPE, Phone.RAW_CONTACT_ID };
  119. private SlideDial parent;
  120. private Cursor managedCursor;
  121. private FilterableContactsList filteredContacts;
  122. private boolean confirmed;
  123. private double downX;
  124. private double downY;
  125. private double lastX;
  126. private double lastY;
  127. private float p2DownX;
  128. private float p2DownY;
  129. private int currentValue;
  130. private boolean screenIsBeingTouched;
  131. private Vibrator vibe;
  132. private int currentWheel;
  133. private String currentCharacter;
  134. private String currentString;
  135. private String currentContact;
  136. private int trackballTimeout = 500;
  137. private boolean trackballEnabled = true;
  138. private ShakeDetector shakeDetector;
  139. private LongPressDetector longPress = null;
  140. private boolean inDPadMode = false;
  141. public ContactsView(Context context) {
  142. super(context);
  143. parent = ((SlideDial) context);
  144. vibe = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
  145. // Get the base URI for People table in Contacts content provider.
  146. // ie. content://contacts/people/
  147. Uri mContacts = Phone.CONTENT_URI;
  148. // Best way to retrieve a query; returns a managed query.
  149. managedCursor = parent.managedQuery(mContacts, PROJECTION, // Which
  150. // columns
  151. // to return.
  152. null, // WHERE clause--we won't specify.
  153. null, // no selection args
  154. Phone.DISPLAY_NAME + " ASC"); // Order-by clause.
  155. boolean moveSucceeded = managedCursor.moveToFirst();
  156. ArrayList<String> contactNames = new ArrayList<String>();
  157. while (moveSucceeded) {
  158. contactNames.add(managedCursor.getString(NAME));
  159. moveSucceeded = managedCursor.moveToNext();
  160. }
  161. filteredContacts = new FilterableContactsList(contactNames);
  162. currentString = "";
  163. currentContact = "";
  164. setClickable(true);
  165. setFocusable(true);
  166. setFocusableInTouchMode(true);
  167. requestFocus();
  168. shakeDetector = new ShakeDetector(context, new ShakeListener() {
  169. @Override
  170. public void onShakeDetected() {
  171. backspace();
  172. }
  173. });
  174. }
  175. public void shutdown() {
  176. shakeDetector.shutdown();
  177. }
  178. @Override
  179. public boolean onTrackballEvent(MotionEvent event) {
  180. int action = event.getAction();
  181. if (trackballEnabled == false) {
  182. return true;
  183. }
  184. Log.i("Motion", Float.toString(event.getY()));
  185. if (event.getY() > .16) {
  186. trackballEnabled = false;
  187. (new Thread(new trackballTimeout())).start();
  188. nextContact();
  189. }
  190. if (event.getY() < -.16) {
  191. trackballEnabled = false;
  192. (new Thread(new trackballTimeout())).start();
  193. prevContact();
  194. }
  195. if (action == MotionEvent.ACTION_DOWN) {
  196. if (longPress == null) {
  197. longPress = new LongPressDetector();
  198. }
  199. postDelayed(longPress, LONG_PRESS_THRESHOLD);
  200. return true;
  201. } else if (action == MotionEvent.ACTION_UP) {
  202. if (longPress != null) {
  203. removeCallbacks(longPress);
  204. }
  205. return true;
  206. }
  207. return true;
  208. }
  209. class trackballTimeout implements Runnable {
  210. @Override
  211. public void run() {
  212. try {
  213. Thread.sleep(trackballTimeout);
  214. trackballEnabled = true;
  215. } catch (InterruptedException e) {
  216. e.printStackTrace();
  217. }
  218. }
  219. }
  220. private void resetContactList() {
  221. currentString = "";
  222. if (managedCursor.getCount() > 0) {
  223. managedCursor.moveToFirst();
  224. if (!managedCursor.isBeforeFirst() && !managedCursor.isAfterLast()) {
  225. // Keep going if the entry doesn't have a name
  226. String name = managedCursor.getString(NAME);
  227. if (name == null) {
  228. nextContact();
  229. return;
  230. }
  231. }
  232. }
  233. }
  234. public void nextContact() {
  235. currentString = "";
  236. // Make sure we don't try to act on an empty table
  237. if (managedCursor.getCount() > 0) {
  238. boolean moveSucceeded = managedCursor.moveToNext();
  239. if (!moveSucceeded) {
  240. managedCursor.moveToFirst();
  241. }
  242. // Keep going if the entry doesn't have a name
  243. String name = managedCursor.getString(NAME);
  244. if (name == null) {
  245. nextContact();
  246. return;
  247. }
  248. vibe.vibrate(PATTERN, -1);
  249. speakCurrentContact(true);
  250. }
  251. }
  252. public void prevContact() {
  253. currentString = "";
  254. // Make sure we don't try to act on an empty table
  255. if (managedCursor.getCount() > 0) {
  256. boolean moveSucceeded = managedCursor.moveToPrevious();
  257. if (!moveSucceeded) {
  258. managedCursor.moveToLast();
  259. }
  260. // Keep going if the entry doesn't have a name
  261. String name = managedCursor.getString(NAME);
  262. if (name == null) {
  263. prevContact();
  264. return;
  265. }
  266. vibe.vibrate(PATTERN, -1);
  267. speakCurrentContact(true);
  268. }
  269. }
  270. private void jumpToFirstFilteredResult() {
  271. ContactEntry entry = filteredContacts.next();
  272. if (entry == null) {
  273. parent.tts.playEarcon(parent.getString(R.string.earcon_tock), 0, null);
  274. if (currentString.length() > 0) {
  275. currentString = currentString.substring(0, currentString.length() - 1);
  276. if (currentString.length() > 0) {
  277. filteredContacts.filter(currentString);
  278. jumpToFirstFilteredResult();
  279. } else {
  280. parent.tts.speak(parent.getString(R.string.no_contacts_found), 0, null);
  281. }
  282. }
  283. return;
  284. }
  285. managedCursor.moveToPosition(entry.index);
  286. speakCurrentContact(true);
  287. }
  288. /**
  289. * @return a string representing the currently selected contact
  290. */
  291. private String getCurrentContact() {
  292. final StringBuilder contact = new StringBuilder();
  293. String name = null;
  294. try {
  295. name = managedCursor.getString(NAME);
  296. } catch (CursorIndexOutOfBoundsException e) {
  297. e.printStackTrace();
  298. } catch (StaleDataException e) {
  299. e.printStackTrace();
  300. }
  301. if (TextUtils.isEmpty(name)) {
  302. return null;
  303. } else {
  304. contact.append(name);
  305. }
  306. final int phoneType = Integer.parseInt(managedCursor.getString(TYPE));
  307. int typeRes = -1;
  308. switch (phoneType) {
  309. case Phone.TYPE_HOME:
  310. typeRes = R.string.home;
  311. break;
  312. case Phone.TYPE_MOBILE:
  313. typeRes = R.string.cell;
  314. break;
  315. case Phone.TYPE_WORK:
  316. typeRes = R.string.work;
  317. break;
  318. }
  319. if (typeRes >= 0) {
  320. contact.append(' ');
  321. contact.append(getContext().getString(typeRes));
  322. }
  323. return contact.toString();
  324. }
  325. /**
  326. * Speaks the currently selected contact and sets the internal current
  327. * contact.
  328. *
  329. * @param interrupt Set to {@code true} to flush queued speech and speak
  330. * immediately.
  331. */
  332. private void speakCurrentContact(boolean interrupt) {
  333. final String contact = getCurrentContact();
  334. if (TextUtils.isEmpty(contact)) {
  335. return;
  336. }
  337. final int mode = interrupt ? TextToSpeech.QUEUE_FLUSH : TextToSpeech.QUEUE_ADD;
  338. parent.tts.speak(contact, mode, null);
  339. currentContact = contact;
  340. invalidate();
  341. }
  342. public void dialActionHandler() {
  343. if (!confirmed) {
  344. if (!TextUtils.isEmpty(currentContact)) {
  345. if (parent.contactsPickerMode) {
  346. parent.tts.speak(
  347. parent.getString(R.string.you_have_selected, currentContact), 0, null);
  348. } else {
  349. parent.tts.speak(
  350. parent.getString(R.string.you_are_about_to_dial, currentContact), 0,
  351. null);
  352. }
  353. confirmed = true;
  354. } else {
  355. // If the user attempts to dial with no contact selected, switch
  356. // to dialing view.
  357. parent.switchToDialingView();
  358. }
  359. } else {
  360. parent.returnResults(managedCursor.getString(NUMBER), currentContact);
  361. }
  362. }
  363. @Override
  364. public boolean onKeyDown(int keyCode, KeyEvent event) {
  365. final char keyLabel = event.getDisplayLabel();
  366. if (Character.isLetterOrDigit(keyLabel)) {
  367. currentString = currentString + keyLabel;
  368. filteredContacts.filter(currentString);
  369. jumpToFirstFilteredResult();
  370. return true;
  371. }
  372. switch (keyCode) {
  373. case KeyEvent.KEYCODE_DPAD_DOWN:
  374. nextContact();
  375. currentString = "";
  376. return true;
  377. case KeyEvent.KEYCODE_DPAD_UP:
  378. prevContact();
  379. currentString = "";
  380. return true;
  381. case KeyEvent.KEYCODE_ENTER:
  382. case KeyEvent.KEYCODE_SEARCH:
  383. case KeyEvent.KEYCODE_CALL:
  384. dialActionHandler();
  385. return true;
  386. case KeyEvent.KEYCODE_MENU:
  387. parent.switchToDialingView();
  388. return true;
  389. case KeyEvent.KEYCODE_DEL:
  390. backspace();
  391. return true;
  392. }
  393. confirmed = false;
  394. return false;
  395. }
  396. private void confirmEntry() {
  397. screenIsBeingTouched = false;
  398. int prevVal = currentValue;
  399. currentValue = evalMotion(lastX, lastY);
  400. // Do some correction if the user lifts up on deadspace
  401. if (currentValue == -1) {
  402. currentValue = prevVal;
  403. }
  404. // The user never got a number that wasn't deadspace,
  405. // so assume 5.
  406. if (currentValue == -1) {
  407. currentValue = 5;
  408. }
  409. currentCharacter = getCharacter(currentWheel, currentValue);
  410. if (currentCharacter.equals("<-")) {
  411. currentContact = "";
  412. initiateMotion(lastX, lastY);
  413. backspace();
  414. return;
  415. } else {
  416. currentString = currentString + currentCharacter;
  417. }
  418. parent.tts.speak(currentCharacter, 0, null);
  419. currentContact = "";
  420. invalidate();
  421. initiateMotion(lastX, lastY);
  422. currentString = currentString + currentCharacter;
  423. filteredContacts.filter(currentString);
  424. jumpToFirstFilteredResult();
  425. }
  426. private void initiateMotion(double x, double y) {
  427. downX = x;
  428. downY = y;
  429. lastX = x;
  430. lastY = y;
  431. currentValue = -1;
  432. currentWheel = NONE;
  433. currentCharacter = "";
  434. }
  435. public boolean onHoverEvent(MotionEvent event) {
  436. return onTouchEvent(event);
  437. }
  438. @Override
  439. public boolean onTouchEvent(MotionEvent event) {
  440. int action = event.getAction();
  441. float x = event.getX();
  442. float y = event.getY();
  443. // Treat the screen as a dpad
  444. if (action == 261) { // 261 == ACTION_POINTER_2_DOWN - using number for
  445. // Android 1.6 compat
  446. removeCallbacks(longPress);
  447. inDPadMode = true;
  448. screenIsBeingTouched = false;
  449. p2DownX = getX(event, 1);
  450. p2DownY = getY(event, 1);
  451. vibe.vibrate(PATTERN, -1);
  452. invalidate();
  453. } else if (action == 262) { // 262 == MotionEvent.ACTION_POINTER_2_UP -
  454. // using number for Android 1.6 compat
  455. float p2DeltaX = getX(event, 1) - p2DownX;
  456. float p2DeltaY = getY(event, 1) - p2DownY;
  457. if (Math.abs(p2DeltaX) > Math.abs(p2DeltaY)) {
  458. if (p2DeltaX < -200) {
  459. backspace();
  460. }
  461. } else {
  462. if (p2DeltaY < -100) {
  463. prevContact();
  464. } else if (p2DeltaY > 100) {
  465. nextContact();
  466. }
  467. }
  468. }
  469. if ((action == MotionEvent.ACTION_UP)
  470. || (action == MotionEventCompatUtils.ACTION_HOVER_EXIT)) {
  471. if (x > 650) {
  472. nextContact();
  473. return true;
  474. }
  475. if (x < 100) {
  476. prevContact();
  477. return true;
  478. }
  479. } else {
  480. if (x > 650) {
  481. return true;
  482. }
  483. if (x < 100) {
  484. return true;
  485. }
  486. }
  487. if ((action == MotionEvent.ACTION_DOWN)
  488. || (action == MotionEventCompatUtils.ACTION_HOVER_ENTER)) {
  489. initiateMotion(x, y);
  490. if (longPress == null) {
  491. longPress = new LongPressDetector();
  492. }
  493. postDelayed(longPress, LONG_PRESS_THRESHOLD);
  494. return true;
  495. } else if ((action == MotionEvent.ACTION_UP)
  496. || (action == MotionEventCompatUtils.ACTION_HOVER_EXIT)) {
  497. if (inDPadMode == false) {
  498. confirmEntry();
  499. } else {
  500. inDPadMode = false;
  501. }
  502. if (longPress != null) {
  503. removeCallbacks(longPress);
  504. }
  505. return true;
  506. } else {
  507. if (!inDPadMode) {
  508. screenIsBeingTouched = true;
  509. lastX = x;
  510. lastY = y;
  511. int prevVal = currentValue;
  512. currentValue = evalMotion(x, y);
  513. // Do nothing since we want a deadzone here;
  514. // restore the state to the previous value.
  515. if (currentValue == -1) {
  516. currentValue = prevVal;
  517. return true;
  518. }
  519. // There is a wheel that is active
  520. if (currentValue != 5) {
  521. if (currentWheel == NONE) {
  522. currentWheel = getWheel(currentValue);
  523. // User has entered a wheel so invalidate the long press
  524. // callback.
  525. if (longPress != null) {
  526. removeCallbacks(longPress);
  527. }
  528. }
  529. currentCharacter = getCharacter(currentWheel, currentValue);
  530. } else {
  531. currentCharacter = "";
  532. }
  533. invalidate();
  534. if (prevVal != currentValue) {
  535. if (currentCharacter.equals("")) {
  536. parent.tts.playEarcon(parent.getString(R.string.earcon_tock), 0, null);
  537. } else {
  538. if (currentCharacter.equals(".")) {
  539. parent.tts.speak(parent.getString(R.string.period), 0, null);
  540. } else if (currentCharacter.equals("!")) {
  541. parent.tts.speak(parent.getString(R.string.exclamation_point), 0, null);
  542. } else if (currentCharacter.equals("?")) {
  543. parent.tts.speak(parent.getString(R.string.question_mark), 0, null);
  544. } else if (currentCharacter.equals(",")) {
  545. parent.tts.speak(parent.getString(R.string.comma), 0, null);
  546. } else if (currentCharacter.equals("<-")) {
  547. parent.tts.speak(parent.getString(R.string.backspace), 0, null);
  548. } else {
  549. parent.tts.speak(currentCharacter, 0, null);
  550. }
  551. }
  552. }
  553. vibe.vibrate(PATTERN, -1);
  554. }
  555. }
  556. return true;
  557. }
  558. public int getWheel(int value) {
  559. switch (value) {
  560. case 1:
  561. return AE;
  562. case 2:
  563. return IM;
  564. case 3:
  565. return QU;
  566. case 4:
  567. return Y;
  568. case 5:
  569. return NONE;
  570. case 6:
  571. return Y;
  572. case 7:
  573. return QU;
  574. case 8:
  575. return IM;
  576. case 9:
  577. return AE;
  578. default:
  579. return NONE;
  580. }
  581. }
  582. public String getCharacter(int wheel, int value) {
  583. switch (wheel) {
  584. case AE:
  585. switch (value) {
  586. case 1:
  587. return "A";
  588. case 2:
  589. return "B";
  590. case 3:
  591. return "C";
  592. case 4:
  593. return "H";
  594. case 5:
  595. return "";
  596. case 6:
  597. return "D";
  598. case 7:
  599. return "G";
  600. case 8:
  601. return "F";
  602. case 9:
  603. return "E";
  604. default:
  605. return "";
  606. }
  607. case IM:
  608. switch (value) {
  609. case 1:
  610. return "P";
  611. case 2:
  612. return "I";
  613. case 3:
  614. return "J";
  615. case 4:
  616. return "O";
  617. case 5:
  618. return "";
  619. case 6:
  620. return "K";
  621. case 7:
  622. return "N";
  623. case 8:
  624. return "M";
  625. case 9:
  626. return "L";
  627. default:
  628. return "";
  629. }
  630. case QU:
  631. switch (value) {
  632. case 1:
  633. return "W";
  634. case 2:
  635. return "X";
  636. case 3:
  637. return "Q";
  638. case 4:
  639. return "V";
  640. case 5:
  641. return "";
  642. case 6:
  643. return "R";
  644. case 7:
  645. return "U";
  646. case 8:
  647. return "T";
  648. case 9:
  649. return "S";
  650. default:
  651. return "";
  652. }
  653. case Y:
  654. switch (value) {
  655. case 1:
  656. return ",";
  657. case 2:
  658. return "!";
  659. case 3:
  660. return ""; // return "MODE";
  661. case 4:
  662. return "<-";
  663. case 5:
  664. return "";
  665. case 6:
  666. return "Y";
  667. case 7:
  668. return ".";
  669. case 8:
  670. return "?";
  671. case 9:
  672. return "Z";
  673. default:
  674. return "";
  675. }
  676. default:
  677. return "";
  678. }
  679. }
  680. public int evalMotion(double x, double y) {
  681. float rTolerance = 25;
  682. double thetaTolerance = (Math.PI / 16);
  683. double r = Math.sqrt(((downX - x) * (downX - x)) + ((downY - y) * (downY - y)));
  684. if (r < rTolerance) {
  685. return 5;
  686. }
  687. double theta = Math.atan2(downY - y, downX - x);
  688. if (Math.abs(theta - left) < thetaTolerance) {
  689. return 4;
  690. } else if (Math.abs(theta - upleft) < thetaTolerance) {
  691. return 1;
  692. } else if (Math.abs(theta - up) < thetaTolerance) {
  693. return 2;
  694. } else if (Math.abs(theta - upright) < thetaTolerance) {
  695. return 3;
  696. } else if (Math.abs(theta - downright) < thetaTolerance) {
  697. return 9;
  698. } else if (Math.abs(theta - down) < thetaTolerance) {
  699. return 8;
  700. } else if (Math.abs(theta - downleft) < thetaTolerance) {
  701. return 7;
  702. } else if ((theta > right - thetaTolerance) || (theta < rightWrap + thetaTolerance)) {
  703. return 6;
  704. }
  705. // Off by more than the threshold, so it doesn't count
  706. return -1;
  707. }
  708. @Override
  709. public void onDraw(Canvas canvas) {
  710. Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
  711. paint.setColor(Color.WHITE);
  712. paint.setTextAlign(Paint.Align.CENTER);
  713. paint.setTypeface(Typeface.DEFAULT_BOLD);
  714. int x = 5;
  715. int y = 50;
  716. paint.setTextSize(50);
  717. paint.setTextAlign(Paint.Align.LEFT);
  718. y -= paint.ascent() / 2;
  719. canvas.drawText(currentString, x, y, paint);
  720. if (currentContact.length() > 0) {
  721. y = 140;
  722. paint.setTextSize(45);
  723. String[] lines = currentContact.split(" ");
  724. for (int i = 0; i < lines.length; i++) {
  725. canvas.drawText(lines[i], x, y + (i * 45), paint);
  726. }
  727. }
  728. paint.setTextSize(50);
  729. if (!screenIsBeingTouched) {
  730. x = 5;
  731. y = getHeight() - 260;
  732. paint.setTextSize(20);
  733. paint.setTextAlign(Paint.Align.LEFT);
  734. y -= paint.ascent() / 2;
  735. canvas.drawText("Press MENU for dialing mode.", x, y, paint);
  736. x = 5;
  737. y = getHeight() - 240;
  738. paint.setTextSize(20);
  739. paint.setTextAlign(Paint.Align.LEFT);
  740. y -= paint.ascent() / 2;
  741. canvas.drawText("Scroll contacts with trackball.", x, y, paint);
  742. x = 5;
  743. y = getHeight() - 220;
  744. paint.setTextSize(20);
  745. paint.setTextAlign(Paint.Align.LEFT);
  746. y -= paint.ascent() / 2;
  747. canvas.drawText("Press CALL twice to confirm.", x, y, paint);
  748. } else {
  749. int offset = 90;
  750. int x1 = (int) downX - offset;
  751. int y1 = (int) downY - offset;
  752. int x2 = (int) downX;
  753. int y2 = (int) downY - offset;
  754. int x3 = (int) downX + offset;
  755. int y3 = (int) downY - offset;
  756. int x4 = (int) downX - offset;
  757. int y4 = (int) downY;
  758. int x6 = (int) downX + offset;
  759. int y6 = (int) downY;
  760. int x7 = (int) downX - offset;
  761. int y7 = (int) downY + offset;
  762. int x8 = (int) downX;
  763. int y8 = (int) downY + offset;
  764. int x9 = (int) downX + offset;
  765. int y9 = (int) downY + offset;
  766. y1 -= paint.ascent() / 2;
  767. y2 -= paint.ascent() / 2;
  768. y3 -= paint.ascent() / 2;
  769. y4 -= paint.ascent() / 2;
  770. y6 -= paint.ascent() / 2;
  771. y7 -= paint.ascent() / 2;
  772. y8 -= paint.ascent() / 2;
  773. y9 -= paint.ascent() / 2;
  774. switch (currentWheel) {
  775. case AE:
  776. paint.setColor(Color.RED);
  777. drawCharacter("A", x1, y1, canvas, paint, currentCharacter.equals("A"));
  778. drawCharacter("B", x2, y2, canvas, paint, currentCharacter.equals("B"));
  779. drawCharacter("C", x3, y3, canvas, paint, currentCharacter.equals("C"));
  780. drawCharacter("H", x4, y4, canvas, paint, currentCharacter.equals("H"));
  781. drawCharacter("D", x6, y6, canvas, paint, currentCharacter.equals("D"));
  782. drawCharacter("G", x7, y7, canvas, paint, currentCharacter.equals("G"));
  783. drawCharacter("F", x8, y8, canvas, paint, currentCharacter.equals("F"));
  784. drawCharacter("E", x9, y9, canvas, paint, currentCharacter.equals("E"));
  785. break;
  786. case IM:
  787. paint.setColor(Color.BLUE);
  788. drawCharacter("P", x1, y1, canvas, paint, currentCharacter.equals("P"));
  789. drawCharacter("I", x2, y2, canvas, paint, currentCharacter.equals("I"));
  790. drawCharacter("J", x3, y3, canvas, paint, currentCharacter.equals("J"));
  791. drawCharacter("O", x4, y4, canvas, paint, currentCharacter.equals("O"));
  792. drawCharacter("K", x6, y6, canvas, paint, currentCharacter.equals("K"));
  793. drawCharacter("N", x7, y7, canvas, paint, currentCharacter.equals("N"));
  794. drawCharacter("M", x8, y8, canvas, paint, currentCharacter.equals("M"));
  795. drawCharacter("L", x9, y9, canvas, paint, currentCharacter.equals("L"));
  796. break;
  797. case QU:
  798. paint.setColor(Color.GREEN);
  799. drawCharacter("W", x1, y1, canvas, paint, currentCharacter.equals("W"));
  800. drawCharacter("X", x2, y2, canvas, paint, currentCharacter.equals("X"));
  801. drawCharacter("Q", x3, y3, canvas, paint, currentCharacter.equals("Q"));
  802. drawCharacter("V", x4, y4, canvas, paint, currentCharacter.equals("V"));
  803. drawCharacter("R", x6, y6, canvas, paint, currentCharacter.equals("R"));
  804. drawCharacter("U", x7, y7, canvas, paint, currentCharacter.equals("U"));
  805. drawCharacter("T", x8, y8, canvas, paint, currentCharacter.equals("T"));
  806. drawCharacter("S", x9, y9, canvas, paint, currentCharacter.equals("S"));
  807. break;
  808. case Y:
  809. paint.setColor(Color.YELLOW);
  810. drawCharacter(",", x1, y1, canvas, paint, currentCharacter.equals(","));
  811. drawCharacter("!", x2, y2, canvas, paint, currentCharacter.equals("!"));
  812. // drawCharacter("MODE", x3, y3, canvas, paint,
  813. // currentCharacter.equals("MODE"));
  814. drawCharacter("<-", x4, y4, canvas, paint, currentCharacter.equals("<-"));
  815. drawCharacter("Y", x6, y6, canvas, paint, currentCharacter.equals("Y"));
  816. drawCharacter(".", x7, y7, canvas, paint, currentCharacter.equals("."));
  817. drawCharacter("?", x8, y8, canvas, paint, currentCharacter.equals("?"));
  818. drawCharacter("Z", x9, y9, canvas, paint, currentCharacter.equals("Z"));
  819. break;
  820. default:
  821. paint.setColor(Color.RED);
  822. canvas.drawText("A", x1, y1, paint);
  823. canvas.drawText("E", x9, y9, paint);
  824. paint.setColor(Color.BLUE);
  825. canvas.drawText("I", x2, y2, paint);
  826. canvas.drawText("M", x8, y8, paint);
  827. paint.setColor(Color.GREEN);
  828. canvas.drawText("Q", x3, y3, paint);
  829. canvas.drawText("U", x7, y7, paint);
  830. paint.setColor(Color.YELLOW);
  831. canvas.drawText("Y", x6, y6, paint);
  832. canvas.drawText("<-", x4, y4, paint);
  833. break;
  834. }
  835. }
  836. }
  837. private void drawCharacter(
  838. String character, int x, int y, Canvas canvas, Paint paint, boolean isSelected) {
  839. int regSize = 50;
  840. int selectedSize = regSize * 2;
  841. if (isSelected) {
  842. paint.setTextSize(selectedSize);
  843. } else {
  844. paint.setTextSize(regSize);
  845. }
  846. canvas.drawText(character, x, y, paint);
  847. }
  848. public void backspace() {
  849. confirmed = false;
  850. String deletedCharacter = "";
  851. if (currentString.length() > 0) {
  852. deletedCharacter = "" + currentString.charAt(currentString.length() - 1);
  853. currentString = currentString.substring(0, currentString.length() - 1);
  854. if (currentString.length() > 0) {
  855. filteredContacts.filter(currentString);
  856. jumpToFirstFilteredResult();
  857. } else {
  858. resetContactList();
  859. }
  860. } else {
  861. parent.tts.playEarcon(parent.getString(R.string.earcon_tock), 0, null);
  862. parent.tts.playEarcon(parent.getString(R.string.earcon_tock), 1, null);
  863. }
  864. invalidate();
  865. }
  866. public void displayContactDetails() {
  867. if (!managedCursor.isAfterLast()) {
  868. final String text = parent.getString(
  869. R.string.load_detail, managedCursor.getString(NAME));
  870. parent.tts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
  871. final String uri = parent.getString(
  872. R.string.people_uri, managedCursor.getString(PERSON_ID));
  873. final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
  874. parent.startActivity(intent);
  875. }
  876. }
  877. private class LongPressDetector implements Runnable {
  878. @Override
  879. public void run() {
  880. invalidate();
  881. displayContactDetails();
  882. }
  883. }
  884. }