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