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