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 17 package com.android.phone; 18 19 import android.media.AudioManager; 20 import android.media.ToneGenerator; 21 import android.os.Handler; 22 import android.os.Message; 23 import android.provider.Settings; 24 import android.telephony.PhoneNumberUtils; 25 import android.text.Editable; 26 import android.text.Spannable; 27 import android.text.method.DialerKeyListener; 28 import android.text.method.MovementMethod; 29 import android.util.Log; 30 import android.view.KeyEvent; 31 import android.view.MotionEvent; 32 import android.view.View; 33 import android.widget.EditText; 34 import android.widget.TextView; 35 36 import com.android.internal.telephony.CallManager; 37 import com.android.internal.telephony.Phone; 38 39 import java.util.HashMap; 40 import java.util.LinkedList; 41 import java.util.Queue; 42 43 44 /** 45 * Dialer class that encapsulates the DTMF twelve key behaviour. 46 * This model backs up the UI behaviour in DTMFTwelveKeyDialerView.java. 47 */ 48 public class DTMFTwelveKeyDialer implements View.OnTouchListener, View.OnKeyListener { 49 private static final String LOG_TAG = "DTMFTwelveKeyDialer"; 50 private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2); 51 52 // events 53 private static final int PHONE_DISCONNECT = 100; 54 private static final int DTMF_SEND_CNF = 101; 55 56 private CallManager mCM; 57 private ToneGenerator mToneGenerator; 58 private Object mToneGeneratorLock = new Object(); 59 60 // indicate if we want to enable the local tone playback. 61 private boolean mLocalToneEnabled; 62 63 // indicates that we are using automatically shortened DTMF tones 64 boolean mShortTone; 65 66 // indicate if the confirmation from TelephonyFW is pending. 67 private boolean mDTMFBurstCnfPending = false; 68 69 // Queue to queue the short dtmf characters. 70 private Queue<Character> mDTMFQueue = new LinkedList<Character>(); 71 72 // Short Dtmf tone duration 73 private static final int DTMF_DURATION_MS = 120; 74 75 76 /** Hash Map to map a character to a tone*/ 77 private static final HashMap<Character, Integer> mToneMap = 78 new HashMap<Character, Integer>(); 79 /** Hash Map to map a view id to a character*/ 80 private static final HashMap<Integer, Character> mDisplayMap = 81 new HashMap<Integer, Character>(); 82 /** Set up the static maps*/ 83 static { 84 // Map the key characters to tones 85 mToneMap.put('1', ToneGenerator.TONE_DTMF_1); 86 mToneMap.put('2', ToneGenerator.TONE_DTMF_2); 87 mToneMap.put('3', ToneGenerator.TONE_DTMF_3); 88 mToneMap.put('4', ToneGenerator.TONE_DTMF_4); 89 mToneMap.put('5', ToneGenerator.TONE_DTMF_5); 90 mToneMap.put('6', ToneGenerator.TONE_DTMF_6); 91 mToneMap.put('7', ToneGenerator.TONE_DTMF_7); 92 mToneMap.put('8', ToneGenerator.TONE_DTMF_8); 93 mToneMap.put('9', ToneGenerator.TONE_DTMF_9); 94 mToneMap.put('0', ToneGenerator.TONE_DTMF_0); 95 mToneMap.put('#', ToneGenerator.TONE_DTMF_P); 96 mToneMap.put('*', ToneGenerator.TONE_DTMF_S); 97 98 // Map the buttons to the display characters 99 mDisplayMap.put(R.id.one, '1'); 100 mDisplayMap.put(R.id.two, '2'); 101 mDisplayMap.put(R.id.three, '3'); 102 mDisplayMap.put(R.id.four, '4'); 103 mDisplayMap.put(R.id.five, '5'); 104 mDisplayMap.put(R.id.six, '6'); 105 mDisplayMap.put(R.id.seven, '7'); 106 mDisplayMap.put(R.id.eight, '8'); 107 mDisplayMap.put(R.id.nine, '9'); 108 mDisplayMap.put(R.id.zero, '0'); 109 mDisplayMap.put(R.id.pound, '#'); 110 mDisplayMap.put(R.id.star, '*'); 111 } 112 113 // EditText field used to display the DTMF digits sent so far. 114 // Note this is null in some modes (like during the CDMA OTA call, 115 // where there's no onscreen "digits" display.) 116 private EditText mDialpadDigits; 117 118 // InCallScreen reference. 119 private InCallScreen mInCallScreen; 120 121 // The DTMFTwelveKeyDialerView we use to display the dialpad. 122 private DTMFTwelveKeyDialerView mDialerView; 123 124 // KeyListener used with the "dialpad digits" EditText widget. 125 private DTMFKeyListener mDialerKeyListener; 126 127 /** 128 * Our own key listener, specialized for dealing with DTMF codes. 129 * 1. Ignore the backspace since it is irrelevant. 130 * 2. Allow ONLY valid DTMF characters to generate a tone and be 131 * sent as a DTMF code. 132 * 3. All other remaining characters are handled by the superclass. 133 * 134 * This code is purely here to handle events from the hardware keyboard 135 * while the DTMF dialpad is up. 136 */ 137 private class DTMFKeyListener extends DialerKeyListener { 138 139 private DTMFKeyListener() { 140 super(); 141 } 142 143 /** 144 * Overriden to return correct DTMF-dialable characters. 145 */ 146 @Override 147 protected char[] getAcceptedChars(){ 148 return DTMF_CHARACTERS; 149 } 150 151 /** special key listener ignores backspace. */ 152 @Override 153 public boolean backspace(View view, Editable content, int keyCode, 154 KeyEvent event) { 155 return false; 156 } 157 158 /** 159 * Return true if the keyCode is an accepted modifier key for the 160 * dialer (ALT or SHIFT). 161 */ 162 private boolean isAcceptableModifierKey(int keyCode) { 163 switch (keyCode) { 164 case KeyEvent.KEYCODE_ALT_LEFT: 165 case KeyEvent.KEYCODE_ALT_RIGHT: 166 case KeyEvent.KEYCODE_SHIFT_LEFT: 167 case KeyEvent.KEYCODE_SHIFT_RIGHT: 168 return true; 169 default: 170 return false; 171 } 172 } 173 174 /** 175 * Overriden so that with each valid button press, we start sending 176 * a dtmf code and play a local dtmf tone. 177 */ 178 @Override 179 public boolean onKeyDown(View view, Editable content, 180 int keyCode, KeyEvent event) { 181 // if (DBG) log("DTMFKeyListener.onKeyDown, keyCode " + keyCode + ", view " + view); 182 183 // find the character 184 char c = (char) lookup(event, content); 185 186 // if not a long press, and parent onKeyDown accepts the input 187 if (event.getRepeatCount() == 0 && super.onKeyDown(view, content, keyCode, event)) { 188 189 boolean keyOK = ok(getAcceptedChars(), c); 190 191 // if the character is a valid dtmf code, start playing the tone and send the 192 // code. 193 if (keyOK) { 194 if (DBG) log("DTMFKeyListener reading '" + c + "' from input."); 195 processDtmf(c); 196 } else if (DBG) { 197 log("DTMFKeyListener rejecting '" + c + "' from input."); 198 } 199 return true; 200 } 201 return false; 202 } 203 204 /** 205 * Overriden so that with each valid button up, we stop sending 206 * a dtmf code and the dtmf tone. 207 */ 208 @Override 209 public boolean onKeyUp(View view, Editable content, 210 int keyCode, KeyEvent event) { 211 // if (DBG) log("DTMFKeyListener.onKeyUp, keyCode " + keyCode + ", view " + view); 212 213 super.onKeyUp(view, content, keyCode, event); 214 215 // find the character 216 char c = (char) lookup(event, content); 217 218 boolean keyOK = ok(getAcceptedChars(), c); 219 220 if (keyOK) { 221 if (DBG) log("Stopping the tone for '" + c + "'"); 222 stopTone(); 223 return true; 224 } 225 226 return false; 227 } 228 229 /** 230 * Handle individual keydown events when we DO NOT have an Editable handy. 231 */ 232 public boolean onKeyDown(KeyEvent event) { 233 char c = lookup(event); 234 if (DBG) log("DTMFKeyListener.onKeyDown: event '" + c + "'"); 235 236 // if not a long press, and parent onKeyDown accepts the input 237 if (event.getRepeatCount() == 0 && c != 0) { 238 // if the character is a valid dtmf code, start playing the tone and send the 239 // code. 240 if (ok(getAcceptedChars(), c)) { 241 if (DBG) log("DTMFKeyListener reading '" + c + "' from input."); 242 processDtmf(c); 243 return true; 244 } else if (DBG) { 245 log("DTMFKeyListener rejecting '" + c + "' from input."); 246 } 247 } 248 return false; 249 } 250 251 /** 252 * Handle individual keyup events. 253 * 254 * @param event is the event we are trying to stop. If this is null, 255 * then we just force-stop the last tone without checking if the event 256 * is an acceptable dialer event. 257 */ 258 public boolean onKeyUp(KeyEvent event) { 259 if (event == null) { 260 //the below piece of code sends stopDTMF event unnecessarily even when a null event 261 //is received, hence commenting it. 262 /*if (DBG) log("Stopping the last played tone."); 263 stopTone();*/ 264 return true; 265 } 266 267 char c = lookup(event); 268 if (DBG) log("DTMFKeyListener.onKeyUp: event '" + c + "'"); 269 270 // TODO: stopTone does not take in character input, we may want to 271 // consider checking for this ourselves. 272 if (ok(getAcceptedChars(), c)) { 273 if (DBG) log("Stopping the tone for '" + c + "'"); 274 stopTone(); 275 return true; 276 } 277 278 return false; 279 } 280 281 /** 282 * Find the Dialer Key mapped to this event. 283 * 284 * @return The char value of the input event, otherwise 285 * 0 if no matching character was found. 286 */ 287 private char lookup(KeyEvent event) { 288 // This code is similar to {@link DialerKeyListener#lookup(KeyEvent, Spannable) lookup} 289 int meta = event.getMetaState(); 290 int number = event.getNumber(); 291 292 if (!((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) || (number == 0)) { 293 int match = event.getMatch(getAcceptedChars(), meta); 294 number = (match != 0) ? match : number; 295 } 296 297 return (char) number; 298 } 299 300 /** 301 * Check to see if the keyEvent is dialable. 302 */ 303 boolean isKeyEventAcceptable (KeyEvent event) { 304 return (ok(getAcceptedChars(), lookup(event))); 305 } 306 307 /** 308 * Overrides the characters used in {@link DialerKeyListener#CHARACTERS} 309 * These are the valid dtmf characters. 310 */ 311 public final char[] DTMF_CHARACTERS = new char[] { 312 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*' 313 }; 314 } 315 316 /** 317 * Our own handler to take care of the messages from the phone state changes 318 */ 319 private Handler mHandler = new Handler() { 320 @Override 321 public void handleMessage(Message msg) { 322 switch (msg.what) { 323 // disconnect action 324 // make sure to close the dialer on ALL disconnect actions. 325 case PHONE_DISCONNECT: 326 if (DBG) log("disconnect message recieved, shutting down."); 327 // unregister since we are closing. 328 mCM.unregisterForDisconnect(this); 329 closeDialer(false); 330 break; 331 case DTMF_SEND_CNF: 332 if (DBG) log("dtmf confirmation received from FW."); 333 // handle burst dtmf confirmation 334 handleBurstDtmfConfirmation(); 335 break; 336 } 337 } 338 }; 339 340 341 /** 342 * DTMFTwelveKeyDialer constructor. 343 * 344 * @param parent the InCallScreen instance that owns us. 345 * @param dialerView the DTMFTwelveKeyDialerView we should use to display the dialpad. 346 */ 347 public DTMFTwelveKeyDialer(InCallScreen parent, 348 DTMFTwelveKeyDialerView dialerView) { 349 if (DBG) log("DTMFTwelveKeyDialer constructor... this = " + this); 350 351 mInCallScreen = parent; 352 mCM = PhoneApp.getInstance().mCM; 353 354 // The passed-in DTMFTwelveKeyDialerView *should* always be 355 // non-null, now that the in-call UI uses only portrait mode. 356 if (dialerView == null) { 357 Log.e(LOG_TAG, "DTMFTwelveKeyDialer: null dialerView!", new IllegalStateException()); 358 // ...continue as best we can, although things will 359 // be pretty broken without the mDialerView UI elements! 360 } 361 mDialerView = dialerView; 362 if (DBG) log("- Got passed-in mDialerView: " + mDialerView); 363 364 if (mDialerView != null) { 365 mDialerView.setDialer(this); 366 367 // In the normal in-call DTMF dialpad, mDialpadDigits is an 368 // EditText used to display the digits the user has typed so 369 // far. But some other modes (like the OTA call) have no 370 // "digits" display at all, in which case mDialpadDigits will 371 // be null. 372 mDialpadDigits = (EditText) mDialerView.findViewById(R.id.dtmfDialerField); 373 if (mDialpadDigits != null) { 374 mDialerKeyListener = new DTMFKeyListener(); 375 mDialpadDigits.setKeyListener(mDialerKeyListener); 376 377 // remove the long-press context menus that support 378 // the edit (copy / paste / select) functions. 379 mDialpadDigits.setLongClickable(false); 380 } 381 382 // Hook up touch / key listeners for the buttons in the onscreen 383 // keypad. 384 setupKeypad(mDialerView); 385 } 386 } 387 388 /** 389 * Null out our reference to the InCallScreen activity. 390 * This indicates that the InCallScreen activity has been destroyed. 391 * At the same time, get rid of listeners since we're not going to 392 * be valid anymore. 393 */ 394 /* package */ void clearInCallScreenReference() { 395 if (DBG) log("clearInCallScreenReference()..."); 396 mInCallScreen = null; 397 mDialerKeyListener = null; 398 mHandler.removeMessages(DTMF_SEND_CNF); 399 synchronized (mDTMFQueue) { 400 mDTMFBurstCnfPending = false; 401 mDTMFQueue.clear(); 402 } 403 closeDialer(false); 404 } 405 406 /** 407 * Dialer code that runs when the dialer is brought up. 408 * This includes layout changes, etc, and just prepares the dialer model for use. 409 */ 410 private void onDialerOpen() { 411 if (DBG) log("onDialerOpen()..."); 412 413 // Any time the dialer is open, listen for "disconnect" events (so 414 // we can close ourself.) 415 mCM.registerForDisconnect(mHandler, PHONE_DISCONNECT, null); 416 417 // On some devices the screen timeout is set to a special value 418 // while the dialpad is up. 419 PhoneApp.getInstance().updateWakeState(); 420 421 // Give the InCallScreen a chance to do any necessary UI updates. 422 mInCallScreen.onDialerOpen(); 423 } 424 425 /** 426 * Allocates some resources we keep around during a "dialer session". 427 * 428 * (Currently, a "dialer session" just means any situation where we 429 * might need to play local DTMF tones, which means that we need to 430 * keep a ToneGenerator instance around. A ToneGenerator instance 431 * keeps an AudioTrack resource busy in AudioFlinger, so we don't want 432 * to keep it around forever.) 433 * 434 * Call {@link stopDialerSession} to release the dialer session 435 * resources. 436 */ 437 public void startDialerSession() { 438 if (DBG) log("startDialerSession()... this = " + this); 439 440 // see if we need to play local tones. 441 if (PhoneApp.getInstance().getResources().getBoolean(R.bool.allow_local_dtmf_tones)) { 442 mLocalToneEnabled = Settings.System.getInt(mInCallScreen.getContentResolver(), 443 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1; 444 } else { 445 mLocalToneEnabled = false; 446 } 447 if (DBG) log("- startDialerSession: mLocalToneEnabled = " + mLocalToneEnabled); 448 449 // create the tone generator 450 // if the mToneGenerator creation fails, just continue without it. It is 451 // a local audio signal, and is not as important as the dtmf tone itself. 452 if (mLocalToneEnabled) { 453 synchronized (mToneGeneratorLock) { 454 if (mToneGenerator == null) { 455 try { 456 mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF, 80); 457 } catch (RuntimeException e) { 458 if (DBG) log("Exception caught while creating local tone generator: " + e); 459 mToneGenerator = null; 460 } 461 } 462 } 463 } 464 } 465 466 /** 467 * Dialer code that runs when the dialer is closed. 468 * This releases resources acquired when we start the dialer. 469 */ 470 private void onDialerClose() { 471 if (DBG) log("onDialerClose()..."); 472 473 // reset back to a short delay for the poke lock. 474 PhoneApp app = PhoneApp.getInstance(); 475 app.updateWakeState(); 476 477 mCM.unregisterForDisconnect(mHandler); 478 479 // Give the InCallScreen a chance to do any necessary UI updates. 480 if (mInCallScreen != null) { 481 mInCallScreen.onDialerClose(); 482 } 483 } 484 485 /** 486 * Releases resources we keep around during a "dialer session" 487 * (see {@link startDialerSession}). 488 * 489 * It's safe to call this even without a corresponding 490 * startDialerSession call. 491 */ 492 public void stopDialerSession() { 493 // release the tone generator. 494 synchronized (mToneGeneratorLock) { 495 if (mToneGenerator != null) { 496 mToneGenerator.release(); 497 mToneGenerator = null; 498 } 499 } 500 } 501 502 /** 503 * Called externally (from InCallScreen) to play a DTMF Tone. 504 */ 505 public boolean onDialerKeyDown(KeyEvent event) { 506 if (DBG) log("Notifying dtmf key down."); 507 return mDialerKeyListener.onKeyDown(event); 508 } 509 510 /** 511 * Called externally (from InCallScreen) to cancel the last DTMF Tone played. 512 */ 513 public boolean onDialerKeyUp(KeyEvent event) { 514 if (DBG) log("Notifying dtmf key up."); 515 return mDialerKeyListener.onKeyUp(event); 516 } 517 518 /** 519 * setup the keys on the dialer activity, using the keymaps. 520 */ 521 private void setupKeypad(DTMFTwelveKeyDialerView dialerView) { 522 // for each view id listed in the displaymap 523 View button; 524 for (int viewId : mDisplayMap.keySet()) { 525 // locate the view 526 button = dialerView.findViewById(viewId); 527 // Setup the listeners for the buttons 528 button.setOnTouchListener(this); 529 button.setClickable(true); 530 button.setOnKeyListener(this); 531 } 532 } 533 534 /** 535 * catch the back and call buttons to return to the in call activity. 536 */ 537 public boolean onKeyDown(int keyCode, KeyEvent event) { 538 // if (DBG) log("onKeyDown: keyCode " + keyCode); 539 switch (keyCode) { 540 // finish for these events 541 case KeyEvent.KEYCODE_BACK: 542 case KeyEvent.KEYCODE_CALL: 543 if (DBG) log("exit requested"); 544 closeDialer(true); // do the "closing" animation 545 return true; 546 } 547 return mInCallScreen.onKeyDown(keyCode, event); 548 } 549 550 /** 551 * catch the back and call buttons to return to the in call activity. 552 */ 553 public boolean onKeyUp(int keyCode, KeyEvent event) { 554 // if (DBG) log("onKeyUp: keyCode " + keyCode); 555 return mInCallScreen.onKeyUp(keyCode, event); 556 } 557 558 /** 559 * Implemented for the TouchListener, process the touch events. 560 */ 561 public boolean onTouch(View v, MotionEvent event) { 562 int viewId = v.getId(); 563 564 // if the button is recognized 565 if (mDisplayMap.containsKey(viewId)) { 566 switch (event.getAction()) { 567 case MotionEvent.ACTION_DOWN: 568 // Append the character mapped to this button, to the display. 569 // start the tone 570 processDtmf(mDisplayMap.get(viewId)); 571 break; 572 case MotionEvent.ACTION_UP: 573 case MotionEvent.ACTION_CANCEL: 574 // stop the tone on ANY other event, except for MOVE. 575 stopTone(); 576 break; 577 } 578 // do not return true [handled] here, since we want the 579 // press / click animation to be handled by the framework. 580 } 581 return false; 582 } 583 584 /** 585 * Implements View.OnKeyListener for the DTMF buttons. Enables dialing with trackball/dpad. 586 */ 587 public boolean onKey(View v, int keyCode, KeyEvent event) { 588 // if (DBG) log("onKey: keyCode " + keyCode + ", view " + v); 589 590 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { 591 int viewId = v.getId(); 592 if (mDisplayMap.containsKey(viewId)) { 593 switch (event.getAction()) { 594 case KeyEvent.ACTION_DOWN: 595 if (event.getRepeatCount() == 0) { 596 processDtmf(mDisplayMap.get(viewId)); 597 } 598 break; 599 case KeyEvent.ACTION_UP: 600 stopTone(); 601 break; 602 } 603 // do not return true [handled] here, since we want the 604 // press / click animation to be handled by the framework. 605 } 606 } 607 return false; 608 } 609 610 /** 611 * @return true if the dialer is currently visible onscreen. 612 */ 613 // TODO: clean up naming inconsistency of "opened" vs. "visible". 614 // This should be called isVisible(), and open/closeDialer() should 615 // be "show" and "hide". 616 public boolean isOpened() { 617 // Return whether or not the dialer view is visible. 618 // (Note that if we're in the middle of a fade-out animation, that 619 // also counts as "not visible" even though mDialerView itself is 620 // technically still VISIBLE.) 621 return ((mDialerView.getVisibility() == View.VISIBLE) 622 && !CallCard.Fade.isFadingOut(mDialerView)); 623 } 624 625 /** 626 * Forces the dialer into the "open" state. 627 * Does nothing if the dialer is already open. 628 * 629 * @param animate if true, open the dialer with an animation. 630 */ 631 public void openDialer(boolean animate) { 632 if (DBG) log("openDialer()..."); 633 634 if (!isOpened()) { 635 // Make the dialer view visible. 636 if (animate) { 637 CallCard.Fade.show(mDialerView); 638 } else { 639 mDialerView.setVisibility(View.VISIBLE); 640 } 641 onDialerOpen(); 642 } 643 } 644 645 /** 646 * Forces the dialer into the "closed" state. 647 * Does nothing if the dialer is already closed. 648 * 649 * @param animate if true, close the dialer with an animation. 650 */ 651 public void closeDialer(boolean animate) { 652 if (DBG) log("closeDialer()..."); 653 654 if (isOpened()) { 655 // Hide the dialer view. 656 if (animate) { 657 CallCard.Fade.hide(mDialerView, View.GONE); 658 } else { 659 mDialerView.setVisibility(View.GONE); 660 } 661 onDialerClose(); 662 } 663 } 664 665 /** 666 * Processes the specified digit as a DTMF key, by playing the 667 * appropriate DTMF tone, and appending the digit to the EditText 668 * field that displays the DTMF digits sent so far. 669 */ 670 private final void processDtmf(char c) { 671 // if it is a valid key, then update the display and send the dtmf tone. 672 if (PhoneNumberUtils.is12Key(c)) { 673 if (DBG) log("updating display and sending dtmf tone for '" + c + "'"); 674 675 // Append this key to the "digits" widget. 676 if (mDialpadDigits != null) { 677 // TODO: maybe *don't* manually append this digit if 678 // mDialpadDigits is focused and this key came from the HW 679 // keyboard, since in that case the EditText field will 680 // get the key event directly and automatically appends 681 // whetever the user types. 682 // (Or, a cleaner fix would be to just make mDialpadDigits 683 // *not* handle HW key presses. That seems to be more 684 // complicated than just setting focusable="false" on it, 685 // though.) 686 mDialpadDigits.getText().append(c); 687 } 688 689 // Play the tone if it exists. 690 if (mToneMap.containsKey(c)) { 691 // begin tone playback. 692 startTone(c); 693 } 694 } else if (DBG) { 695 log("ignoring dtmf request for '" + c + "'"); 696 } 697 698 // Any DTMF keypress counts as explicit "user activity". 699 PhoneApp.getInstance().pokeUserActivity(); 700 } 701 702 /** 703 * Clears out the display of "DTMF digits typed so far" that's kept in 704 * mDialpadDigits. 705 * 706 * The InCallScreen is responsible for calling this method any time a 707 * new call becomes active (or, more simply, any time a call ends). 708 * This is how we make sure that the "history" of DTMF digits you type 709 * doesn't persist from one call to the next. 710 * 711 * TODO: it might be more elegent if the dialpad itself could remember 712 * the call that we're associated with, and clear the digits if the 713 * "current call" has changed since last time. (This would require 714 * some unique identifier that's different for each call. We can't 715 * just use the foreground Call object, since that's a singleton that 716 * lasts the whole life of the phone process. Instead, maybe look at 717 * the Connection object that comes back from getEarliestConnection()? 718 * Or getEarliestConnectTime()?) 719 * 720 * Or to be even fancier, we could keep a mapping of *multiple* 721 * "active calls" to DTMF strings. That way you could have two lines 722 * in use and swap calls multiple times, and we'd still remember the 723 * digits for each call. (But that's such an obscure use case that 724 * it's probably not worth the extra complexity.) 725 */ 726 public void clearDigits() { 727 if (DBG) log("clearDigits()..."); 728 729 if (mDialpadDigits != null) { 730 mDialpadDigits.setText(""); 731 } 732 } 733 734 /** 735 * Plays the local tone based the phone type. 736 */ 737 public void startTone(char c) { 738 // Only play the tone if it exists. 739 if (!mToneMap.containsKey(c)) { 740 return; 741 } 742 // Read the settings as it may be changed by the user during the call 743 Phone phone = mCM.getFgPhone(); 744 mShortTone = TelephonyCapabilities.useShortDtmfTones(phone, phone.getContext()); 745 746 if (DBG) log("startDtmfTone()..."); 747 748 // For Short DTMF we need to play the local tone for fixed duration 749 if (mShortTone) { 750 sendShortDtmfToNetwork(c); 751 } else { 752 // Pass as a char to be sent to network 753 Log.i(LOG_TAG, "send long dtmf for " + c); 754 mCM.startDtmf(c); 755 } 756 startLocalToneIfNeeded(c); 757 } 758 759 /** 760 * Plays the local tone based the phone type. 761 */ 762 public void startLocalToneIfNeeded(char c) { 763 // if local tone playback is enabled, start it. 764 // Only play the tone if it exists. 765 if (!mToneMap.containsKey(c)) { 766 return; 767 } 768 if (mLocalToneEnabled) { 769 synchronized (mToneGeneratorLock) { 770 if (mToneGenerator == null) { 771 if (DBG) log("startDtmfTone: mToneGenerator == null, tone: " + c); 772 } else { 773 if (DBG) log("starting local tone " + c); 774 int toneDuration = -1; 775 if (mShortTone) { 776 toneDuration = DTMF_DURATION_MS; 777 } 778 mToneGenerator.startTone(mToneMap.get(c), toneDuration); 779 } 780 } 781 } 782 } 783 784 /** 785 * Check to see if the keyEvent is dialable. 786 */ 787 boolean isKeyEventAcceptable (KeyEvent event) { 788 return (mDialerKeyListener != null && mDialerKeyListener.isKeyEventAcceptable(event)); 789 } 790 791 /** 792 * static logging method 793 */ 794 private static void log(String msg) { 795 Log.d(LOG_TAG, msg); 796 } 797 798 /** 799 * Stops the local tone based on the phone type. 800 */ 801 public void stopTone() { 802 if (!mShortTone) { 803 if (DBG) log("stopping remote tone."); 804 mCM.stopDtmf(); 805 stopLocalToneIfNeeded(); 806 } 807 } 808 809 /** 810 * Stops the local tone based on the phone type. 811 */ 812 public void stopLocalToneIfNeeded() { 813 if (!mShortTone) { 814 if (DBG) log("stopping remote tone."); 815 // if local tone playback is enabled, stop it. 816 if (DBG) log("trying to stop local tone..."); 817 if (mLocalToneEnabled) { 818 synchronized (mToneGeneratorLock) { 819 if (mToneGenerator == null) { 820 if (DBG) log("stopLocalTone: mToneGenerator == null"); 821 } else { 822 if (DBG) log("stopping local tone."); 823 mToneGenerator.stopTone(); 824 } 825 } 826 } 827 } 828 } 829 830 /** 831 * Sends the dtmf character over the network for short DTMF settings 832 * When the characters are entered in quick succession, 833 * the characters are queued before sending over the network. 834 */ 835 private void sendShortDtmfToNetwork(char dtmfDigit) { 836 synchronized (mDTMFQueue) { 837 if (mDTMFBurstCnfPending == true) { 838 // Insert the dtmf char to the queue 839 mDTMFQueue.add(new Character(dtmfDigit)); 840 } else { 841 String dtmfStr = Character.toString(dtmfDigit); 842 Log.i(LOG_TAG, "dtmfsent = " + dtmfStr); 843 mCM.sendBurstDtmf(dtmfStr, 0, 0, mHandler.obtainMessage(DTMF_SEND_CNF)); 844 // Set flag to indicate wait for Telephony confirmation. 845 mDTMFBurstCnfPending = true; 846 } 847 } 848 } 849 850 /** 851 * Handles Burst Dtmf Confirmation from the Framework. 852 */ 853 void handleBurstDtmfConfirmation() { 854 Character dtmfChar = null; 855 synchronized (mDTMFQueue) { 856 mDTMFBurstCnfPending = false; 857 if (!mDTMFQueue.isEmpty()) { 858 dtmfChar = mDTMFQueue.remove(); 859 Log.i(LOG_TAG, "The dtmf character removed from queue" + dtmfChar); 860 } 861 } 862 if (dtmfChar != null) { 863 sendShortDtmfToNetwork(dtmfChar); 864 } 865 } 866 } 867