Teleca 2b changes.
[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
48 /**
49  * Dialer class that encapsulates the DTMF twelve key behaviour.
50  * This model backs up the UI behaviour in DTMFTwelveKeyDialerView.java.
51  */
52 public class DTMFTwelveKeyDialer implements
53         CallerInfoAsyncQuery.OnQueryCompleteListener,
54         SlidingDrawer.OnDrawerOpenListener,
55         SlidingDrawer.OnDrawerCloseListener,
56         View.OnTouchListener,
57         View.OnKeyListener {
58     private static final String LOG_TAG = "DTMFTwelveKeyDialer";
59     private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
60
61     // events
62     private static final int PHONE_DISCONNECT = 100;
63
64     private Phone mPhone;
65     private ToneGenerator mToneGenerator;
66     private Object mToneGeneratorLock = new Object();
67
68     // indicate if we want to enable the DTMF tone playback.
69     private boolean mDTMFToneEnabled;
70
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*/
78     static {
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);
92
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, '*');
106     }
107
108     // EditText field used to display the DTMF digits sent so far.
109     // - In portrait mode, we use the EditText that comes from
110     //   the full dialpad:
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.)
116
117     // InCallScreen reference.
118     private InCallScreen mInCallScreen;
119
120     // SlidingDrawer reference.
121     private SlidingDrawer mDialerContainer;
122
123     // view reference
124     private DTMFTwelveKeyDialerView mDialerView;
125
126     // key listner reference, may or may not be attached to a view.
127     private DTMFKeyListener mDialerKeyListener;
128
129     /**
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.
132      */
133     private static class DTMFDisplayMovementMethod implements MovementMethod {
134
135         /**Return false since we are NOT consuming the input.*/
136         public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
137             return false;
138         }
139
140         /**Return false since we are NOT consuming the input.*/
141         public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
142             return false;
143         }
144
145         /**Return false since we are NOT consuming the input.*/
146         public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
147             return false;
148         }
149
150         /**Return false since we are NOT consuming the input.*/
151         public boolean onTrackballEvent(TextView widget, Spannable buffer, MotionEvent event) {
152             return false;
153         }
154
155         /**Return false since we are NOT consuming the input.*/
156         public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
157             return false;
158         }
159
160         public void initialize(TextView widget, Spannable text) {
161         }
162
163         public void onTakeFocus(TextView view, Spannable text, int dir) {
164         }
165
166         /**Disallow arbitrary selection.*/
167         public boolean canSelectArbitrarily() {
168             return false;
169         }
170     }
171
172     /**
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.
178      */
179     private class DTMFKeyListener extends DialerKeyListener {
180
181         private DTMFDisplayAnimation mDTMFDisplayAnimation;
182
183         /**
184          * Class that controls the fade in/out of the DTMF dialer field.
185          * Logic is tied into the keystroke events handled by the
186          * DTMFKeyListener.
187          *
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.
192          *
193          * On fade in:
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.
197          * On fade out:
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.
201          */
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;
206
207             // static constants
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;
212             /**
213              * Wait time after last user activity to begin fade out.
214              * Timeout to match:
215              * {@link com.android.server.PowerManagerService#SHORT_KEYLIGHT_DELAY}
216              */
217             private static final int FADE_OUT_TIMEOUT = 6000;
218
219             /**
220              * Value indicating we should expect user input.  This is used
221              * to keep animations in the started / initial state until a new
222              * start time is set.
223              */
224             private static final long WAIT_FOR_USER_INPUT = Long.MAX_VALUE;
225
226             // DTMF display field
227             private View mDTMFDisplay;
228
229             // Fade in / out animations.
230             private AlphaAnimation mFadeIn;
231             private AlphaAnimation mFadeOut;
232
233             /**
234              * API implemented for AnimationListener, called on start of animation.
235              */
236             public void onAnimationStart(Animation animation) {}
237
238             /**
239              * API implemented for AnimationListener, called on end of animation.
240              * This code just prepares the next animation to be run.
241              */
242             public void onAnimationEnd(Animation animation) {
243                 sendEmptyMessage(animation == mFadeOut ? EVENT_FADE_IN : EVENT_FADE_OUT);
244             }
245
246             /**
247              * API implemented for AnimationListener, called on repeat of animation.
248              */
249             public void onAnimationRepeat(Animation animation) {}
250
251             /**
252              * Handle the FADE_IN and FADE_OUT messages
253              */
254             @Override
255             public void handleMessage(Message msg) {
256                 switch (msg.what) {
257                     case EVENT_FADE_IN:
258                         // just initialize to normal fade in.
259                         prepareFadeIn();
260                         break;
261                     case EVENT_FADE_OUT:
262                     default:
263                         // set animation to fade out.
264                         mDTMFDisplay.setAnimation(mFadeOut);
265                         break;
266                 }
267             }
268
269             DTMFDisplayAnimation(EditText display) {
270                 mDTMFDisplay = display;
271
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);
277
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);
283             }
284
285             /**
286              * Set up dtmf display field for the fade in trigger.
287              */
288             void prepareFadeIn() {
289                 mDTMFDisplay.setAnimation(mFadeIn);
290                 mFadeIn.setStartTime(WAIT_FOR_USER_INPUT);
291             }
292
293             /**
294              * Notify that a key press has occurred, handle the appropriate
295              * animation changes.
296              */
297             void onKeyDown() {
298                 long currentAnimTime = AnimationUtils.currentAnimationTimeMillis();
299
300                 if ((mDTMFDisplay.getAnimation() == mFadeOut) &&
301                         (mFadeOut.getStartTime() < currentAnimTime)) {
302                     // reset the animation if it is running.
303                     mFadeOut.reset();
304                 } else if (mFadeIn.getStartTime() > currentAnimTime){
305                     // otherwise start the fade in.
306                     mFadeIn.start();
307                 }
308
309                 // Reset the fade out timer.
310                 mFadeOut.setStartTime(WAIT_FOR_USER_INPUT);
311             }
312
313             /**
314              * Notify that a key up has occurred, set the fade out animation
315              * start time accordingly.
316              */
317             void onKeyUp() {
318                 mFadeOut.setStartTime(AnimationUtils.currentAnimationTimeMillis() +
319                         FADE_OUT_TIMEOUT);
320             }
321         }
322
323         private DTMFKeyListener(EditText display) {
324             super();
325
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();
330             }
331         }
332
333         /**
334          * Overriden to return correct DTMF-dialable characters.
335          */
336         @Override
337         protected char[] getAcceptedChars(){
338             return DTMF_CHARACTERS;
339         }
340
341         /** special key listener ignores backspace. */
342         @Override
343         public boolean backspace(View view, Editable content, int keyCode,
344                 KeyEvent event) {
345             return false;
346         }
347
348         /**
349          * Return true if the keyCode is an accepted modifier key for the
350          * dialer (ALT or SHIFT).
351          */
352         private boolean isAcceptableModifierKey(int keyCode) {
353             switch (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:
358                     return true;
359                 default:
360                     return false;
361             }
362         }
363
364         /**
365          * Overriden so that with each valid button press, we start sending
366          * a dtmf code and play a local dtmf tone.
367          */
368         @Override
369         public boolean onKeyDown(View view, Editable content,
370                                  int keyCode, KeyEvent event) {
371             // if (DBG) log("DTMFKeyListener.onKeyDown, keyCode " + keyCode + ", view " + view);
372
373             // find the character
374             char c = (char) lookup(event, content);
375
376             // if not a long press, and parent onKeyDown accepts the input
377             if (event.getRepeatCount() == 0 && super.onKeyDown(view, content, keyCode, event)) {
378
379                 boolean keyOK = ok(getAcceptedChars(), c);
380
381                 // show the display on any key down.
382                 if (mDTMFDisplayAnimation != null && (keyOK || isAcceptableModifierKey(keyCode))) {
383                     mDTMFDisplayAnimation.onKeyDown();
384                 }
385
386                 // if the character is a valid dtmf code, start playing the tone and send the
387                 // code.
388                 if (keyOK) {
389                     if (DBG) log("DTMFKeyListener reading '" + c + "' from input.");
390                     processDtmf(c);
391                 } else if (DBG) {
392                     log("DTMFKeyListener rejecting '" + c + "' from input.");
393                 }
394                 return true;
395             }
396             return false;
397         }
398
399         /**
400          * Overriden so that with each valid button up, we stop sending
401          * a dtmf code and the dtmf tone.
402          */
403         @Override
404         public boolean onKeyUp(View view, Editable content,
405                                  int keyCode, KeyEvent event) {
406             // if (DBG) log("DTMFKeyListener.onKeyUp, keyCode " + keyCode + ", view " + view);
407
408             super.onKeyUp(view, content, keyCode, event);
409
410             // find the character
411             char c = (char) lookup(event, content);
412
413             boolean keyOK = ok(getAcceptedChars(), c);
414
415             // show the display on any key down.
416             if (mDTMFDisplayAnimation != null && (keyOK || isAcceptableModifierKey(keyCode))) {
417                 mDTMFDisplayAnimation.onKeyUp();
418             }
419
420             if (keyOK) {
421                 if (DBG) log("Stopping the tone for '" + c + "'");
422                 stopTone();
423                 return true;
424             }
425
426             return false;
427         }
428
429         /**
430          * Handle individual keydown events when we DO NOT have an Editable handy.
431          */
432         public boolean onKeyDown(KeyEvent event) {
433             char c = lookup (event);
434             if (DBG) log("recieved keydown for '" + c + "'");
435
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
439                 // code.
440                 if (ok(getAcceptedChars(), c)) {
441                     if (DBG) log("DTMFKeyListener reading '" + c + "' from input.");
442                     processDtmf(c);
443                     return true;
444                 } else if (DBG) {
445                     log("DTMFKeyListener rejecting '" + c + "' from input.");
446                 }
447             }
448             return false;
449         }
450
451         /**
452          * Handle individual keyup events.
453          *
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.
457          */
458         public boolean onKeyUp(KeyEvent event) {
459             if (event == null) {
460                 //the below piece of code sends stopDTMF event unnecessarily even when a null event
461                 //is received, hence commenting it.
462                 /*if (DBG) log("Stopping the last played tone.");
463                 stopTone();*/
464                 return true;
465             }
466
467             char c = lookup (event);
468             if (DBG) log("recieved keyup for '" + c + "'");
469
470             // TODO: stopTone does not take in character input, we may want to
471             // consider checking for this ourselves.
472             if (ok(getAcceptedChars(), c)) {
473                 if (DBG) log("Stopping the tone for '" + c + "'");
474                 stopTone();
475                 return true;
476             }
477
478             return false;
479         }
480
481         /**
482          * Find the Dialer Key mapped to this event.
483          *
484          * @return The char value of the input event, otherwise
485          * 0 if no matching character was found.
486          */
487         private char lookup(KeyEvent event) {
488             // This code is similar to {@link DialerKeyListener#lookup(KeyEvent, Spannable) lookup}
489             int meta = event.getMetaState();
490             int number = event.getNumber();
491
492             if (!((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) || (number == 0)) {
493                 int match = event.getMatch(getAcceptedChars(), meta);
494                 number = (match != 0) ? match : number;
495             }
496
497             return (char) number;
498         }
499
500         /**
501          * Check to see if the keyEvent is dialable.
502          */
503         boolean isKeyEventAcceptable (KeyEvent event) {
504             return (ok(getAcceptedChars(), lookup(event)));
505         }
506
507         /**
508          * Overrides the characters used in {@link DialerKeyListener#CHARACTERS}
509          * These are the valid dtmf characters.
510          */
511         public final char[] DTMF_CHARACTERS = new char[] {
512             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*'
513         };
514     }
515
516     /**
517      * Our own handler to take care of the messages from the phone state changes
518      */
519     private Handler mHandler = new Handler() {
520         @Override
521         public void handleMessage(Message msg) {
522             switch (msg.what) {
523                 // disconnect action
524                 // make sure to close the dialer on ALL disconnect actions.
525                 case PHONE_DISCONNECT:
526                     if (DBG) log("disconnect message recieved, shutting down.");
527                     // unregister since we are closing.
528                     mPhone.unregisterForDisconnect(this);
529                     closeDialer(false);
530                     break;
531             }
532         }
533     };
534
535
536     public DTMFTwelveKeyDialer(InCallScreen parent) {
537         mInCallScreen = parent;
538         mPhone = ((PhoneApp) mInCallScreen.getApplication()).phone;
539         mDialerContainer = (SlidingDrawer) mInCallScreen.findViewById(R.id.dialer_container);
540
541         // mDialerContainer is only valid when we're looking at the portrait version of
542         // dtmf_twelve_key_dialer.
543         if (mDialerContainer != null) {
544             mDialerContainer.setOnDrawerOpenListener(this);
545             mDialerContainer.setOnDrawerCloseListener(this);
546         }
547
548         // Set up the EditText widget that displays DTMF digits in
549         // landscape mode.  (This widget belongs to the InCallScreen, as
550         // opposed to mDialpadDigits, which is part of the full dialpad,
551         // and is used in portrait mode.)
552         mInCallDigits = mInCallScreen.getDialerDisplay();
553
554         mDialerKeyListener = new DTMFKeyListener(mInCallDigits);
555         // If the widget exists, set the behavior correctly.
556         if (mInCallDigits != null && InCallScreen.ConfigurationHelper.isLandscape()) {
557             mInCallDigits.setKeyListener(mDialerKeyListener);
558             mInCallDigits.setMovementMethod(new DTMFDisplayMovementMethod());
559
560             // remove the long-press context menus that support
561             // the edit (copy / paste / select) functions.
562             mInCallDigits.setLongClickable(false);
563         }
564     }
565
566     /**
567      * Called when we want to hide the DTMF Display field immediately.
568      *
569      * @param shouldHide if true, hide the display (and disable DTMF tones) immediately;
570      * otherwise, re-enable the display.
571      */
572     public void hideDTMFDisplay(boolean shouldHide) {
573         DTMFKeyListener.DTMFDisplayAnimation animation = mDialerKeyListener.mDTMFDisplayAnimation;
574
575         // if the animation is in place
576         if (animation != null) {
577             View text = animation.mDTMFDisplay;
578
579             // and the display is available
580             if (text != null) {
581                 // hide the display if necessary
582                 text.setVisibility(shouldHide ? View.GONE : View.VISIBLE);
583                 if (shouldHide) {
584                     // null the animation - this makes the display disappear faster
585                     text.setAnimation(null);
586                 } else {
587                     // otherwise reset the animation to the initial state.
588                     animation.prepareFadeIn();
589                 }
590             }
591         }
592     }
593
594     /**
595      * Null out our reference to the InCallScreen activity.
596      * This indicates that the InCallScreen activity has been destroyed.
597      * At the same time, get rid of listeners since we're not going to
598      * be valid anymore.
599      */
600     /* package */ void clearInCallScreenReference() {
601         mInCallScreen = null;
602         mDialerKeyListener = null;
603         if (mDialerContainer != null) {
604             mDialerContainer.setOnDrawerOpenListener(null);
605             mDialerContainer.setOnDrawerCloseListener(null);
606         }
607         closeDialer(false);
608     }
609
610     /**
611      * Dialer code that runs when the dialer is brought up.
612      * This includes layout changes, etc, and just prepares the dialer model for use.
613      */
614     private void onDialerOpen() {
615         if (DBG) log("onDialerOpen()...");
616
617         // inflate the view.
618         mDialerView = (DTMFTwelveKeyDialerView) mInCallScreen.findViewById(R.id.dtmf_dialer);
619         mDialerView.setDialer(this);
620
621         // Have the WindowManager filter out cheek touch events
622         mInCallScreen.getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
623
624         mPhone.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
625
626         // set to a longer delay while the dialer is up.
627         PhoneApp app = PhoneApp.getInstance();
628         app.updateWakeState();
629
630         // setup the digit display
631         mDialpadDigits = (EditText) mDialerView.findViewById(R.id.dtmfDialerField);
632         mDialpadDigits.setKeyListener(new DTMFKeyListener(null));
633         mDialpadDigits.requestFocus();
634
635         // remove the long-press context menus that support
636         // the edit (copy / paste / select) functions.
637         mDialpadDigits.setLongClickable(false);
638
639         // Check for the presence of the keypad (portrait mode)
640         View view = mDialerView.findViewById(R.id.one);
641         if (view != null) {
642             if (DBG) log("portrait mode setup");
643             setupKeypad();
644         } else {
645             if (DBG) log("landscape mode setup");
646             // Adding hint text to the field to indicate that keyboard
647             // is needed while in landscape mode.
648             mDialpadDigits.setHint(R.string.dialerKeyboardHintText);
649         }
650
651         // setup the local tone generator.
652         startDialerSession();
653
654         // Give the InCallScreen a chance to do any necessary UI updates.
655         mInCallScreen.onDialerOpen();
656     }
657
658     /**
659      * Setup the local tone generator.  Should have corresponding calls to
660      * {@link onDialerPause}.
661      */
662     public void startDialerSession() {
663         // see if we need to play local tones.
664         mDTMFToneEnabled = Settings.System.getInt(mInCallScreen.getContentResolver(),
665                 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
666
667         // create the tone generator
668         // if the mToneGenerator creation fails, just continue without it.  It is
669         // a local audio signal, and is not as important as the dtmf tone itself.
670         if (mDTMFToneEnabled) {
671             synchronized (mToneGeneratorLock) {
672                 if (mToneGenerator == null) {
673                     try {
674                         mToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 80);
675                     } catch (RuntimeException e) {
676                         if (DBG) log("Exception caught while creating local tone generator: " + e);
677                         mToneGenerator = null;
678                     }
679                 }
680             }
681         }
682     }
683
684     /**
685      * Dialer code that runs when the dialer is closed.
686      * This releases resources acquired when we start the dialer.
687      */
688     private void onDialerClose() {
689         if (DBG) log("onDialerClose()...");
690
691         // reset back to a short delay for the poke lock.
692         PhoneApp app = PhoneApp.getInstance();
693         app.updateWakeState();
694
695         mPhone.unregisterForDisconnect(mHandler);
696
697         stopDialerSession();
698
699         // Give the InCallScreen a chance to do any necessary UI updates.
700         mInCallScreen.onDialerClose();
701     }
702
703     /**
704      * Tear down the local tone generator, corresponds to calls to
705      * {@link onDialerResume}
706      */
707     public void stopDialerSession() {
708         // release the tone generator.
709         synchronized (mToneGeneratorLock) {
710             if (mToneGenerator != null) {
711                 mToneGenerator.release();
712                 mToneGenerator = null;
713             }
714         }
715     }
716
717     /**
718      * upon completion of the query, update the name field in the status.
719      */
720     public void onQueryComplete(int token, Object cookie, CallerInfo ci){
721         if (DBG) log("callerinfo query complete, updating ui.");
722
723         ((TextView) cookie).setText(PhoneUtils.getCompactNameFromCallerInfo(ci, mInCallScreen));
724     }
725
726     /**
727      * Called externally (from InCallScreen) to play a DTMF Tone.
728      */
729     public boolean onDialerKeyDown(KeyEvent event) {
730         if (DBG) log("Notifying dtmf key down.");
731         return mDialerKeyListener.onKeyDown(event);
732     }
733
734     /**
735      * Called externally (from InCallScreen) to cancel the last DTMF Tone played.
736      */
737     public boolean onDialerKeyUp(KeyEvent event) {
738         if (DBG) log("Notifying dtmf key up.");
739         return mDialerKeyListener.onKeyUp(event);
740     }
741
742     /**
743      * setup the keys on the dialer activity, using the keymaps.
744      */
745     private void setupKeypad() {
746         // for each view id listed in the displaymap
747         View button;
748         for (int viewId : mDisplayMap.keySet()) {
749             // locate the view
750             button = mDialerView.findViewById(viewId);
751             // Setup the listeners for the buttons
752             button.setOnTouchListener(this);
753             button.setClickable(true);
754             button.setOnKeyListener(this);
755         }
756     }
757
758     /**
759      * catch the back and call buttons to return to the in call activity.
760      */
761     public boolean onKeyDown(int keyCode, KeyEvent event) {
762         // if (DBG) log("onKeyDown:  keyCode " + keyCode);
763         switch (keyCode) {
764             // finish for these events
765             case KeyEvent.KEYCODE_BACK:
766             case KeyEvent.KEYCODE_CALL:
767                 if (DBG) log("exit requested");
768                 closeDialer(true);  // do the "closing" animation
769                 return true;
770         }
771         return mInCallScreen.onKeyDown(keyCode, event);
772     }
773
774     /**
775      * catch the back and call buttons to return to the in call activity.
776      */
777     public boolean onKeyUp(int keyCode, KeyEvent event) {
778         // if (DBG) log("onKeyUp:  keyCode " + keyCode);
779         return mInCallScreen.onKeyUp(keyCode, event);
780     }
781
782     /**
783      * Implemented for the TouchListener, process the touch events.
784      */
785     public boolean onTouch(View v, MotionEvent event) {
786         int viewId = v.getId();
787
788         // if the button is recognized
789         if (mDisplayMap.containsKey(viewId)) {
790             switch (event.getAction()) {
791                 case MotionEvent.ACTION_DOWN:
792                     // Append the character mapped to this button, to the display.
793                     // start the tone
794                     processDtmf(mDisplayMap.get(viewId));
795                     break;
796                 case MotionEvent.ACTION_UP:
797                 case MotionEvent.ACTION_CANCEL:
798                     // stop the tone on ANY other event, except for MOVE.
799                     stopTone();
800                     break;
801             }
802             // do not return true [handled] here, since we want the
803             // press / click animation to be handled by the framework.
804         }
805         return false;
806     }
807
808     /**
809      * Implements View.OnKeyListener for the DTMF buttons.  Enables dialing with trackball/dpad.
810      */
811     public boolean onKey(View v, int keyCode, KeyEvent event) {
812         // if (DBG) log("onKey:  keyCode " + keyCode + ", view " + v);
813
814         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
815             int viewId = v.getId();
816             if (mDisplayMap.containsKey(viewId)) {
817                 switch (event.getAction()) {
818                 case KeyEvent.ACTION_DOWN:
819                     if (event.getRepeatCount() == 0) {
820                         processDtmf(mDisplayMap.get(viewId));
821                     }
822                     break;
823                 case KeyEvent.ACTION_UP:
824                     stopTone();
825                     break;
826                 }
827                 // do not return true [handled] here, since we want the
828                 // press / click animation to be handled by the framework.
829             }
830         }
831         return false;
832     }
833
834     /**
835      * @return true if the dialer is currently opened (i.e. expanded).
836      */
837     public boolean isOpened() {
838         return mDialerContainer != null && mDialerContainer.isOpened();
839     }
840
841     /**
842      * Forces the dialer into the "open" state.
843      * Does nothing if the dialer is already open.
844      *
845      * @param animate if true, open the dialer with an animation.
846      */
847     public void openDialer(boolean animate) {
848         if (mDialerContainer != null && !mDialerContainer.isOpened()) {
849             if (animate) {
850                 mDialerContainer.animateToggle();
851             } else {
852                 mDialerContainer.toggle();
853             }
854         }
855     }
856
857     /**
858      * Forces the dialer into the "closed" state.
859      * Does nothing if the dialer is already closed.
860      *
861      * @param animate if true, close the dialer with an animation.
862      */
863     public void closeDialer(boolean animate) {
864         if (mDialerContainer != null && mDialerContainer.isOpened()) {
865             if (animate) {
866                 mDialerContainer.animateToggle();
867             } else {
868                 mDialerContainer.toggle();
869             }
870         }
871     }
872
873     /**
874      * Implemented for the SlidingDrawer open listener, prepare the dialer.
875      */
876     public void onDrawerOpened() {
877         onDialerOpen();
878     }
879
880     /**
881      * Implemented for the SlidingDrawer close listener, release the dialer.
882      */
883     public void onDrawerClosed() {
884         onDialerClose();
885     }
886
887     /**
888      * Processes the specified digit as a DTMF key, by playing the
889      * appropriate DTMF tone, and appending the digit to the EditText
890      * field that displays the DTMF digits sent so far.
891      */
892     private final void processDtmf(char c) {
893         // if it is a valid key, then update the display and send the dtmf tone.
894         if (PhoneNumberUtils.is12Key(c)) {
895             if (DBG) log("updating display and sending dtmf tone for '" + c + "'");
896
897             if (mDialpadDigits != null) {
898                 mDialpadDigits.getText().append(c);
899             }
900
901             // Note we *don't* need to manually append this digit to the
902             // landscape-mode EditText field (mInCallDigits), since it
903             // gets key events directly and automatically appends whetever
904             // the user types.
905
906             // Play the tone if it exists.
907             if (mToneMap.containsKey(c)) {
908                 // begin tone playback.
909                 startTone(c);
910             }
911         } else if (DBG) {
912             log("ignoring dtmf request for '" + c + "'");
913         }
914
915         // Any DTMF keypress counts as explicit "user activity".
916         PhoneApp.getInstance().pokeUserActivity();
917     }
918
919     /**
920      * Clears out the display of "DTMF digits typed so far" that's kept in
921      * either mDialpadDigits or mInCallDigits (depending on whether we're
922      * in portrait or landscape mode.)
923      *
924      * The InCallScreen is responsible for calling this method any time a
925      * new call becomes active (or, more simply, any time a call ends).
926      * This is how we make sure that the "history" of DTMF digits you type
927      * doesn't persist from one call to the next.
928      *
929      * TODO: it might be more elegent if the dialpad itself could remember
930      * the call that we're associated with, and clear the digits if the
931      * "current call" has changed since last time.  (This would require
932      * some unique identifier that's different for each call.  We can't
933      * just use the foreground Call object, since that's a singleton that
934      * lasts the whole life of the phone process.  Instead, maybe look at
935      * the Connection object that comes back from getEarliestConnection()?
936      * Or getEarliestConnectTime()?)
937      *
938      * Or to be even fancier, we could keep a mapping of *multiple*
939      * "active calls" to DTMF strings.  That way you could have two lines
940      * in use and swap calls multiple times, and we'd still remember the
941      * digits for each call.  (But that's such an obscure use case that
942      * it's probably not worth the extra complexity.)
943      */
944     public void clearDigits() {
945         if (DBG) log("clearDigits()...");
946
947         if (mDialpadDigits != null) {
948             mDialpadDigits.setText("");
949         }
950         if (mInCallDigits != null) {
951             mInCallDigits.setText("");
952         }
953     }
954
955     /**
956      * Starts playing a DTMF tone.  Also begins the local tone playback,
957      * if enabled.
958      *
959      * @param tone a tone code from {@link ToneGenerator}
960      */
961     private void startTone(char tone) {
962         if (DBG) log("startTone()...");
963         PhoneApp.getInstance().phone.startDtmf(tone);
964
965         // if local tone playback is enabled, start it.
966         if (mDTMFToneEnabled) {
967             synchronized (mToneGeneratorLock) {
968                 if (mToneGenerator == null) {
969                     if (DBG) log("startTone: mToneGenerator == null, tone: " + tone);
970                 } else {
971                     if (DBG) log("starting local tone " + tone);
972                     mToneGenerator.startTone(mToneMap.get(tone));
973                 }
974             }
975         }
976     }
977
978     /**
979      * Stops playing the current DTMF tone.
980      *
981      * The ToneStopper class (similar to that in {@link TwelveKeyDialer#mToneStopper})
982      * has been removed in favor of synchronous start / stop calls since tone duration
983      * is now a function of the input.
984      */
985     private void stopTone() {
986         if (DBG) log("stopTone()...");
987         PhoneApp.getInstance().phone.stopDtmf();
988
989         // if local tone playback is enabled, stop it.
990         if (DBG) log("trying to stop local tone...");
991         if (mDTMFToneEnabled) {
992             synchronized (mToneGeneratorLock) {
993                 if (mToneGenerator == null) {
994                     if (DBG) log("stopTone: mToneGenerator == null");
995                 } else {
996                     if (DBG) log("stopping local tone.");
997                     mToneGenerator.stopTone();
998                 }
999             }
1000         }
1001     }
1002
1003     /**
1004      * Check to see if the keyEvent is dialable.
1005      */
1006     boolean isKeyEventAcceptable (KeyEvent event) {
1007         return (mDialerKeyListener != null && mDialerKeyListener.isKeyEventAcceptable(event));
1008     }
1009
1010     /**
1011      * static logging method
1012      */
1013     private static void log(String msg) {
1014         Log.d(LOG_TAG, msg);
1015     }
1016 }