DTMFTwelveKeyDialer.java revision abc47110c17fa8e8cb6161bc045e87f31eeb7a1c
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.Call; 21import com.android.internal.telephony.CallerInfo; 22import com.android.internal.telephony.CallerInfoAsyncQuery; 23import com.android.internal.telephony.Connection; 24import com.android.internal.telephony.Phone; 25import com.android.internal.widget.SlidingDrawer; 26 27import android.media.AudioManager; 28import android.media.ToneGenerator; 29import android.os.Handler; 30import android.os.Message; 31import android.os.SystemClock; 32import android.provider.Settings; 33import android.telephony.PhoneNumberUtils; 34import android.text.Editable; 35import android.text.method.DialerKeyListener; 36import android.util.Log; 37import android.view.KeyEvent; 38import android.view.MotionEvent; 39import android.view.View; 40import android.view.WindowManager; 41import android.widget.Chronometer; 42import android.widget.EditText; 43import android.widget.ImageView; 44import android.widget.LinearLayout; 45import android.widget.TextView; 46 47import java.util.HashMap; 48 49/** 50 * Dialer class that encapsulates the DTMF twelve key behaviour. 51 * This model backs up the UI behaviour in DTMFTwelveKeyDialerView.java. 52 */ 53public class DTMFTwelveKeyDialer implements 54 CallerInfoAsyncQuery.OnQueryCompleteListener, 55 SlidingDrawer.OnDrawerOpenListener, 56 SlidingDrawer.OnDrawerCloseListener, 57 View.OnClickListener, 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 private LinearLayout mStatusView; 117 private ImageView mStatusCallIcon; 118 private Chronometer mStatusCallTime; 119 private TextView mStatusCallState; 120 private TextView mStatusCallerName; 121 122 // InCallScreen reference. 123 private InCallScreen mInCallScreen; 124 125 // SlidingDrawer reference. 126 private SlidingDrawer mDialerContainer; 127 128 // view reference 129 private DTMFTwelveKeyDialerView mDialerView; 130 131 // key listner reference, may or may not be attached to a view. 132 private DTMFKeyListener mDialerKeyListener; 133 134 /** 135 * Our own key listener, specialized for dealing with DTMF codes. 136 * 1. Ignore the backspace since it is irrelevant. 137 * 2. Allow ONLY valid DTMF characters to generate a tone and be 138 * sent as a DTMF code. 139 * 3. All other remaining characters are handled by the superclass. 140 */ 141 private class DTMFKeyListener extends DialerKeyListener { 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 * Overriden so that with each valid button press, we start sending 160 * a dtmf code and play a local dtmf tone. 161 */ 162 @Override 163 public boolean onKeyDown(View view, Editable content, 164 int keyCode, KeyEvent event) { 165 // find the character 166 char c = (char) lookup(event, content); 167 168 // if not a long press, and parent onKeyDown accepts the input 169 if (event.getRepeatCount() == 0 && super.onKeyDown(view, content, keyCode, event)) { 170 171 // if the character is a valid dtmf code, start playing the tone and send the 172 // code. 173 if (ok(getAcceptedChars(), c)) { 174 if (DBG) log("DTMFKeyListener reading '" + c + "' from input."); 175 playTone(c); 176 } else if (DBG) { 177 log("DTMFKeyListener rejecting '" + c + "' from input."); 178 } 179 return true; 180 } 181 return false; 182 } 183 184 /** 185 * Overriden so that with each valid button up, we stop sending 186 * a dtmf code and the dtmf tone. 187 */ 188 @Override 189 public boolean onKeyUp(View view, Editable content, 190 int keyCode, KeyEvent event) { 191 192 super.onKeyUp(view, content, keyCode, event); 193 194 // find the character 195 char c = (char) lookup(event, content); 196 197 if (ok(getAcceptedChars(), c)) { 198 if (DBG) log("Stopping the tone for '" + c + "'"); 199 stopTone(); 200 return true; 201 } 202 203 return false; 204 } 205 206 /** 207 * Handle individual keydown events when we DO NOT have an Editable handy. 208 */ 209 public boolean onKeyDown(KeyEvent event) { 210 char c = lookup (event); 211 if (DBG) log("recieved keydown for '" + c + "'"); 212 213 // if not a long press, and parent onKeyDown accepts the input 214 if (event.getRepeatCount() == 0 && c != 0) { 215 // if the character is a valid dtmf code, start playing the tone and send the 216 // code. 217 if (ok(getAcceptedChars(), c)) { 218 if (DBG) log("DTMFKeyListener reading '" + c + "' from input."); 219 playTone(c); 220 return true; 221 } else if (DBG) { 222 log("DTMFKeyListener rejecting '" + c + "' from input."); 223 } 224 } 225 return false; 226 } 227 228 /** 229 * Handle individual keyup events. 230 * 231 * @param event is the event we are trying to stop. If this is null, 232 * then we just force-stop the last tone without checking if the event 233 * is an acceptable dialer event. 234 */ 235 public boolean onKeyUp(KeyEvent event) { 236 if (event == null) { 237 if (DBG) log("Stopping the last played tone."); 238 stopTone(); 239 return true; 240 } 241 242 char c = lookup (event); 243 if (DBG) log("recieved keyup for '" + c + "'"); 244 245 // TODO: stopTone does not take in character input, we may want to 246 // consider checking for this ourselves. 247 if (ok(getAcceptedChars(), c)) { 248 if (DBG) log("Stopping the tone for '" + c + "'"); 249 stopTone(); 250 return true; 251 } 252 253 return false; 254 } 255 256 /** 257 * Find the Dialer Key mapped to this event. 258 * 259 * @return The char value of the input event, otherwise 260 * 0 if no matching character was found. 261 */ 262 private char lookup (KeyEvent event) { 263 // This code is similar to {@link DialerKeyListener#lookup(KeyEvent, Spannable) lookup} 264 int meta = event.getMetaState(); 265 int number = event.getNumber(); 266 267 if (!((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) || (number == 0)) { 268 int match = event.getMatch(getAcceptedChars(), meta); 269 number = (match != 0) ? match : number; 270 } 271 272 return (char) number; 273 } 274 275 /** 276 * Check to see if the keyEvent is dialable. 277 */ 278 boolean isKeyEventAcceptable (KeyEvent event) { 279 return (ok(getAcceptedChars(), lookup(event))); 280 } 281 282 /** 283 * Overrides the characters used in {@link DialerKeyListener#CHARACTERS} 284 * These are the valid dtmf characters. 285 */ 286 public final char[] DTMF_CHARACTERS = new char[] { 287 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*' 288 }; 289 } 290 291 /** 292 * Our own handler to take care of the messages from the phone state changes 293 */ 294 private Handler mHandler = new Handler () { 295 @Override 296 public void handleMessage(Message msg) { 297 switch (msg.what) { 298 // disconnect action 299 // make sure to close the dialer on ALL disconnect actions. 300 case PHONE_DISCONNECT: 301 if (DBG) log("disconnect message recieved, shutting down."); 302 // unregister since we are closing. 303 mPhone.unregisterForDisconnect(this); 304 closeDialer(false); 305 break; 306 } 307 } 308 }; 309 310 311 DTMFTwelveKeyDialer (InCallScreen parent) { 312 mInCallScreen = parent; 313 mPhone = ((PhoneApp) mInCallScreen.getApplication()).phone; 314 mDialerContainer = (SlidingDrawer) mInCallScreen.findViewById(R.id.dialer_container); 315 mDialerContainer.setOnDrawerOpenListener(this); 316 mDialerContainer.setOnDrawerCloseListener(this); 317 mDialerKeyListener = new DTMFKeyListener(); 318 } 319 320 /** 321 * Null out our reference to the InCallScreen activity. 322 * This indicates that the InCallScreen activity has been destroyed. 323 * At the same time, get rid of listeners since we're not going to 324 * be valid anymore. 325 */ 326 void clearInCallScreenReference() { 327 mInCallScreen = null; 328 mDialerKeyListener = null; 329 mDialerContainer.setOnDrawerOpenListener(null); 330 mDialerContainer.setOnDrawerCloseListener(null); 331 closeDialer(false); 332 } 333 334 LinearLayout getView() { 335 return mDialerView; 336 } 337 338 /** 339 * Dialer code that runs when the dialer is brought up. 340 * This includes layout changes, etc, and just prepares the dialer model for use. 341 */ 342 void onDialerOpen() { 343 if (DBG) log("initMenu()..."); 344 345 // inflate the view. 346 mDialerView = (DTMFTwelveKeyDialerView) mInCallScreen.findViewById(R.id.dtmf_dialer); 347 mDialerView.setDialer(this); 348 349 // Have the WindowManager filter out cheek touch events 350 mInCallScreen.getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES); 351 352 // TODO: Need new assets, Hide the voicemail icon. 353 /* 354 View v = mDialerView.findViewById(R.id.oneVoicemailIcon); 355 if (v != null) { 356 v.setVisibility(View.GONE); 357 } 358 */ 359 360 mPhone.registerForDisconnect(mHandler, PHONE_DISCONNECT, null); 361 362 // set to a longer delay while the dialer is up. 363 mInCallScreen.updateWakeState(); 364 365 // setup the digit display 366 mDigits = (EditText) mDialerView.findViewById(R.id.digits); 367 mDigits.setKeyListener(new DTMFKeyListener()); 368 mDigits.requestFocus(); 369 370 // remove the long-press context menus that support 371 // the edit (copy / paste / select) functions. 372 mDigits.setLongClickable(false); 373 374 // setup the status view 375 mStatusView = (LinearLayout) mDialerView.findViewById(R.id.status_view); 376 mStatusView.setOnClickListener(this); 377 378 // get a handle to the relevant UI widgets 379 mStatusCallIcon = (ImageView) mDialerView.findViewById(R.id.status_call_icon); 380 mStatusCallTime = (Chronometer) mDialerView.findViewById(R.id.status_call_time); 381 mStatusCallState = (TextView) mDialerView.findViewById(R.id.status_call_state); 382 mStatusCallerName = (TextView) mDialerView.findViewById(R.id.status_caller_name); 383 384 // Check for the presence of the keypad (portrait mode) 385 View view = mDialerView.findViewById(R.id.one); 386 if (view != null) { 387 if (DBG) log("portrait mode setup"); 388 setupKeypad(); 389 } else { 390 if (DBG) log("landscape mode setup"); 391 // Adding hint text to the field to indicate that keyboard 392 // is needed while in landscape mode. 393 mDigits.setHint(R.string.dialerKeyboardHintText); 394 } 395 396 // update the status screen 397 updateStatus(); 398 399 // setup the local tone generator. 400 startDialerSession(); 401 } 402 403 /** 404 * Setup the local tone generator. Should have corresponding calls to 405 * {@link onDialerPause}. 406 */ 407 public void startDialerSession() { 408 // see if we need to play local tones. 409 mDTMFToneEnabled = Settings.System.getInt(mInCallScreen.getContentResolver(), 410 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1; 411 412 // create the tone generator 413 // if the mToneGenerator creation fails, just continue without it. It is 414 // a local audio signal, and is not as important as the dtmf tone itself. 415 if (mDTMFToneEnabled) { 416 synchronized (mToneGeneratorLock) { 417 if (mToneGenerator == null) { 418 try { 419 mToneGenerator = new ToneGenerator(AudioManager.STREAM_RING, 80); 420 } catch (RuntimeException e) { 421 if (DBG) log("Exception caught while creating local tone generator: " + e); 422 mToneGenerator = null; 423 } 424 } 425 } 426 } 427 } 428 429 /** 430 * Dialer code that runs when the dialer is closed. 431 * This releases resources acquired when we start the dialer. 432 */ 433 public void onDialerClose() { 434 // reset back to a short delay for the poke lock. 435 mInCallScreen.updateWakeState(); 436 437 mPhone.unregisterForDisconnect(mHandler); 438 439 if (mStatusCallTime != null) { 440 mStatusCallTime.stop(); 441 } 442 443 stopDialerSession(); 444 } 445 446 /** 447 * Tear down the local tone generator, corresponds to calls to 448 * {@link onDialerResume} 449 */ 450 public void stopDialerSession() { 451 // release the tone generator. 452 synchronized (mToneGeneratorLock) { 453 if (mToneGenerator != null) { 454 mToneGenerator.release(); 455 mToneGenerator = null; 456 } 457 } 458 } 459 460 /** 461 * update the status display, code taken mostly from 462 * NotificationMgr.updateInCallNotification 463 */ 464 private void updateStatus () { 465 if (DBG) log("updating status display"); 466 467 // get statuses 468 final boolean hasActiveCall = !mPhone.getForegroundCall().isIdle(); 469 final boolean hasHoldingCall = !mPhone.getBackgroundCall().isIdle(); 470 471 // figure out which icon to display 472 int resId = (!hasActiveCall && hasHoldingCall) ? 473 android.R.drawable.stat_sys_phone_call_on_hold : 474 android.R.drawable.stat_sys_phone_call; 475 476 // get the current connected call. 477 Call currentCall = hasActiveCall ? mPhone.getForegroundCall() 478 : mPhone.getBackgroundCall(); 479 Connection currentConn = currentCall.getEarliestConnection(); 480 481 // update the information about the current connection (chronometer) 482 // only if the current connection exists. 483 if (currentConn != null) { 484 // figure out the elapsed time 485 long callDurationMsec = currentConn.getDurationMillis(); 486 long chronometerBaseTime = SystemClock.elapsedRealtime() - callDurationMsec; 487 488 // figure out the call status to display. 489 String statusInfo; 490 if (hasHoldingCall && !hasActiveCall) { 491 statusInfo = mInCallScreen.getString(R.string.onHold); 492 493 // hide the timer while on hold. 494 mStatusCallTime.setVisibility(View.INVISIBLE); 495 } else { 496 statusInfo = mInCallScreen.getString(R.string.ongoing); 497 498 // setup and start the status timer. 499 mStatusCallTime.setVisibility(View.VISIBLE); 500 mStatusCallTime.setBase(chronometerBaseTime); 501 mStatusCallTime.start(); 502 } 503 504 // display the call state 505 mStatusCallState.setText(statusInfo); 506 507 } else if (DBG) { 508 log("updateStatus: connection is null, call display not updated."); 509 } 510 511 // this code is independent of the chronometer code; used to 512 // display the caller name. 513 // TODO: it may not make sense for every point to make separate 514 // checks for isConferenceCall, so we need to think about 515 // possibly including this in startGetCallerInfo or some other 516 // common point. 517 if (PhoneUtils.isConferenceCall(currentCall)) { 518 // if this is a conference call, just use that as the caller name. 519 mStatusCallerName.setText(R.string.card_title_conf_call); 520 } else { 521 // get the caller name. 522 PhoneUtils.CallerInfoToken cit = 523 PhoneUtils.startGetCallerInfo(mInCallScreen, currentCall, this, 524 mStatusCallerName); 525 mStatusCallerName.setText( 526 PhoneUtils.getCompactNameFromCallerInfo(cit.currentInfo, mInCallScreen)); 527 } 528 529 // set the icon 530 mStatusCallIcon.setImageResource(resId); 531 } 532 533 /** 534 * upon completion of the query, update the name field in the status. 535 */ 536 public void onQueryComplete(int token, Object cookie, CallerInfo ci){ 537 if (DBG) log("callerinfo query complete, updating ui."); 538 539 ((TextView) cookie).setText(PhoneUtils.getCompactNameFromCallerInfo(ci, mInCallScreen)); 540 } 541 542 /** 543 * Called externally (from InCallScreen) to play a DTMF Tone. 544 */ 545 public boolean onDialerKeyDown(KeyEvent event) { 546 if (DBG) log("Notifying dtmf key down."); 547 return mDialerKeyListener.onKeyDown(event); 548 } 549 550 /** 551 * Called externally (from InCallScreen) to cancel the last DTMF Tone played. 552 */ 553 public boolean onDialerKeyUp(KeyEvent event) { 554 if (DBG) log("Notifying dtmf key up."); 555 return mDialerKeyListener.onKeyUp(event); 556 } 557 558 /** 559 * setup the keys on the dialer activity, using the keymaps. 560 */ 561 private void setupKeypad() { 562 // for each view id listed in the displaymap 563 View button; 564 for (int viewId : mDisplayMap.keySet()) { 565 // locate the view 566 button = mDialerView.findViewById(viewId); 567 // Setup the listeners for the buttons 568 button.setOnTouchListener(this); 569 button.setClickable(true); 570 button.setOnKeyListener(this); 571 } 572 } 573 574 /** 575 * catch the back and call buttons to return to the in call activity. 576 */ 577 public boolean onKeyDown(int keyCode, KeyEvent event) { 578 switch (keyCode) { 579 // finish for these events 580 case KeyEvent.KEYCODE_BACK: 581 case KeyEvent.KEYCODE_CALL: 582 if (DBG) log("exit requested"); 583 closeDialer(true); // do the "closing" animation 584 return true; 585 } 586 return mInCallScreen.onKeyDown(keyCode, event); 587 } 588 589 /** 590 * catch the back and call buttons to return to the in call activity. 591 */ 592 public boolean onKeyUp(int keyCode, KeyEvent event) { 593 return mInCallScreen.onKeyUp(keyCode, event); 594 } 595 596 /** 597 * for clicklistener, process the incoming button presses. 598 */ 599 public void onClick(View view) { 600 if (view == mStatusView) { 601 if (DBG) log("exit requested from status view"); 602 closeDialer(true); // do the "closing" animation 603 } 604 } 605 606 /** 607 * Implemented for the TouchListener, process the touch events. 608 */ 609 public boolean onTouch(View v, MotionEvent event) { 610 int viewId = v.getId(); 611 612 // if the button is recognized 613 if (mDisplayMap.containsKey(viewId)) { 614 switch (event.getAction()) { 615 case MotionEvent.ACTION_DOWN: 616 // Append the character mapped to this button, to the display. 617 // start the tone 618 appendDigit(mDisplayMap.get(viewId)); 619 break; 620 case MotionEvent.ACTION_UP: 621 case MotionEvent.ACTION_CANCEL: 622 // stop the tone on ANY other event, except for MOVE. 623 stopTone(); 624 break; 625 } 626 // do not return true [handled] here, since we want the 627 // press / click animation to be handled by the framework. 628 } 629 return false; 630 } 631 632 /** 633 * Implements View.OnKeyListener for the DTMF buttons. Enables dialing with trackball/dpad. 634 */ 635 public boolean onKey(View v, int keyCode, KeyEvent event) { 636 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { 637 int viewId = v.getId(); 638 if (mDisplayMap.containsKey(viewId)) { 639 switch (event.getAction()) { 640 case KeyEvent.ACTION_DOWN: 641 if (event.getRepeatCount() == 0) { 642 appendDigit(mDisplayMap.get(viewId)); 643 } 644 break; 645 case KeyEvent.ACTION_UP: 646 stopTone(); 647 break; 648 } 649 // do not return true [handled] here, since we want the 650 // press / click animation to be handled by the framework. 651 } 652 } 653 return false; 654 } 655 656 /** 657 * @return true if the dialer is currently opened (i.e. expanded). 658 */ 659 public boolean isOpened() { 660 return mDialerContainer.isOpened(); 661 } 662 663 /** 664 * Forces the dialer into the "open" state. 665 * Does nothing if the dialer is already open. 666 * 667 * @param animate if true, open the dialer with an animation. 668 */ 669 public void openDialer(boolean animate) { 670 if (!mDialerContainer.isOpened()) { 671 if (animate) { 672 mDialerContainer.animateToggle(); 673 } else { 674 mDialerContainer.toggle(); 675 } 676 } 677 } 678 679 /** 680 * Forces the dialer into the "closed" state. 681 * Does nothing if the dialer is already closed. 682 * 683 * @param animate if true, close the dialer with an animation. 684 */ 685 public void closeDialer(boolean animate) { 686 if (mDialerContainer.isOpened()) { 687 if (animate) { 688 mDialerContainer.animateToggle(); 689 } else { 690 mDialerContainer.toggle(); 691 } 692 } 693 } 694 695 /** 696 * Implemented for the SlidingDrawer open listener, prepare the dialer. 697 */ 698 public void onDrawerOpened() { 699 onDialerOpen(); 700 } 701 702 /** 703 * Implemented for the SlidingDrawer close listener, release the dialer. 704 */ 705 public void onDrawerClosed() { 706 onDialerClose(); 707 } 708 709 /** 710 * update the text area and playback the tone. 711 */ 712 private final void appendDigit(char c) { 713 // if it is a valid key, then update the display and send the dtmf tone. 714 if (PhoneNumberUtils.is12Key(c)) { 715 if (DBG) log("updating display and sending dtmf tone for '" + c + "'"); 716 717 if (mDigits != null) { 718 mDigits.getText().append(c); 719 } 720 // play the tone if it exists. 721 if (mToneMap.containsKey(c)) { 722 // begin tone playback. 723 playTone(c); 724 } 725 } else if (DBG) { 726 log("ignoring dtmf request for '" + c + "'"); 727 } 728 729 } 730 731 /** 732 * Start playing a DTMF tone, also begin the local tone playback if it is 733 * enabled. 734 * 735 * @param tone a tone code from {@link ToneGenerator} 736 */ 737 void playTone(char tone) { 738 if (DBG) log("starting remote tone."); 739 PhoneApp.getInstance().phone.startDtmf(tone); 740 741 // if local tone playback is enabled, start it. 742 if (mDTMFToneEnabled) { 743 synchronized (mToneGeneratorLock) { 744 if (mToneGenerator == null) { 745 if (DBG) log("playTone: mToneGenerator == null, tone: " + tone); 746 } else { 747 if (DBG) log("starting local tone " + tone); 748 mToneGenerator.startTone(mToneMap.get(tone)); 749 } 750 } 751 } 752 } 753 754 /** 755 * Stop playing the current DTMF tone. 756 * 757 * The ToneStopper class (similar to that in {@link TwelveKeyDialer#mToneStopper}) 758 * has been removed in favor of synchronous start / stop calls since tone duration 759 * is now a function of the input. 760 */ 761 void stopTone() { 762 if (DBG) log("stopping remote tone."); 763 PhoneApp.getInstance().phone.stopDtmf(); 764 765 // if local tone playback is enabled, stop it. 766 if (DBG) log("trying to stop local tone..."); 767 if (mDTMFToneEnabled) { 768 synchronized (mToneGeneratorLock) { 769 if (mToneGenerator == null) { 770 if (DBG) log("stopTone: mToneGenerator == null"); 771 } else { 772 if (DBG) log("stopping local tone."); 773 mToneGenerator.stopTone(); 774 } 775 } 776 } 777 } 778 779 /** 780 * Check to see if the keyEvent is dialable. 781 */ 782 boolean isKeyEventAcceptable (KeyEvent event) { 783 return (mDialerKeyListener != null && mDialerKeyListener.isKeyEventAcceptable(event)); 784 } 785 786 /** 787 * static logging method 788 */ 789 private static void log(String msg) { 790 Log.d(LOG_TAG, msg); 791 } 792} 793