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