2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.phone;
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;
42 import com.android.internal.telephony.CallerInfo;
43 import com.android.internal.telephony.CallerInfoAsyncQuery;
44 import com.android.internal.telephony.Phone;
46 import java.util.HashMap;
49 * Dialer class that encapsulates the DTMF twelve key behaviour.
50 * This model backs up the UI behaviour in DTMFTwelveKeyDialerView.java.
52 public class DTMFTwelveKeyDialer implements
53 CallerInfoAsyncQuery.OnQueryCompleteListener,
54 SlidingDrawer.OnDrawerOpenListener,
55 SlidingDrawer.OnDrawerCloseListener,
58 private static final String LOG_TAG = "DTMFTwelveKeyDialer";
59 private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
62 private static final int PHONE_DISCONNECT = 100;
64 private static Phone mPhone;
65 private ToneGenerator mToneGenerator;
66 private Object mToneGeneratorLock = new Object();
68 // indicate if we want to enable the DTMF tone playback.
69 private boolean mDTMFToneEnabled;
71 /** Hash Map to map a character to a tone*/
72 private static final HashMap<Character, Integer> mToneMap =
73 new HashMap<Character, Integer>();
74 /** Hash Map to map a view id to a character*/
75 private static final HashMap<Integer, Character> mDisplayMap =
76 new HashMap<Integer, Character>();
77 /** Set up the static maps*/
79 // Map the key characters to tones
80 mToneMap.put('1', ToneGenerator.TONE_DTMF_1);
81 mToneMap.put('2', ToneGenerator.TONE_DTMF_2);
82 mToneMap.put('3', ToneGenerator.TONE_DTMF_3);
83 mToneMap.put('4', ToneGenerator.TONE_DTMF_4);
84 mToneMap.put('5', ToneGenerator.TONE_DTMF_5);
85 mToneMap.put('6', ToneGenerator.TONE_DTMF_6);
86 mToneMap.put('7', ToneGenerator.TONE_DTMF_7);
87 mToneMap.put('8', ToneGenerator.TONE_DTMF_8);
88 mToneMap.put('9', ToneGenerator.TONE_DTMF_9);
89 mToneMap.put('0', ToneGenerator.TONE_DTMF_0);
90 mToneMap.put('#', ToneGenerator.TONE_DTMF_P);
91 mToneMap.put('*', ToneGenerator.TONE_DTMF_S);
93 // Map the buttons to the display characters
94 mDisplayMap.put(R.id.one, '1');
95 mDisplayMap.put(R.id.two, '2');
96 mDisplayMap.put(R.id.three, '3');
97 mDisplayMap.put(R.id.four, '4');
98 mDisplayMap.put(R.id.five, '5');
99 mDisplayMap.put(R.id.six, '6');
100 mDisplayMap.put(R.id.seven, '7');
101 mDisplayMap.put(R.id.eight, '8');
102 mDisplayMap.put(R.id.nine, '9');
103 mDisplayMap.put(R.id.zero, '0');
104 mDisplayMap.put(R.id.pound, '#');
105 mDisplayMap.put(R.id.star, '*');
108 // EditText field used to display the DTMF digits sent so far.
109 // - In portrait mode, we use the EditText that comes from
111 private EditText mDialpadDigits;
112 // - In landscape mode, we use a different EditText that's
113 // built into the InCallScreen:
114 private EditText mInCallDigits;
115 // (Only one of these will be visible at any given point.)
117 // InCallScreen reference.
118 private InCallScreen mInCallScreen;
120 // SlidingDrawer reference.
121 private SlidingDrawer mDialerContainer;
124 private DTMFTwelveKeyDialerView mDialerView;
126 // key listner reference, may or may not be attached to a view.
127 private DTMFKeyListener mDialerKeyListener;
130 * Create an input method just so that the textview can display the cursor.
131 * There is no selecting / positioning on the dialer field, only number input.
133 private class DTMFDisplayMovementMethod implements MovementMethod {
135 /**Return false since we are NOT consuming the input.*/
136 public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
140 /**Return false since we are NOT consuming the input.*/
141 public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
145 /**Return false since we are NOT consuming the input.*/
146 public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
150 /**Return false since we are NOT consuming the input.*/
151 public boolean onTrackballEvent(TextView widget, Spannable buffer, MotionEvent event) {
155 /**Return false since we are NOT consuming the input.*/
156 public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
160 public void initialize(TextView widget, Spannable text) {
163 public void onTakeFocus(TextView view, Spannable text, int dir) {
166 /**Disallow arbitrary selection.*/
167 public boolean canSelectArbitrarily() {
173 * Our own key listener, specialized for dealing with DTMF codes.
174 * 1. Ignore the backspace since it is irrelevant.
175 * 2. Allow ONLY valid DTMF characters to generate a tone and be
176 * sent as a DTMF code.
177 * 3. All other remaining characters are handled by the superclass.
179 private class DTMFKeyListener extends DialerKeyListener {
181 private DTMFDisplayAnimation mDTMFDisplayAnimation;
184 * Class that controls the fade in/out of the DTMF dialer field.
185 * Logic is tied into the keystroke events handled by the
188 * The key to this logic is the use of WAIT_FOR_USER_INPUT and
189 * Animation.fillBefore(true). This keeps the alpha animation in its
190 * beginning state until some key interaction is detected. On the
191 * key interaction, the animation start time is reset as appropriate.
194 * 1.Set and hold the alpha value to 0.0.
195 * 2.Animation is triggered on key down.
196 * 2.Animation is started immediately.
198 * 1.Set and hold the alpha value to 1.0.
199 * 2.Animation is triggered on key up.
200 * 2.Animation is FADE_OUT_TIMEOUT after trigger.
202 private class DTMFDisplayAnimation extends Handler implements AnimationListener {
203 // events for the fade in and out.
204 private static final int EVENT_FADE_IN = -1;
205 private static final int EVENT_FADE_OUT = -2;
208 // duration for the fade in animation
209 private static final int FADE_IN_ANIMATION_TIME = 500;
210 // duration for the fade out animation
211 private static final int FADE_OUT_ANIMATION_TIME = 1000;
213 * Wait time after last user activity to begin fade out.
215 * {@link com.android.server.PowerManagerService#SHORT_KEYLIGHT_DELAY}
217 private static final int FADE_OUT_TIMEOUT = 6000;
220 * Value indicating we should expect user input. This is used
221 * to keep animations in the started / initial state until a new
224 private static final long WAIT_FOR_USER_INPUT = Long.MAX_VALUE;
226 // DTMF display field
227 private View mDTMFDisplay;
229 // Fade in / out animations.
230 private AlphaAnimation mFadeIn;
231 private AlphaAnimation mFadeOut;
234 * API implemented for AnimationListener, called on start of animation.
236 public void onAnimationStart(Animation animation) {}
239 * API implemented for AnimationListener, called on end of animation.
240 * This code just prepares the next animation to be run.
242 public void onAnimationEnd(Animation animation) {
243 sendEmptyMessage(animation == mFadeOut ? EVENT_FADE_IN : EVENT_FADE_OUT);
247 * API implemented for AnimationListener, called on repeat of animation.
249 public void onAnimationRepeat(Animation animation) {}
252 * Handle the FADE_IN and FADE_OUT messages
255 public void handleMessage(Message msg) {
258 // just initialize to normal fade in.
263 // set animation to fade out.
264 mDTMFDisplay.setAnimation(mFadeOut);
269 DTMFDisplayAnimation(EditText display) {
270 mDTMFDisplay = display;
272 // create fade in animation
273 mFadeIn = new AlphaAnimation(0.0f, 1.0f);
274 mFadeIn.setDuration(FADE_IN_ANIMATION_TIME);
275 mFadeIn.setAnimationListener(this);
276 mFadeIn.setFillBefore(true);
278 // create fade out animation.
279 mFadeOut = new AlphaAnimation(1.0f, 0.0f);
280 mFadeOut.setDuration(FADE_OUT_ANIMATION_TIME);
281 mFadeOut.setAnimationListener(this);
282 mFadeOut.setFillBefore(true);
286 * Set up dtmf display field for the fade in trigger.
288 void prepareFadeIn() {
289 mDTMFDisplay.setAnimation(mFadeIn);
290 mFadeIn.setStartTime(WAIT_FOR_USER_INPUT);
294 * Notify that a key press has occurred, handle the appropriate
298 long currentAnimTime = AnimationUtils.currentAnimationTimeMillis();
300 if ((mDTMFDisplay.getAnimation() == mFadeOut) &&
301 (mFadeOut.getStartTime() < currentAnimTime)) {
302 // reset the animation if it is running.
304 } else if (mFadeIn.getStartTime() > currentAnimTime){
305 // otherwise start the fade in.
309 // Reset the fade out timer.
310 mFadeOut.setStartTime(WAIT_FOR_USER_INPUT);
314 * Notify that a key up has occurred, set the fade out animation
315 * start time accordingly.
318 mFadeOut.setStartTime(AnimationUtils.currentAnimationTimeMillis() +
323 private DTMFKeyListener(EditText display) {
326 // setup the display and animation if we're in landscape.
327 if (display != null && InCallScreen.ConfigurationHelper.isLandscape()) {
328 mDTMFDisplayAnimation = new DTMFDisplayAnimation(display);
329 mDTMFDisplayAnimation.prepareFadeIn();
334 * Overriden to return correct DTMF-dialable characters.
337 protected char[] getAcceptedChars(){
338 return DTMF_CHARACTERS;
341 /** special key listener ignores backspace. */
343 public boolean backspace(View view, Editable content, int keyCode,
349 * Return true if the keyCode is an accepted modifier key for the
350 * dialer (ALT or SHIFT).
352 private boolean isAcceptableModifierKey(int keyCode) {
354 case KeyEvent.KEYCODE_ALT_LEFT:
355 case KeyEvent.KEYCODE_ALT_RIGHT:
356 case KeyEvent.KEYCODE_SHIFT_LEFT:
357 case KeyEvent.KEYCODE_SHIFT_RIGHT:
365 * Overriden so that with each valid button press, we start sending
366 * a dtmf code and play a local dtmf tone.
369 public boolean onKeyDown(View view, Editable content,
370 int keyCode, KeyEvent event) {
371 // if (DBG) log("DTMFKeyListener.onKeyDown, keyCode " + keyCode + ", view " + view);
373 // find the character
374 char c = (char) lookup(event, content);
376 // if not a long press, and parent onKeyDown accepts the input
377 if (event.getRepeatCount() == 0 && super.onKeyDown(view, content, keyCode, event)) {
379 boolean keyOK = ok(getAcceptedChars(), c);
381 // show the display on any key down.
382 if (mDTMFDisplayAnimation != null && (keyOK || isAcceptableModifierKey(keyCode))) {
383 mDTMFDisplayAnimation.onKeyDown();
386 // if the character is a valid dtmf code, start playing the tone and send the
389 if (DBG) log("DTMFKeyListener reading '" + c + "' from input.");
392 log("DTMFKeyListener rejecting '" + c + "' from input.");
400 * Overriden so that with each valid button up, we stop sending
401 * a dtmf code and the dtmf tone.
404 public boolean onKeyUp(View view, Editable content,
405 int keyCode, KeyEvent event) {
406 // if (DBG) log("DTMFKeyListener.onKeyUp, keyCode " + keyCode + ", view " + view);
408 super.onKeyUp(view, content, keyCode, event);
410 // find the character
411 char c = (char) lookup(event, content);
413 boolean keyOK = ok(getAcceptedChars(), c);
415 // show the display on any key down.
416 if (mDTMFDisplayAnimation != null && (keyOK || isAcceptableModifierKey(keyCode))) {
417 mDTMFDisplayAnimation.onKeyUp();
421 if (DBG) log("Stopping the tone for '" + c + "'");
430 * Handle individual keydown events when we DO NOT have an Editable handy.
432 public boolean onKeyDown(KeyEvent event) {
433 char c = lookup (event);
434 if (DBG) log("recieved keydown for '" + c + "'");
436 // if not a long press, and parent onKeyDown accepts the input
437 if (event.getRepeatCount() == 0 && c != 0) {
438 // if the character is a valid dtmf code, start playing the tone and send the
440 if (ok(getAcceptedChars(), c)) {
441 if (DBG) log("DTMFKeyListener reading '" + c + "' from input.");
445 log("DTMFKeyListener rejecting '" + c + "' from input.");
452 * Handle individual keyup events.
454 * @param event is the event we are trying to stop. If this is null,
455 * then we just force-stop the last tone without checking if the event
456 * is an acceptable dialer event.
458 public boolean onKeyUp(KeyEvent event) {
460 if (DBG) log("Stopping the last played tone.");
465 char c = lookup (event);
466 if (DBG) log("recieved keyup for '" + c + "'");
468 // TODO: stopTone does not take in character input, we may want to
469 // consider checking for this ourselves.
470 if (ok(getAcceptedChars(), c)) {
471 if (DBG) log("Stopping the tone for '" + c + "'");
480 * Find the Dialer Key mapped to this event.
482 * @return The char value of the input event, otherwise
483 * 0 if no matching character was found.
485 private char lookup(KeyEvent event) {
486 // This code is similar to {@link DialerKeyListener#lookup(KeyEvent, Spannable) lookup}
487 int meta = event.getMetaState();
488 int number = event.getNumber();
490 if (!((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) || (number == 0)) {
491 int match = event.getMatch(getAcceptedChars(), meta);
492 number = (match != 0) ? match : number;
495 return (char) number;
499 * Check to see if the keyEvent is dialable.
501 boolean isKeyEventAcceptable (KeyEvent event) {
502 return (ok(getAcceptedChars(), lookup(event)));
506 * Overrides the characters used in {@link DialerKeyListener#CHARACTERS}
507 * These are the valid dtmf characters.
509 public final char[] DTMF_CHARACTERS = new char[] {
510 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*'
515 * Our own handler to take care of the messages from the phone state changes
517 private Handler mHandler = new Handler() {
519 public void handleMessage(Message msg) {
522 // make sure to close the dialer on ALL disconnect actions.
523 case PHONE_DISCONNECT:
524 if (DBG) log("disconnect message recieved, shutting down.");
525 // unregister since we are closing.
526 mPhone.unregisterForDisconnect(this);
534 public DTMFTwelveKeyDialer(InCallScreen parent) {
535 mInCallScreen = parent;
536 mPhone = ((PhoneApp) mInCallScreen.getApplication()).phone;
537 mDialerContainer = (SlidingDrawer) mInCallScreen.findViewById(R.id.dialer_container);
539 // mDialerContainer is only valid when we're looking at the portrait version of
540 // dtmf_twelve_key_dialer.
541 if (mDialerContainer != null) {
542 mDialerContainer.setOnDrawerOpenListener(this);
543 mDialerContainer.setOnDrawerCloseListener(this);
546 // Set up the EditText widget that displays DTMF digits in
547 // landscape mode. (This widget belongs to the InCallScreen, as
548 // opposed to mDialpadDigits, which is part of the full dialpad,
549 // and is used in portrait mode.)
550 mInCallDigits = mInCallScreen.getDialerDisplay();
552 mDialerKeyListener = new DTMFKeyListener(mInCallDigits);
553 // If the widget exists, set the behavior correctly.
554 if (mInCallDigits != null && InCallScreen.ConfigurationHelper.isLandscape()) {
555 mInCallDigits.setKeyListener(mDialerKeyListener);
556 mInCallDigits.setMovementMethod(new DTMFDisplayMovementMethod());
558 // remove the long-press context menus that support
559 // the edit (copy / paste / select) functions.
560 mInCallDigits.setLongClickable(false);
565 * Called when we want to hide the DTMF Display field immediately.
567 * @param shouldHide if true, hide the display (and disable DTMF tones) immediately;
568 * otherwise, re-enable the display.
570 public void hideDTMFDisplay(boolean shouldHide) {
571 DTMFKeyListener.DTMFDisplayAnimation animation = mDialerKeyListener.mDTMFDisplayAnimation;
573 // if the animation is in place
574 if (animation != null) {
575 View text = animation.mDTMFDisplay;
577 // and the display is available
579 // hide the display if necessary
580 text.setVisibility(shouldHide ? View.GONE : View.VISIBLE);
582 // null the animation - this makes the display disappear faster
583 text.setAnimation(null);
585 // otherwise reset the animation to the initial state.
586 animation.prepareFadeIn();
593 * Null out our reference to the InCallScreen activity.
594 * This indicates that the InCallScreen activity has been destroyed.
595 * At the same time, get rid of listeners since we're not going to
598 /* package */ void clearInCallScreenReference() {
599 mInCallScreen = null;
600 mDialerKeyListener = null;
601 if (mDialerContainer != null) {
602 mDialerContainer.setOnDrawerOpenListener(null);
603 mDialerContainer.setOnDrawerCloseListener(null);
609 * Dialer code that runs when the dialer is brought up.
610 * This includes layout changes, etc, and just prepares the dialer model for use.
612 private void onDialerOpen() {
613 if (DBG) log("onDialerOpen()...");
616 mDialerView = (DTMFTwelveKeyDialerView) mInCallScreen.findViewById(R.id.dtmf_dialer);
617 mDialerView.setDialer(this);
619 // Have the WindowManager filter out cheek touch events
620 mInCallScreen.getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
622 mPhone.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
624 // set to a longer delay while the dialer is up.
625 PhoneApp app = PhoneApp.getInstance();
626 app.updateWakeState();
628 // setup the digit display
629 mDialpadDigits = (EditText) mDialerView.findViewById(R.id.dtmfDialerField);
630 mDialpadDigits.setKeyListener(new DTMFKeyListener(null));
631 mDialpadDigits.requestFocus();
633 // remove the long-press context menus that support
634 // the edit (copy / paste / select) functions.
635 mDialpadDigits.setLongClickable(false);
637 // Check for the presence of the keypad (portrait mode)
638 View view = mDialerView.findViewById(R.id.one);
640 if (DBG) log("portrait mode setup");
643 if (DBG) log("landscape mode setup");
644 // Adding hint text to the field to indicate that keyboard
645 // is needed while in landscape mode.
646 mDialpadDigits.setHint(R.string.dialerKeyboardHintText);
649 // setup the local tone generator.
650 startDialerSession();
652 // Give the InCallScreen a chance to do any necessary UI updates.
653 mInCallScreen.onDialerOpen();
657 * Setup the local tone generator. Should have corresponding calls to
658 * {@link onDialerPause}.
660 public void startDialerSession() {
661 // see if we need to play local tones.
662 mDTMFToneEnabled = Settings.System.getInt(mInCallScreen.getContentResolver(),
663 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
665 // create the tone generator
666 // if the mToneGenerator creation fails, just continue without it. It is
667 // a local audio signal, and is not as important as the dtmf tone itself.
668 if (mDTMFToneEnabled) {
669 synchronized (mToneGeneratorLock) {
670 if (mToneGenerator == null) {
672 mToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 80);
673 } catch (RuntimeException e) {
674 if (DBG) log("Exception caught while creating local tone generator: " + e);
675 mToneGenerator = null;
683 * Dialer code that runs when the dialer is closed.
684 * This releases resources acquired when we start the dialer.
686 private void onDialerClose() {
687 if (DBG) log("onDialerClose()...");
689 // reset back to a short delay for the poke lock.
690 PhoneApp app = PhoneApp.getInstance();
691 app.updateWakeState();
693 mPhone.unregisterForDisconnect(mHandler);
697 // Give the InCallScreen a chance to do any necessary UI updates.
698 mInCallScreen.onDialerClose();
702 * Tear down the local tone generator, corresponds to calls to
703 * {@link onDialerResume}
705 public void stopDialerSession() {
706 // release the tone generator.
707 synchronized (mToneGeneratorLock) {
708 if (mToneGenerator != null) {
709 mToneGenerator.release();
710 mToneGenerator = null;
716 * upon completion of the query, update the name field in the status.
718 public void onQueryComplete(int token, Object cookie, CallerInfo ci){
719 if (DBG) log("callerinfo query complete, updating ui.");
721 ((TextView) cookie).setText(PhoneUtils.getCompactNameFromCallerInfo(ci, mInCallScreen));
725 * Called externally (from InCallScreen) to play a DTMF Tone.
727 public boolean onDialerKeyDown(KeyEvent event) {
728 if (DBG) log("Notifying dtmf key down.");
729 return mDialerKeyListener.onKeyDown(event);
733 * Called externally (from InCallScreen) to cancel the last DTMF Tone played.
735 public boolean onDialerKeyUp(KeyEvent event) {
736 if (DBG) log("Notifying dtmf key up.");
737 return mDialerKeyListener.onKeyUp(event);
741 * setup the keys on the dialer activity, using the keymaps.
743 private void setupKeypad() {
744 // for each view id listed in the displaymap
746 for (int viewId : mDisplayMap.keySet()) {
748 button = mDialerView.findViewById(viewId);
749 // Setup the listeners for the buttons
750 button.setOnTouchListener(this);
751 button.setClickable(true);
752 button.setOnKeyListener(this);
757 * catch the back and call buttons to return to the in call activity.
759 public boolean onKeyDown(int keyCode, KeyEvent event) {
760 // if (DBG) log("onKeyDown: keyCode " + keyCode);
762 // finish for these events
763 case KeyEvent.KEYCODE_BACK:
764 case KeyEvent.KEYCODE_CALL:
765 if (DBG) log("exit requested");
766 closeDialer(true); // do the "closing" animation
769 return mInCallScreen.onKeyDown(keyCode, event);
773 * catch the back and call buttons to return to the in call activity.
775 public boolean onKeyUp(int keyCode, KeyEvent event) {
776 // if (DBG) log("onKeyUp: keyCode " + keyCode);
777 return mInCallScreen.onKeyUp(keyCode, event);
781 * Implemented for the TouchListener, process the touch events.
783 public boolean onTouch(View v, MotionEvent event) {
784 int viewId = v.getId();
786 // if the button is recognized
787 if (mDisplayMap.containsKey(viewId)) {
788 switch (event.getAction()) {
789 case MotionEvent.ACTION_DOWN:
790 // Append the character mapped to this button, to the display.
792 processDtmf(mDisplayMap.get(viewId));
794 case MotionEvent.ACTION_UP:
795 case MotionEvent.ACTION_CANCEL:
796 // stop the tone on ANY other event, except for MOVE.
800 // do not return true [handled] here, since we want the
801 // press / click animation to be handled by the framework.
807 * Implements View.OnKeyListener for the DTMF buttons. Enables dialing with trackball/dpad.
809 public boolean onKey(View v, int keyCode, KeyEvent event) {
810 // if (DBG) log("onKey: keyCode " + keyCode + ", view " + v);
812 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
813 int viewId = v.getId();
814 if (mDisplayMap.containsKey(viewId)) {
815 switch (event.getAction()) {
816 case KeyEvent.ACTION_DOWN:
817 if (event.getRepeatCount() == 0) {
818 processDtmf(mDisplayMap.get(viewId));
821 case KeyEvent.ACTION_UP:
825 // do not return true [handled] here, since we want the
826 // press / click animation to be handled by the framework.
833 * @return true if the dialer is currently opened (i.e. expanded).
835 public boolean isOpened() {
836 return mDialerContainer != null && mDialerContainer.isOpened();
840 * Forces the dialer into the "open" state.
841 * Does nothing if the dialer is already open.
843 * @param animate if true, open the dialer with an animation.
845 public void openDialer(boolean animate) {
846 if (mDialerContainer != null && !mDialerContainer.isOpened()) {
848 mDialerContainer.animateToggle();
850 mDialerContainer.toggle();
856 * Forces the dialer into the "closed" state.
857 * Does nothing if the dialer is already closed.
859 * @param animate if true, close the dialer with an animation.
861 public void closeDialer(boolean animate) {
862 if (mDialerContainer != null && mDialerContainer.isOpened()) {
864 mDialerContainer.animateToggle();
866 mDialerContainer.toggle();
872 * Implemented for the SlidingDrawer open listener, prepare the dialer.
874 public void onDrawerOpened() {
879 * Implemented for the SlidingDrawer close listener, release the dialer.
881 public void onDrawerClosed() {
886 * Processes the specified digit as a DTMF key, by playing the
887 * appropriate DTMF tone, and appending the digit to the EditText
888 * field that displays the DTMF digits sent so far.
890 private final void processDtmf(char c) {
891 // if it is a valid key, then update the display and send the dtmf tone.
892 if (PhoneNumberUtils.is12Key(c)) {
893 if (DBG) log("updating display and sending dtmf tone for '" + c + "'");
895 if (mDialpadDigits != null) {
896 mDialpadDigits.getText().append(c);
899 // Note we *don't* need to manually append this digit to the
900 // landscape-mode EditText field (mInCallDigits), since it
901 // gets key events directly and automatically appends whetever
904 // Play the tone if it exists.
905 if (mToneMap.containsKey(c)) {
906 // begin tone playback.
910 log("ignoring dtmf request for '" + c + "'");
916 * Clears out the display of "DTMF digits typed so far" that's kept in
917 * either mDialpadDigits or mInCallDigits (depending on whether we're
918 * in portrait or landscape mode.)
920 * The InCallScreen is responsible for calling this method any time a
921 * new call becomes active (or, more simply, any time a call ends).
922 * This is how we make sure that the "history" of DTMF digits you type
923 * doesn't persist from one call to the next.
925 * TODO: it might be more elegent if the dialpad itself could remember
926 * the call that we're associated with, and clear the digits if the
927 * "current call" has changed since last time. (This would require
928 * some unique identifier that's different for each call. We can't
929 * just use the foreground Call object, since that's a singleton that
930 * lasts the whole life of the phone process. Instead, maybe look at
931 * the Connection object that comes back from getEarliestConnection()?
932 * Or getEarliestConnectTime()?)
934 * Or to be even fancier, we could keep a mapping of *multiple*
935 * "active calls" to DTMF strings. That way you could have two lines
936 * in use and swap calls multiple times, and we'd still remember the
937 * digits for each call. (But that's such an obscure use case that
938 * it's probably not worth the extra complexity.)
940 public void clearDigits() {
941 if (DBG) log("clearDigits()...");
943 if (mDialpadDigits != null) {
944 mDialpadDigits.setText("");
946 if (mInCallDigits != null) {
947 mInCallDigits.setText("");
952 * Starts playing a DTMF tone. Also begins the local tone playback,
955 * @param tone a tone code from {@link ToneGenerator}
957 private void startTone(char tone) {
958 if (DBG) log("startTone()...");
959 PhoneApp.getInstance().phone.startDtmf(tone);
961 // if local tone playback is enabled, start it.
962 if (mDTMFToneEnabled) {
963 synchronized (mToneGeneratorLock) {
964 if (mToneGenerator == null) {
965 if (DBG) log("startTone: mToneGenerator == null, tone: " + tone);
967 if (DBG) log("starting local tone " + tone);
968 mToneGenerator.startTone(mToneMap.get(tone));
975 * Stops playing the current DTMF tone.
977 * The ToneStopper class (similar to that in {@link TwelveKeyDialer#mToneStopper})
978 * has been removed in favor of synchronous start / stop calls since tone duration
979 * is now a function of the input.
981 private void stopTone() {
982 if (DBG) log("stopTone()...");
983 PhoneApp.getInstance().phone.stopDtmf();
985 // if local tone playback is enabled, stop it.
986 if (DBG) log("trying to stop local tone...");
987 if (mDTMFToneEnabled) {
988 synchronized (mToneGeneratorLock) {
989 if (mToneGenerator == null) {
990 if (DBG) log("stopTone: mToneGenerator == null");
992 if (DBG) log("stopping local tone.");
993 mToneGenerator.stopTone();
1000 * Check to see if the keyEvent is dialable.
1002 boolean isKeyEventAcceptable (KeyEvent event) {
1003 return (mDialerKeyListener != null && mDialerKeyListener.isKeyEventAcceptable(event));
1007 * static logging method
1009 private static void log(String msg) {
1010 Log.d(LOG_TAG, msg);