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