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