DTMFTwelveKeyDialer.java revision c563f5594dc6b2ffc0c3bf74ac03108bb168abd6
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.phone; 18 19 20import com.android.internal.telephony.CallerInfo; 21import com.android.internal.telephony.CallerInfoAsyncQuery; 22import com.android.internal.telephony.Phone; 23import android.widget.SlidingDrawer; 24 25import android.media.AudioManager; 26import android.media.ToneGenerator; 27import android.os.Handler; 28import android.os.Message; 29import android.provider.Settings; 30import android.telephony.PhoneNumberUtils; 31import android.text.Editable; 32import android.text.Spannable; 33import android.text.method.DialerKeyListener; 34import android.text.method.MovementMethod; 35import android.util.Log; 36import android.view.KeyEvent; 37import android.view.MotionEvent; 38import android.view.View; 39import android.view.WindowManager; 40import android.view.animation.AlphaAnimation; 41import android.view.animation.Animation; 42import android.view.animation.AnimationUtils; 43import android.view.animation.Animation.AnimationListener; 44import android.widget.EditText; 45import android.widget.LinearLayout; 46import android.widget.TextView; 47 48import java.util.HashMap; 49 50/** 51 * Dialer class that encapsulates the DTMF twelve key behaviour. 52 * This model backs up the UI behaviour in DTMFTwelveKeyDialerView.java. 53 */ 54public class DTMFTwelveKeyDialer implements 55 CallerInfoAsyncQuery.OnQueryCompleteListener, 56 SlidingDrawer.OnDrawerOpenListener, 57 SlidingDrawer.OnDrawerCloseListener, 58 View.OnTouchListener, 59 View.OnKeyListener { 60 61 // debug data 62 private static final String LOG_TAG = "dtmf dialer"; 63 private static final boolean DBG = false; 64 65 // events 66 private static final int PHONE_DISCONNECT = 100; 67 68 private static Phone mPhone; 69 private ToneGenerator mToneGenerator; 70 private Object mToneGeneratorLock = new Object(); 71 72 // indicate if we want to enable the DTMF tone playback. 73 private boolean mDTMFToneEnabled; 74 75 /** Hash Map to map a character to a tone*/ 76 private static final HashMap<Character, Integer> mToneMap = 77 new HashMap<Character, Integer>(); 78 /** Hash Map to map a view id to a character*/ 79 private static final HashMap<Integer, Character> mDisplayMap = 80 new HashMap<Integer, Character>(); 81 /** Set up the static maps*/ 82 static { 83 // Map the key characters to tones 84 mToneMap.put('1', ToneGenerator.TONE_DTMF_1); 85 mToneMap.put('2', ToneGenerator.TONE_DTMF_2); 86 mToneMap.put('3', ToneGenerator.TONE_DTMF_3); 87 mToneMap.put('4', ToneGenerator.TONE_DTMF_4); 88 mToneMap.put('5', ToneGenerator.TONE_DTMF_5); 89 mToneMap.put('6', ToneGenerator.TONE_DTMF_6); 90 mToneMap.put('7', ToneGenerator.TONE_DTMF_7); 91 mToneMap.put('8', ToneGenerator.TONE_DTMF_8); 92 mToneMap.put('9', ToneGenerator.TONE_DTMF_9); 93 mToneMap.put('0', ToneGenerator.TONE_DTMF_0); 94 mToneMap.put('#', ToneGenerator.TONE_DTMF_P); 95 mToneMap.put('*', ToneGenerator.TONE_DTMF_S); 96 97 // Map the buttons to the display characters 98 mDisplayMap.put(R.id.one, '1'); 99 mDisplayMap.put(R.id.two, '2'); 100 mDisplayMap.put(R.id.three, '3'); 101 mDisplayMap.put(R.id.four, '4'); 102 mDisplayMap.put(R.id.five, '5'); 103 mDisplayMap.put(R.id.six, '6'); 104 mDisplayMap.put(R.id.seven, '7'); 105 mDisplayMap.put(R.id.eight, '8'); 106 mDisplayMap.put(R.id.nine, '9'); 107 mDisplayMap.put(R.id.zero, '0'); 108 mDisplayMap.put(R.id.pound, '#'); 109 mDisplayMap.put(R.id.star, '*'); 110 } 111 112 // UI elements 113 // Including elements used in the call status, now split 114 // between call state and call timer. 115 private EditText mDigits; 116 117 // InCallScreen reference. 118 private InCallScreen mInCallScreen; 119 120 // SlidingDrawer reference. 121 private SlidingDrawer mDialerContainer; 122 123 // view reference 124 private DTMFTwelveKeyDialerView mDialerView; 125 126 // key listner reference, may or may not be attached to a view. 127 private DTMFKeyListener mDialerKeyListener; 128 129 /** 130 * Create an input method just so that the textview can display the cursor. 131 * There is no selecting / positioning on the dialer field, only number input. 132 */ 133 private class DTMFDisplayMovementMethod implements MovementMethod { 134 135 /**Return false since we are NOT consuming the input.*/ 136 public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { 137 return false; 138 } 139 140 /**Return false since we are NOT consuming the input.*/ 141 public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { 142 return false; 143 } 144 145 /**Return false since we are NOT consuming the input.*/ 146 public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) { 147 return false; 148 } 149 150 /**Return false since we are NOT consuming the input.*/ 151 public boolean onTrackballEvent(TextView widget, Spannable buffer, MotionEvent event) { 152 return false; 153 } 154 155 /**Return false since we are NOT consuming the input.*/ 156 public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { 157 return false; 158 } 159 160 public void initialize(TextView widget, Spannable text) { 161 } 162 163 public void onTakeFocus(TextView view, Spannable text, int dir) { 164 } 165 166 /**Disallow arbitrary selection.*/ 167 public boolean canSelectArbitrarily() { 168 return false; 169 } 170 } 171 172 /** 173 * Our own key listener, specialized for dealing with DTMF codes. 174 * 1. Ignore the backspace since it is irrelevant. 175 * 2. Allow ONLY valid DTMF characters to generate a tone and be 176 * sent as a DTMF code. 177 * 3. All other remaining characters are handled by the superclass. 178 */ 179 private class DTMFKeyListener extends DialerKeyListener { 180 181 private DTMFDisplayAnimation mDTMFDisplayAnimation; 182 183 /** 184 * Class that controls the fade in/out of the DTMF dialer field. 185 * Logic is tied into the keystroke events handled by the 186 * DTMFKeyListener. 187 * 188 * The key to this logic is the use of WAIT_FOR_USER_INPUT and 189 * Animation.fillBefore(true). This keeps the alpha animation in its 190 * beginning state until some key interaction is detected. On the 191 * key interaction, the animation start time is reset as appropriate. 192 * 193 * On fade in: 194 * 1.Set and hold the alpha value to 0.0. 195 * 2.Animation is triggered on key down. 196 * 2.Animation is started immediately. 197 * On fade out: 198 * 1.Set and hold the alpha value to 1.0. 199 * 2.Animation is triggered on key up. 200 * 2.Animation is FADE_OUT_TIMEOUT after trigger. 201 */ 202 private class DTMFDisplayAnimation extends Handler implements AnimationListener { 203 // events for the fade in and out. 204 private static final int EVENT_FADE_IN = -1; 205 private static final int EVENT_FADE_OUT = -2; 206 207 // static constants 208 // duration for the fade in animation 209 private static final int FADE_IN_ANIMATION_TIME = 500; 210 // duration for the fade out animation 211 private static final int FADE_OUT_ANIMATION_TIME = 1000; 212 /** 213 * Wait time after last user activity to begin fade out. 214 * Timeout to match: 215 * {@link com.android.server.PowerManagerService#SHORT_KEYLIGHT_DELAY} 216 */ 217 private static final int FADE_OUT_TIMEOUT = 6000; 218 219 /** 220 * Value indicating we should expect user input. This is used 221 * to keep animations in the started / initial state until a new 222 * start time is set. 223 */ 224 private static final long WAIT_FOR_USER_INPUT = Long.MAX_VALUE; 225 226 // DTMF display field 227 private View mDTMFDisplay; 228 229 // Fade in / out animations. 230 private AlphaAnimation mFadeIn; 231 private AlphaAnimation mFadeOut; 232 233 /** 234 * API implemented for AnimationListener, called on start of animation. 235 */ 236 public void onAnimationStart(Animation animation) {} 237 238 /** 239 * API implemented for AnimationListener, called on end of animation. 240 * This code just prepares the next animation to be run. 241 */ 242 public void onAnimationEnd(Animation animation) { 243 sendEmptyMessage(animation == mFadeOut ? EVENT_FADE_IN : EVENT_FADE_OUT); 244 } 245 246 /** 247 * API implemented for AnimationListener, called on repeat of animation. 248 */ 249 public void onAnimationRepeat(Animation animation) {} 250 251 /** 252 * Handle the FADE_IN and FADE_OUT messages 253 */ 254 @Override 255 public void handleMessage(Message msg) { 256 switch (msg.what) { 257 case EVENT_FADE_IN: 258 // just initialize to normal fade in. 259 prepareFadeIn(); 260 break; 261 case EVENT_FADE_OUT: 262 default: 263 // set animation to fade out. 264 mDTMFDisplay.setAnimation(mFadeOut); 265 break; 266 } 267 } 268 269 DTMFDisplayAnimation(EditText display) { 270 mDTMFDisplay = display; 271 272 // create fade in animation 273 mFadeIn = new AlphaAnimation(0.0f, 1.0f); 274 mFadeIn.setDuration(FADE_IN_ANIMATION_TIME); 275 mFadeIn.setAnimationListener(this); 276 mFadeIn.setFillBefore(true); 277 278 // create fade out animation. 279 mFadeOut = new AlphaAnimation(1.0f, 0.0f); 280 mFadeOut.setDuration(FADE_OUT_ANIMATION_TIME); 281 mFadeOut.setAnimationListener(this); 282 mFadeOut.setFillBefore(true); 283 } 284 285 /** 286 * Set up dtmf display field for the fade in trigger. 287 */ 288 void prepareFadeIn() { 289 mDTMFDisplay.setAnimation(mFadeIn); 290 mFadeIn.setStartTime(WAIT_FOR_USER_INPUT); 291 } 292 293 /** 294 * Notify that a key press has occurred, handle the appropriate 295 * animation changes. 296 */ 297 void onKeyDown() { 298 long currentAnimTime = AnimationUtils.currentAnimationTimeMillis(); 299 300 if ((mDTMFDisplay.getAnimation() == mFadeOut) && 301 (mFadeOut.getStartTime() < currentAnimTime)) { 302 // reset the animation if it is running. 303 mFadeOut.reset(); 304 } else if (mFadeIn.getStartTime() > currentAnimTime){ 305 // otherwise start the fade in. 306 mFadeIn.start(); 307 } 308 309 // Reset the fade out timer. 310 mFadeOut.setStartTime(WAIT_FOR_USER_INPUT); 311 } 312 313 /** 314 * Notify that a key up has occurred, set the fade out animation 315 * start time accordingly. 316 */ 317 void onKeyUp() { 318 mFadeOut.setStartTime(AnimationUtils.currentAnimationTimeMillis() + 319 FADE_OUT_TIMEOUT); 320 } 321 } 322 323 private DTMFKeyListener(EditText display) { 324 super(); 325 326 // setup the display and animation if we're in landscape. 327 if (display != null && InCallScreen.ConfigurationHelper.isLandscape()) { 328 mDTMFDisplayAnimation = new DTMFDisplayAnimation(display); 329 mDTMFDisplayAnimation.prepareFadeIn(); 330 } 331 } 332 333 /** 334 * Overriden to return correct DTMF-dialable characters. 335 */ 336 @Override 337 protected char[] getAcceptedChars(){ 338 return DTMF_CHARACTERS; 339 } 340 341 /** special key listener ignores backspace. */ 342 @Override 343 public boolean backspace(View view, Editable content, int keyCode, 344 KeyEvent event) { 345 return false; 346 } 347 348 /** 349 * Return true if the keyCode is an accepted modifier key for the 350 * dialer (ALT or SHIFT). 351 */ 352 private boolean isAcceptableModifierKey (int keyCode) { 353 switch (keyCode) { 354 case KeyEvent.KEYCODE_ALT_LEFT: 355 case KeyEvent.KEYCODE_ALT_RIGHT: 356 case KeyEvent.KEYCODE_SHIFT_LEFT: 357 case KeyEvent.KEYCODE_SHIFT_RIGHT: 358 return true; 359 default: 360 return false; 361 } 362 } 363 364 /** 365 * Overriden so that with each valid button press, we start sending 366 * a dtmf code and play a local dtmf tone. 367 */ 368 @Override 369 public boolean onKeyDown(View view, Editable content, 370 int keyCode, KeyEvent event) { 371 // find the character 372 char c = (char) lookup(event, content); 373 374 // if not a long press, and parent onKeyDown accepts the input 375 if (event.getRepeatCount() == 0 && super.onKeyDown(view, content, keyCode, event)) { 376 377 boolean keyOK = ok(getAcceptedChars(), c); 378 379 // show the display on any key down. 380 if (mDTMFDisplayAnimation != null && (keyOK || isAcceptableModifierKey(keyCode))) { 381 mDTMFDisplayAnimation.onKeyDown(); 382 } 383 384 // if the character is a valid dtmf code, start playing the tone and send the 385 // code. 386 if (keyOK) { 387 if (DBG) log("DTMFKeyListener reading '" + c + "' from input."); 388 playTone(c); 389 } else if (DBG) { 390 log("DTMFKeyListener rejecting '" + c + "' from input."); 391 } 392 return true; 393 } 394 return false; 395 } 396 397 /** 398 * Overriden so that with each valid button up, we stop sending 399 * a dtmf code and the dtmf tone. 400 */ 401 @Override 402 public boolean onKeyUp(View view, Editable content, 403 int keyCode, KeyEvent event) { 404 405 super.onKeyUp(view, content, keyCode, event); 406 407 // find the character 408 char c = (char) lookup(event, content); 409 410 boolean keyOK = ok(getAcceptedChars(), c); 411 412 // show the display on any key down. 413 if (mDTMFDisplayAnimation != null && (keyOK || isAcceptableModifierKey(keyCode))) { 414 mDTMFDisplayAnimation.onKeyUp(); 415 } 416 417 if (keyOK) { 418 if (DBG) log("Stopping the tone for '" + c + "'"); 419 stopTone(); 420 return true; 421 } 422 423 return false; 424 } 425 426 /** 427 * Handle individual keydown events when we DO NOT have an Editable handy. 428 */ 429 public boolean onKeyDown(KeyEvent event) { 430 char c = lookup (event); 431 if (DBG) log("recieved keydown for '" + c + "'"); 432 433 // if not a long press, and parent onKeyDown accepts the input 434 if (event.getRepeatCount() == 0 && c != 0) { 435 // if the character is a valid dtmf code, start playing the tone and send the 436 // code. 437 if (ok(getAcceptedChars(), c)) { 438 if (DBG) log("DTMFKeyListener reading '" + c + "' from input."); 439 playTone(c); 440 return true; 441 } else if (DBG) { 442 log("DTMFKeyListener rejecting '" + c + "' from input."); 443 } 444 } 445 return false; 446 } 447 448 /** 449 * Handle individual keyup events. 450 * 451 * @param event is the event we are trying to stop. If this is null, 452 * then we just force-stop the last tone without checking if the event 453 * is an acceptable dialer event. 454 */ 455 public boolean onKeyUp(KeyEvent event) { 456 if (event == null) { 457 if (DBG) log("Stopping the last played tone."); 458 stopTone(); 459 return true; 460 } 461 462 char c = lookup (event); 463 if (DBG) log("recieved keyup for '" + c + "'"); 464 465 // TODO: stopTone does not take in character input, we may want to 466 // consider checking for this ourselves. 467 if (ok(getAcceptedChars(), c)) { 468 if (DBG) log("Stopping the tone for '" + c + "'"); 469 stopTone(); 470 return true; 471 } 472 473 return false; 474 } 475 476 /** 477 * Find the Dialer Key mapped to this event. 478 * 479 * @return The char value of the input event, otherwise 480 * 0 if no matching character was found. 481 */ 482 private char lookup (KeyEvent event) { 483 // This code is similar to {@link DialerKeyListener#lookup(KeyEvent, Spannable) lookup} 484 int meta = event.getMetaState(); 485 int number = event.getNumber(); 486 487 if (!((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) || (number == 0)) { 488 int match = event.getMatch(getAcceptedChars(), meta); 489 number = (match != 0) ? match : number; 490 } 491 492 return (char) number; 493 } 494 495 /** 496 * Check to see if the keyEvent is dialable. 497 */ 498 boolean isKeyEventAcceptable (KeyEvent event) { 499 return (ok(getAcceptedChars(), lookup(event))); 500 } 501 502 /** 503 * Overrides the characters used in {@link DialerKeyListener#CHARACTERS} 504 * These are the valid dtmf characters. 505 */ 506 public final char[] DTMF_CHARACTERS = new char[] { 507 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*' 508 }; 509 } 510 511 /** 512 * Our own handler to take care of the messages from the phone state changes 513 */ 514 private Handler mHandler = new Handler () { 515 @Override 516 public void handleMessage(Message msg) { 517 switch (msg.what) { 518 // disconnect action 519 // make sure to close the dialer on ALL disconnect actions. 520 case PHONE_DISCONNECT: 521 if (DBG) log("disconnect message recieved, shutting down."); 522 // unregister since we are closing. 523 mPhone.unregisterForDisconnect(this); 524 closeDialer(false); 525 break; 526 } 527 } 528 }; 529 530 531 DTMFTwelveKeyDialer (InCallScreen parent) { 532 mInCallScreen = parent; 533 mPhone = ((PhoneApp) mInCallScreen.getApplication()).phone; 534 mDialerContainer = (SlidingDrawer) mInCallScreen.findViewById(R.id.dialer_container); 535 536 // mDialerContainer is only valid when we're looking at the portrait version of 537 // dtmf_twelve_key_dialer. 538 if (mDialerContainer != null) { 539 mDialerContainer.setOnDrawerOpenListener(this); 540 mDialerContainer.setOnDrawerCloseListener(this); 541 } 542 543 // setup the dialer display reference. 544 EditText display = parent.getDialerDisplay(); 545 mDialerKeyListener = new DTMFKeyListener(display); 546 // if the display is visible, set the behaviour correctly. 547 if (display != null && InCallScreen.ConfigurationHelper.isLandscape()) { 548 display.setKeyListener(mDialerKeyListener); 549 display.setMovementMethod(new DTMFDisplayMovementMethod()); 550 551 // remove the long-press context menus that support 552 // the edit (copy / paste / select) functions. 553 display.setLongClickable(false); 554 } 555 } 556 557 /** 558 * Called when we want to hide the DTMF Display field immediately. 559 * 560 * @param shouldHide if true, hide the display (and disable DTMF tones) immediately; 561 * otherwise, re-enable the display. 562 */ 563 void hideDTMFDisplay(boolean shouldHide) { 564 DTMFKeyListener.DTMFDisplayAnimation animation = mDialerKeyListener.mDTMFDisplayAnimation; 565 566 // if the animation is in place 567 if (animation != null) { 568 View text = animation.mDTMFDisplay; 569 570 // and the display is available 571 if (text != null) { 572 // hide the display if necessary 573 text.setVisibility(shouldHide ? View.GONE : View.VISIBLE); 574 if (shouldHide) { 575 // null the animation - this makes the display disappear faster 576 text.setAnimation(null); 577 } else { 578 // otherwise reset the animation to the initial state. 579 animation.prepareFadeIn(); 580 } 581 } 582 } 583 } 584 585 /** 586 * Null out our reference to the InCallScreen activity. 587 * This indicates that the InCallScreen activity has been destroyed. 588 * At the same time, get rid of listeners since we're not going to 589 * be valid anymore. 590 */ 591 void clearInCallScreenReference() { 592 mInCallScreen = null; 593 mDialerKeyListener = null; 594 if (mDialerContainer != null) { 595 mDialerContainer.setOnDrawerOpenListener(null); 596 mDialerContainer.setOnDrawerCloseListener(null); 597 } 598 closeDialer(false); 599 } 600 601 LinearLayout getView() { 602 return mDialerView; 603 } 604 605 /** 606 * Dialer code that runs when the dialer is brought up. 607 * This includes layout changes, etc, and just prepares the dialer model for use. 608 */ 609 void onDialerOpen() { 610 if (DBG) log("onDialerOpen()..."); 611 612 // inflate the view. 613 mDialerView = (DTMFTwelveKeyDialerView) mInCallScreen.findViewById(R.id.dtmf_dialer); 614 mDialerView.setDialer(this); 615 616 // Have the WindowManager filter out cheek touch events 617 mInCallScreen.getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES); 618 619 mPhone.registerForDisconnect(mHandler, PHONE_DISCONNECT, null); 620 621 // set to a longer delay while the dialer is up. 622 PhoneApp app = PhoneApp.getInstance(); 623 app.updateWakeState(); 624 625 // setup the digit display 626 mDigits = (EditText) mDialerView.findViewById(R.id.dtmfDialerField); 627 mDigits.setKeyListener(new DTMFKeyListener(null)); 628 mDigits.requestFocus(); 629 630 // remove the long-press context menus that support 631 // the edit (copy / paste / select) functions. 632 mDigits.setLongClickable(false); 633 634 // Check for the presence of the keypad (portrait mode) 635 View view = mDialerView.findViewById(R.id.one); 636 if (view != null) { 637 if (DBG) log("portrait mode setup"); 638 setupKeypad(); 639 } else { 640 if (DBG) log("landscape mode setup"); 641 // Adding hint text to the field to indicate that keyboard 642 // is needed while in landscape mode. 643 mDigits.setHint(R.string.dialerKeyboardHintText); 644 } 645 646 // setup the local tone generator. 647 startDialerSession(); 648 649 // Give the InCallScreen a chance to do any necessary UI updates. 650 mInCallScreen.onDialerOpen(); 651 } 652 653 /** 654 * Setup the local tone generator. Should have corresponding calls to 655 * {@link onDialerPause}. 656 */ 657 public void startDialerSession() { 658 // see if we need to play local tones. 659 mDTMFToneEnabled = Settings.System.getInt(mInCallScreen.getContentResolver(), 660 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1; 661 662 // create the tone generator 663 // if the mToneGenerator creation fails, just continue without it. It is 664 // a local audio signal, and is not as important as the dtmf tone itself. 665 if (mDTMFToneEnabled) { 666 synchronized (mToneGeneratorLock) { 667 if (mToneGenerator == null) { 668 try { 669 mToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 80); 670 } catch (RuntimeException e) { 671 if (DBG) log("Exception caught while creating local tone generator: " + e); 672 mToneGenerator = null; 673 } 674 } 675 } 676 } 677 } 678 679 /** 680 * Dialer code that runs when the dialer is closed. 681 * This releases resources acquired when we start the dialer. 682 */ 683 public void onDialerClose() { 684 if (DBG) log("onDialerClose()..."); 685 686 // reset back to a short delay for the poke lock. 687 PhoneApp app = PhoneApp.getInstance(); 688 app.updateWakeState(); 689 690 mPhone.unregisterForDisconnect(mHandler); 691 692 stopDialerSession(); 693 694 // Give the InCallScreen a chance to do any necessary UI updates. 695 mInCallScreen.onDialerClose(); 696 } 697 698 /** 699 * Tear down the local tone generator, corresponds to calls to 700 * {@link onDialerResume} 701 */ 702 public void stopDialerSession() { 703 // release the tone generator. 704 synchronized (mToneGeneratorLock) { 705 if (mToneGenerator != null) { 706 mToneGenerator.release(); 707 mToneGenerator = null; 708 } 709 } 710 } 711 712 /** 713 * upon completion of the query, update the name field in the status. 714 */ 715 public void onQueryComplete(int token, Object cookie, CallerInfo ci){ 716 if (DBG) log("callerinfo query complete, updating ui."); 717 718 ((TextView) cookie).setText(PhoneUtils.getCompactNameFromCallerInfo(ci, mInCallScreen)); 719 } 720 721 /** 722 * Called externally (from InCallScreen) to play a DTMF Tone. 723 */ 724 public boolean onDialerKeyDown(KeyEvent event) { 725 if (DBG) log("Notifying dtmf key down."); 726 return mDialerKeyListener.onKeyDown(event); 727 } 728 729 /** 730 * Called externally (from InCallScreen) to cancel the last DTMF Tone played. 731 */ 732 public boolean onDialerKeyUp(KeyEvent event) { 733 if (DBG) log("Notifying dtmf key up."); 734 return mDialerKeyListener.onKeyUp(event); 735 } 736 737 /** 738 * setup the keys on the dialer activity, using the keymaps. 739 */ 740 private void setupKeypad() { 741 // for each view id listed in the displaymap 742 View button; 743 for (int viewId : mDisplayMap.keySet()) { 744 // locate the view 745 button = mDialerView.findViewById(viewId); 746 // Setup the listeners for the buttons 747 button.setOnTouchListener(this); 748 button.setClickable(true); 749 button.setOnKeyListener(this); 750 } 751 } 752 753 /** 754 * catch the back and call buttons to return to the in call activity. 755 */ 756 public boolean onKeyDown(int keyCode, KeyEvent event) { 757 switch (keyCode) { 758 // finish for these events 759 case KeyEvent.KEYCODE_BACK: 760 case KeyEvent.KEYCODE_CALL: 761 if (DBG) log("exit requested"); 762 closeDialer(true); // do the "closing" animation 763 return true; 764 } 765 return mInCallScreen.onKeyDown(keyCode, event); 766 } 767 768 /** 769 * catch the back and call buttons to return to the in call activity. 770 */ 771 public boolean onKeyUp(int keyCode, KeyEvent event) { 772 return mInCallScreen.onKeyUp(keyCode, event); 773 } 774 775 /** 776 * Implemented for the TouchListener, process the touch events. 777 */ 778 public boolean onTouch(View v, MotionEvent event) { 779 int viewId = v.getId(); 780 781 // if the button is recognized 782 if (mDisplayMap.containsKey(viewId)) { 783 switch (event.getAction()) { 784 case MotionEvent.ACTION_DOWN: 785 // Append the character mapped to this button, to the display. 786 // start the tone 787 appendDigit(mDisplayMap.get(viewId)); 788 break; 789 case MotionEvent.ACTION_UP: 790 case MotionEvent.ACTION_CANCEL: 791 // stop the tone on ANY other event, except for MOVE. 792 stopTone(); 793 break; 794 } 795 // do not return true [handled] here, since we want the 796 // press / click animation to be handled by the framework. 797 } 798 return false; 799 } 800 801 /** 802 * Implements View.OnKeyListener for the DTMF buttons. Enables dialing with trackball/dpad. 803 */ 804 public boolean onKey(View v, int keyCode, KeyEvent event) { 805 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { 806 int viewId = v.getId(); 807 if (mDisplayMap.containsKey(viewId)) { 808 switch (event.getAction()) { 809 case KeyEvent.ACTION_DOWN: 810 if (event.getRepeatCount() == 0) { 811 appendDigit(mDisplayMap.get(viewId)); 812 } 813 break; 814 case KeyEvent.ACTION_UP: 815 stopTone(); 816 break; 817 } 818 // do not return true [handled] here, since we want the 819 // press / click animation to be handled by the framework. 820 } 821 } 822 return false; 823 } 824 825 /** 826 * @return true if the dialer is currently opened (i.e. expanded). 827 */ 828 public boolean isOpened() { 829 return mDialerContainer != null && mDialerContainer.isOpened(); 830 } 831 832 /** 833 * Forces the dialer into the "open" state. 834 * Does nothing if the dialer is already open. 835 * 836 * @param animate if true, open the dialer with an animation. 837 */ 838 public void openDialer(boolean animate) { 839 if (mDialerContainer != null && !mDialerContainer.isOpened()) { 840 if (animate) { 841 mDialerContainer.animateToggle(); 842 } else { 843 mDialerContainer.toggle(); 844 } 845 } 846 } 847 848 /** 849 * Forces the dialer into the "closed" state. 850 * Does nothing if the dialer is already closed. 851 * 852 * @param animate if true, close the dialer with an animation. 853 */ 854 public void closeDialer(boolean animate) { 855 if (mDialerContainer != null && mDialerContainer.isOpened()) { 856 if (animate) { 857 mDialerContainer.animateToggle(); 858 } else { 859 mDialerContainer.toggle(); 860 } 861 } 862 } 863 864 /** 865 * Implemented for the SlidingDrawer open listener, prepare the dialer. 866 */ 867 public void onDrawerOpened() { 868 onDialerOpen(); 869 } 870 871 /** 872 * Implemented for the SlidingDrawer close listener, release the dialer. 873 */ 874 public void onDrawerClosed() { 875 onDialerClose(); 876 } 877 878 /** 879 * update the text area and playback the tone. 880 */ 881 private final void appendDigit(char c) { 882 // if it is a valid key, then update the display and send the dtmf tone. 883 if (PhoneNumberUtils.is12Key(c)) { 884 if (DBG) log("updating display and sending dtmf tone for '" + c + "'"); 885 886 if (mDigits != null) { 887 mDigits.getText().append(c); 888 } 889 // play the tone if it exists. 890 if (mToneMap.containsKey(c)) { 891 // begin tone playback. 892 playTone(c); 893 } 894 } else if (DBG) { 895 log("ignoring dtmf request for '" + c + "'"); 896 } 897 898 } 899 900 /** 901 * Start playing a DTMF tone, also begin the local tone playback if it is 902 * enabled. 903 * 904 * @param tone a tone code from {@link ToneGenerator} 905 */ 906 void playTone(char tone) { 907 if (DBG) log("starting remote tone."); 908 PhoneApp.getInstance().phone.startDtmf(tone); 909 910 // if local tone playback is enabled, start it. 911 if (mDTMFToneEnabled) { 912 synchronized (mToneGeneratorLock) { 913 if (mToneGenerator == null) { 914 if (DBG) log("playTone: mToneGenerator == null, tone: " + tone); 915 } else { 916 if (DBG) log("starting local tone " + tone); 917 mToneGenerator.startTone(mToneMap.get(tone)); 918 } 919 } 920 } 921 } 922 923 /** 924 * Stop playing the current DTMF tone. 925 * 926 * The ToneStopper class (similar to that in {@link TwelveKeyDialer#mToneStopper}) 927 * has been removed in favor of synchronous start / stop calls since tone duration 928 * is now a function of the input. 929 */ 930 void stopTone() { 931 if (DBG) log("stopping remote tone."); 932 PhoneApp.getInstance().phone.stopDtmf(); 933 934 // if local tone playback is enabled, stop it. 935 if (DBG) log("trying to stop local tone..."); 936 if (mDTMFToneEnabled) { 937 synchronized (mToneGeneratorLock) { 938 if (mToneGenerator == null) { 939 if (DBG) log("stopTone: mToneGenerator == null"); 940 } else { 941 if (DBG) log("stopping local tone."); 942 mToneGenerator.stopTone(); 943 } 944 } 945 } 946 } 947 948 /** 949 * Check to see if the keyEvent is dialable. 950 */ 951 boolean isKeyEventAcceptable (KeyEvent event) { 952 return (mDialerKeyListener != null && mDialerKeyListener.isKeyEventAcceptable(event)); 953 } 954 955 /** 956 * static logging method 957 */ 958 private static void log(String msg) { 959 Log.d(LOG_TAG, msg); 960 } 961} 962