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.Phone; 39 40 import java.util.HashMap; 41 import java.util.LinkedList; 42 import 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 */ 49 public 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 if (mInCallScreen != null) { 555 mInCallScreen.onDialerClose(); 556 } 557 } 558 559 /** 560 * Releases resources we keep around during a "dialer session" 561 * (see {@link startDialerSession}). 562 * 563 * It's safe to call this even without a corresponding 564 * startDialerSession call. 565 */ 566 public void stopDialerSession() { 567 // release the tone generator. 568 synchronized (mToneGeneratorLock) { 569 if (mToneGenerator != null) { 570 mToneGenerator.release(); 571 mToneGenerator = null; 572 } 573 } 574 } 575 576 /** 577 * Called externally (from InCallScreen) to play a DTMF Tone. 578 */ 579 public boolean onDialerKeyDown(KeyEvent event) { 580 if (DBG) log("Notifying dtmf key down."); 581 return mDialerKeyListener.onKeyDown(event); 582 } 583 584 /** 585 * Called externally (from InCallScreen) to cancel the last DTMF Tone played. 586 */ 587 public boolean onDialerKeyUp(KeyEvent event) { 588 if (DBG) log("Notifying dtmf key up."); 589 return mDialerKeyListener.onKeyUp(event); 590 } 591 592 /** 593 * setup the keys on the dialer activity, using the keymaps. 594 */ 595 private void setupKeypad(DTMFTwelveKeyDialerView dialerView) { 596 // for each view id listed in the displaymap 597 View button; 598 for (int viewId : mDisplayMap.keySet()) { 599 // locate the view 600 button = dialerView.findViewById(viewId); 601 // Setup the listeners for the buttons 602 button.setOnTouchListener(this); 603 button.setClickable(true); 604 button.setOnKeyListener(this); 605 } 606 } 607 608 /** 609 * catch the back and call buttons to return to the in call activity. 610 */ 611 public boolean onKeyDown(int keyCode, KeyEvent event) { 612 // if (DBG) log("onKeyDown: keyCode " + keyCode); 613 switch (keyCode) { 614 // finish for these events 615 case KeyEvent.KEYCODE_BACK: 616 case KeyEvent.KEYCODE_CALL: 617 if (DBG) log("exit requested"); 618 closeDialer(true); // do the "closing" animation 619 return true; 620 } 621 return mInCallScreen.onKeyDown(keyCode, event); 622 } 623 624 /** 625 * catch the back and call buttons to return to the in call activity. 626 */ 627 public boolean onKeyUp(int keyCode, KeyEvent event) { 628 // if (DBG) log("onKeyUp: keyCode " + keyCode); 629 return mInCallScreen.onKeyUp(keyCode, event); 630 } 631 632 /** 633 * Implemented for the TouchListener, process the touch events. 634 */ 635 public boolean onTouch(View v, MotionEvent event) { 636 int viewId = v.getId(); 637 638 // if the button is recognized 639 if (mDisplayMap.containsKey(viewId)) { 640 switch (event.getAction()) { 641 case MotionEvent.ACTION_DOWN: 642 // Append the character mapped to this button, to the display. 643 // start the tone 644 processDtmf(mDisplayMap.get(viewId)); 645 break; 646 case MotionEvent.ACTION_UP: 647 case MotionEvent.ACTION_CANCEL: 648 // stop the tone on ANY other event, except for MOVE. 649 stopTone(); 650 break; 651 } 652 // do not return true [handled] here, since we want the 653 // press / click animation to be handled by the framework. 654 } 655 return false; 656 } 657 658 /** 659 * Implements View.OnKeyListener for the DTMF buttons. Enables dialing with trackball/dpad. 660 */ 661 public boolean onKey(View v, int keyCode, KeyEvent event) { 662 // if (DBG) log("onKey: keyCode " + keyCode + ", view " + v); 663 664 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { 665 int viewId = v.getId(); 666 if (mDisplayMap.containsKey(viewId)) { 667 switch (event.getAction()) { 668 case KeyEvent.ACTION_DOWN: 669 if (event.getRepeatCount() == 0) { 670 processDtmf(mDisplayMap.get(viewId)); 671 } 672 break; 673 case KeyEvent.ACTION_UP: 674 stopTone(); 675 break; 676 } 677 // do not return true [handled] here, since we want the 678 // press / click animation to be handled by the framework. 679 } 680 } 681 return false; 682 } 683 684 /** 685 * @return true if the dialer is currently visible onscreen. 686 */ 687 // TODO: clean up naming inconsistency of "opened" vs. "visible". 688 // This should be called isVisible(), and open/closeDialer() should 689 // be "show" and "hide". 690 public boolean isOpened() { 691 if (mDialerDrawer != null) { 692 // If we're using a SlidingDrawer, report whether or not the 693 // drawer is open. 694 return mDialerDrawer.isOpened(); 695 } else { 696 // Otherwise, return whether or not the dialer view is visible. 697 return mDialerView.getVisibility() == View.VISIBLE; 698 } 699 } 700 701 /** 702 * @return true if we're using the style of dialpad that's contained 703 * within a SlidingDrawer. 704 */ 705 public boolean usingSlidingDrawer() { 706 return (mDialerDrawer != null); 707 } 708 709 /** 710 * Forces the dialer into the "open" state. 711 * Does nothing if the dialer is already open. 712 * 713 * @param animate if true, open the dialer with an animation. 714 */ 715 public void openDialer(boolean animate) { 716 if (DBG) log("openDialer()..."); 717 718 if (!isOpened()) { 719 if (mDialerDrawer != null) { 720 // If we're using a SlidingDrawer, open the drawer. 721 if (animate) { 722 mDialerDrawer.animateToggle(); 723 } else { 724 mDialerDrawer.toggle(); 725 } 726 } else { 727 // If we're not using a SlidingDrawer, just make 728 // the dialer view visible. 729 // TODO: add a fade-in animation if "animate" is true? 730 mDialerView.setVisibility(View.VISIBLE); 731 732 // And since we're not using a SlidingDrawer, we won't get an 733 // onDrawerOpened() event, so we have to to manually trigger 734 // an onDialerOpen() call. 735 onDialerOpen(); 736 } 737 } 738 } 739 740 /** 741 * Forces the dialer into the "closed" state. 742 * Does nothing if the dialer is already closed. 743 * 744 * @param animate if true, close the dialer with an animation. 745 */ 746 public void closeDialer(boolean animate) { 747 if (DBG) log("closeDialer()..."); 748 749 if (isOpened()) { 750 if (mDialerDrawer != null) { 751 // If we're using a SlidingDrawer, close the drawer. 752 if (animate) { 753 mDialerDrawer.animateToggle(); 754 } else { 755 mDialerDrawer.toggle(); 756 } 757 } else { 758 // If we're not using a SlidingDrawer, just hide 759 // the dialer view. 760 // TODO: add a fade-out animation if "animate" is true? 761 mDialerView.setVisibility(View.GONE); 762 763 // And since we're not using a SlidingDrawer, we won't get an 764 // onDrawerClosed() event, so we have to to manually trigger 765 // an onDialerClose() call. 766 onDialerClose(); 767 } 768 } 769 } 770 771 /** 772 * Sets the visibility of the dialpad's onscreen "handle". 773 * This has no effect on platforms that don't use 774 * a SlidingDrawer as a container for the dialpad. 775 */ 776 public void setHandleVisible(boolean visible) { 777 if (mDialerDrawer != null) { 778 mDialerDrawer.setVisibility(visible ? View.VISIBLE : View.GONE); 779 } 780 } 781 782 /** 783 * Implemented for the SlidingDrawer open listener, prepare the dialer. 784 */ 785 public void onDrawerOpened() { 786 onDialerOpen(); 787 } 788 789 /** 790 * Implemented for the SlidingDrawer close listener, release the dialer. 791 */ 792 public void onDrawerClosed() { 793 onDialerClose(); 794 } 795 796 /** 797 * Processes the specified digit as a DTMF key, by playing the 798 * appropriate DTMF tone, and appending the digit to the EditText 799 * field that displays the DTMF digits sent so far. 800 */ 801 private final void processDtmf(char c) { 802 // if it is a valid key, then update the display and send the dtmf tone. 803 if (PhoneNumberUtils.is12Key(c)) { 804 if (DBG) log("updating display and sending dtmf tone for '" + c + "'"); 805 806 // Append this key to the "digits" widget. 807 if (mDialpadDigits != null) { 808 // TODO: maybe *don't* manually append this digit if 809 // mDialpadDigits is focused and this key came from the HW 810 // keyboard, since in that case the EditText field will 811 // get the key event directly and automatically appends 812 // whetever the user types. 813 // (Or, a cleaner fix would be to just make mDialpadDigits 814 // *not* handle HW key presses. That seems to be more 815 // complicated than just setting focusable="false" on it, 816 // though.) 817 mDialpadDigits.getText().append(c); 818 } 819 820 // Play the tone if it exists. 821 if (mToneMap.containsKey(c)) { 822 // begin tone playback. 823 startTone(c); 824 } 825 } else if (DBG) { 826 log("ignoring dtmf request for '" + c + "'"); 827 } 828 829 // Any DTMF keypress counts as explicit "user activity". 830 PhoneApp.getInstance().pokeUserActivity(); 831 } 832 833 /** 834 * Clears out the display of "DTMF digits typed so far" that's kept in 835 * mDialpadDigits. 836 * 837 * The InCallScreen is responsible for calling this method any time a 838 * new call becomes active (or, more simply, any time a call ends). 839 * This is how we make sure that the "history" of DTMF digits you type 840 * doesn't persist from one call to the next. 841 * 842 * TODO: it might be more elegent if the dialpad itself could remember 843 * the call that we're associated with, and clear the digits if the 844 * "current call" has changed since last time. (This would require 845 * some unique identifier that's different for each call. We can't 846 * just use the foreground Call object, since that's a singleton that 847 * lasts the whole life of the phone process. Instead, maybe look at 848 * the Connection object that comes back from getEarliestConnection()? 849 * Or getEarliestConnectTime()?) 850 * 851 * Or to be even fancier, we could keep a mapping of *multiple* 852 * "active calls" to DTMF strings. That way you could have two lines 853 * in use and swap calls multiple times, and we'd still remember the 854 * digits for each call. (But that's such an obscure use case that 855 * it's probably not worth the extra complexity.) 856 */ 857 public void clearDigits() { 858 if (DBG) log("clearDigits()..."); 859 860 if (mDialpadDigits != null) { 861 mDialpadDigits.setText(""); 862 } 863 } 864 865 /** 866 * Starts playing a DTMF tone. Also begins the local tone playback, 867 * if enabled. 868 * The access of this function is package rather than private 869 * since this is being referred from InCallScreen. 870 * InCallScreen calls this function to utilize the DTMF ToneGenerator properties 871 * defined here. 872 * @param tone a tone code from {@link ToneGenerator} 873 */ 874 /* package */ void startDtmfTone(char tone) { 875 if (DBG) log("startDtmfTone()..."); 876 mPhone.startDtmf(tone); 877 878 // if local tone playback is enabled, start it. 879 if (mDTMFToneEnabled) { 880 synchronized (mToneGeneratorLock) { 881 if (mToneGenerator == null) { 882 if (DBG) log("startDtmfTone: mToneGenerator == null, tone: " + tone); 883 } else { 884 if (DBG) log("starting local tone " + tone); 885 mToneGenerator.startTone(mToneMap.get(tone)); 886 } 887 } 888 } 889 } 890 891 /** 892 * Stops playing the current DTMF tone. 893 * 894 * The ToneStopper class (similar to that in {@link TwelveKeyDialer#mToneStopper}) 895 * has been removed in favor of synchronous start / stop calls since tone duration 896 * is now a function of the input. 897 * The acess of this function is package rather than private 898 * since this is being referred from InCallScreen. 899 * InCallScreen calls this function to utilize the DTMF ToneGenerator properties 900 * defined here. 901 */ 902 /* package */ void stopDtmfTone() { 903 if (DBG) log("stopDtmfTone()..."); 904 mPhone.stopDtmf(); 905 906 // if local tone playback is enabled, stop it. 907 if (DBG) log("trying to stop local tone..."); 908 if (mDTMFToneEnabled) { 909 synchronized (mToneGeneratorLock) { 910 if (mToneGenerator == null) { 911 if (DBG) log("stopDtmfTone: mToneGenerator == null"); 912 } else { 913 if (DBG) log("stopping local tone."); 914 mToneGenerator.stopTone(); 915 } 916 } 917 } 918 } 919 920 /** 921 * Check to see if the keyEvent is dialable. 922 */ 923 boolean isKeyEventAcceptable (KeyEvent event) { 924 return (mDialerKeyListener != null && mDialerKeyListener.isKeyEventAcceptable(event)); 925 } 926 927 /** 928 * static logging method 929 */ 930 private static void log(String msg) { 931 Log.d(LOG_TAG, msg); 932 } 933 934 /** 935 * Plays the local tone based the phone type. 936 */ 937 private void startTone(char c) { 938 int phoneType = mPhone.getPhoneType(); 939 if (phoneType == Phone.PHONE_TYPE_GSM) { 940 startDtmfTone(c); 941 } else if (phoneType == Phone.PHONE_TYPE_CDMA) { 942 startToneCdma(c); 943 } else { 944 throw new IllegalStateException("Unexpected phone type: " + phoneType); 945 } 946 } 947 948 /** 949 * Stops the local tone based on the phone type. 950 */ 951 private void stopTone() { 952 int phoneType = mPhone.getPhoneType(); 953 if (phoneType == Phone.PHONE_TYPE_GSM) { 954 stopDtmfTone(); 955 } else if (phoneType == Phone.PHONE_TYPE_CDMA) { 956 // Cdma case we do stopTone only for Long DTMF Setting 957 if (mDTMFToneType == CallFeaturesSetting.DTMF_TONE_TYPE_LONG) { 958 stopToneCdma(); 959 } 960 } else { 961 throw new IllegalStateException("Unexpected phone type: " + phoneType); 962 } 963 } 964 965 /** 966 * Plays tone when the DTMF setting is normal(Short). 967 */ 968 void startToneCdma(char tone) { 969 if (DBG) log("startToneCdma('" + tone + "')..."); 970 971 // Read the settings as it may be changed by the user during the call 972 mDTMFToneType = Settings.System.getInt(mInCallScreen.getContentResolver(), 973 Settings.System.DTMF_TONE_TYPE_WHEN_DIALING, 974 CallFeaturesSetting.DTMF_TONE_TYPE_NORMAL); 975 // For Short DTMF we need to play the local tone for fixed duration 976 if (mDTMFToneType == CallFeaturesSetting.DTMF_TONE_TYPE_NORMAL) { 977 sendShortDtmfToNetwork (tone); 978 } else { 979 // Pass as a char to be sent to network 980 Log.i(LOG_TAG, "send long dtmf for " + tone); 981 mPhone.startDtmf(tone); 982 } 983 984 startLocalToneCdma(tone); 985 } 986 987 /** 988 * Plays local tone for CDMA. 989 */ 990 void startLocalToneCdma(char tone) { 991 if (DBG) log("startLocalToneCdma('" + tone + "')..." 992 + " mDTMFToneEnabled = " + mDTMFToneEnabled + " this = " + this); 993 994 // if local tone playback is enabled, start it. 995 if (mDTMFToneEnabled) { 996 synchronized (mToneGeneratorLock) { 997 if (mToneGenerator == null) { 998 if (DBG) log("startToneCdma: mToneGenerator == null, tone: " + tone); 999 } else { 1000 if (DBG) log("starting local tone " + tone); 1001 1002 // Start the new tone. 1003 int toneDuration = -1; 1004 if (mDTMFToneType == CallFeaturesSetting.DTMF_TONE_TYPE_NORMAL) { 1005 toneDuration = DTMF_DURATION_MS; 1006 } 1007 mToneGenerator.startTone(mToneMap.get(tone), toneDuration); 1008 } 1009 } 1010 } 1011 } 1012 1013 /** 1014 * Sends the dtmf character over the network for short DTMF settings 1015 * When the characters are entered in quick succession, 1016 * the characters are queued before sending over the network. 1017 */ 1018 private void sendShortDtmfToNetwork(char dtmfDigit) { 1019 synchronized (mDTMFQueue) { 1020 if (mDTMFBurstCnfPending == true) { 1021 // Insert the dtmf char to the queue 1022 mDTMFQueue.add(new Character(dtmfDigit)); 1023 } else { 1024 String dtmfStr = Character.toString(dtmfDigit); 1025 Log.i(LOG_TAG, "dtmfsent = " + dtmfStr); 1026 mPhone.sendBurstDtmf(dtmfStr, 0, 0, mHandler.obtainMessage(DTMF_SEND_CNF)); 1027 // Set flag to indicate wait for Telephony confirmation. 1028 mDTMFBurstCnfPending = true; 1029 } 1030 } 1031 } 1032 1033 /** 1034 * Stops the dtmf from being sent over the network for Long DTMF case 1035 * and stops local DTMF key feedback tone. 1036 */ 1037 private void stopToneCdma() { 1038 if (DBG) log("stopping remote tone."); 1039 1040 mPhone.stopDtmf(); 1041 stopLocalToneCdma(); 1042 } 1043 1044 /** 1045 * Stops the local dtmf tone. 1046 */ 1047 void stopLocalToneCdma() { 1048 // if local tone playback is enabled, stop it. 1049 if (DBG) log("trying to stop local tone..."); 1050 if (mDTMFToneEnabled) { 1051 synchronized (mToneGeneratorLock) { 1052 if (mToneGenerator == null) { 1053 if (DBG) log("stopLocalToneCdma: mToneGenerator == null"); 1054 } else { 1055 if (DBG) log("stopping local tone."); 1056 mToneGenerator.stopTone(); 1057 } 1058 } 1059 } 1060 } 1061 1062 /** 1063 * Handles Burst Dtmf Confirmation from the Framework. 1064 */ 1065 void handleBurstDtmfConfirmation() { 1066 Character dtmfChar = null; 1067 synchronized (mDTMFQueue) { 1068 mDTMFBurstCnfPending = false; 1069 if (!mDTMFQueue.isEmpty()) { 1070 dtmfChar = mDTMFQueue.remove(); 1071 Log.i(LOG_TAG, "The dtmf character removed from queue" + dtmfChar); 1072 } 1073 } 1074 if (dtmfChar != null) { 1075 sendShortDtmfToNetwork(dtmfChar); 1076 } 1077 } 1078 } 1079