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