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