DTMFTwelveKeyDialer.java revision 0918cc33174eebee7ae13449a76a2fe48f84d6dc
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
17package com.android.phone;
18
19
20import com.android.internal.telephony.CallerInfo;
21import com.android.internal.telephony.CallerInfoAsyncQuery;
22import com.android.internal.telephony.Phone;
23import android.widget.SlidingDrawer;
24
25import android.media.AudioManager;
26import android.media.ToneGenerator;
27import android.os.Handler;
28import android.os.Message;
29import android.provider.Settings;
30import android.telephony.PhoneNumberUtils;
31import android.text.Editable;
32import android.text.Spannable;
33import android.text.method.DialerKeyListener;
34import android.text.method.MovementMethod;
35import android.util.Log;
36import android.view.KeyEvent;
37import android.view.MotionEvent;
38import android.view.View;
39import android.view.WindowManager;
40import android.view.animation.AlphaAnimation;
41import android.view.animation.Animation;
42import android.view.animation.AnimationUtils;
43import android.view.animation.Animation.AnimationListener;
44import android.widget.EditText;
45import android.widget.LinearLayout;
46import android.widget.TextView;
47
48import java.util.HashMap;
49
50/**
51 * Dialer class that encapsulates the DTMF twelve key behaviour.
52 * This model backs up the UI behaviour in DTMFTwelveKeyDialerView.java.
53 */
54public class DTMFTwelveKeyDialer implements
55        CallerInfoAsyncQuery.OnQueryCompleteListener,
56        SlidingDrawer.OnDrawerOpenListener,
57        SlidingDrawer.OnDrawerCloseListener,
58        View.OnTouchListener,
59        View.OnKeyListener {
60
61    // debug data
62    private static final String LOG_TAG = "dtmf dialer";
63    private static final boolean DBG = false;
64
65    // events
66    private static final int PHONE_DISCONNECT = 100;
67
68    private static Phone mPhone;
69    private ToneGenerator mToneGenerator;
70    private Object mToneGeneratorLock = new Object();
71
72    // indicate if we want to enable the DTMF tone playback.
73    private boolean mDTMFToneEnabled;
74
75    /** Hash Map to map a character to a tone*/
76    private static final HashMap<Character, Integer> mToneMap =
77        new HashMap<Character, Integer>();
78    /** Hash Map to map a view id to a character*/
79    private static final HashMap<Integer, Character> mDisplayMap =
80        new HashMap<Integer, Character>();
81    /** Set up the static maps*/
82    static {
83        // Map the key characters to tones
84        mToneMap.put('1', ToneGenerator.TONE_DTMF_1);
85        mToneMap.put('2', ToneGenerator.TONE_DTMF_2);
86        mToneMap.put('3', ToneGenerator.TONE_DTMF_3);
87        mToneMap.put('4', ToneGenerator.TONE_DTMF_4);
88        mToneMap.put('5', ToneGenerator.TONE_DTMF_5);
89        mToneMap.put('6', ToneGenerator.TONE_DTMF_6);
90        mToneMap.put('7', ToneGenerator.TONE_DTMF_7);
91        mToneMap.put('8', ToneGenerator.TONE_DTMF_8);
92        mToneMap.put('9', ToneGenerator.TONE_DTMF_9);
93        mToneMap.put('0', ToneGenerator.TONE_DTMF_0);
94        mToneMap.put('#', ToneGenerator.TONE_DTMF_P);
95        mToneMap.put('*', ToneGenerator.TONE_DTMF_S);
96
97        // Map the buttons to the display characters
98        mDisplayMap.put(R.id.one, '1');
99        mDisplayMap.put(R.id.two, '2');
100        mDisplayMap.put(R.id.three, '3');
101        mDisplayMap.put(R.id.four, '4');
102        mDisplayMap.put(R.id.five, '5');
103        mDisplayMap.put(R.id.six, '6');
104        mDisplayMap.put(R.id.seven, '7');
105        mDisplayMap.put(R.id.eight, '8');
106        mDisplayMap.put(R.id.nine, '9');
107        mDisplayMap.put(R.id.zero, '0');
108        mDisplayMap.put(R.id.pound, '#');
109        mDisplayMap.put(R.id.star, '*');
110    }
111
112    // UI elements
113    // Including elements used in the call status, now split
114    // between call state and call timer.
115    private EditText mDigits;
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 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            // find the character
372            char c = (char) lookup(event, content);
373
374            // if not a long press, and parent onKeyDown accepts the input
375            if (event.getRepeatCount() == 0 && super.onKeyDown(view, content, keyCode, event)) {
376
377                boolean keyOK = ok(getAcceptedChars(), c);
378
379                // show the display on any key down.
380                if (mDTMFDisplayAnimation != null && (keyOK || isAcceptableModifierKey(keyCode))) {
381                    mDTMFDisplayAnimation.onKeyDown();
382                }
383
384                // if the character is a valid dtmf code, start playing the tone and send the
385                // code.
386                if (keyOK) {
387                    if (DBG) log("DTMFKeyListener reading '" + c + "' from input.");
388                    playTone(c);
389                } else if (DBG) {
390                    log("DTMFKeyListener rejecting '" + c + "' from input.");
391                }
392                return true;
393            }
394            return false;
395        }
396
397        /**
398         * Overriden so that with each valid button up, we stop sending
399         * a dtmf code and the dtmf tone.
400         */
401        @Override
402        public boolean onKeyUp(View view, Editable content,
403                                 int keyCode, KeyEvent event) {
404
405            super.onKeyUp(view, content, keyCode, event);
406
407            // find the character
408            char c = (char) lookup(event, content);
409
410            boolean keyOK = ok(getAcceptedChars(), c);
411
412            // show the display on any key down.
413            if (mDTMFDisplayAnimation != null && (keyOK || isAcceptableModifierKey(keyCode))) {
414                mDTMFDisplayAnimation.onKeyUp();
415            }
416
417            if (keyOK) {
418                if (DBG) log("Stopping the tone for '" + c + "'");
419                stopTone();
420                return true;
421            }
422
423            return false;
424        }
425
426        /**
427         * Handle individual keydown events when we DO NOT have an Editable handy.
428         */
429        public boolean onKeyDown(KeyEvent event) {
430            char c = lookup (event);
431            if (DBG) log("recieved keydown for '" + c + "'");
432
433            // if not a long press, and parent onKeyDown accepts the input
434            if (event.getRepeatCount() == 0 && c != 0) {
435                // if the character is a valid dtmf code, start playing the tone and send the
436                // code.
437                if (ok(getAcceptedChars(), c)) {
438                    if (DBG) log("DTMFKeyListener reading '" + c + "' from input.");
439                    playTone(c);
440                    return true;
441                } else if (DBG) {
442                    log("DTMFKeyListener rejecting '" + c + "' from input.");
443                }
444            }
445            return false;
446        }
447
448        /**
449         * Handle individual keyup events.
450         *
451         * @param event is the event we are trying to stop.  If this is null,
452         * then we just force-stop the last tone without checking if the event
453         * is an acceptable dialer event.
454         */
455        public boolean onKeyUp(KeyEvent event) {
456            if (event == null) {
457                if (DBG) log("Stopping the last played tone.");
458                stopTone();
459                return true;
460            }
461
462            char c = lookup (event);
463            if (DBG) log("recieved keyup for '" + c + "'");
464
465            // TODO: stopTone does not take in character input, we may want to
466            // consider checking for this ourselves.
467            if (ok(getAcceptedChars(), c)) {
468                if (DBG) log("Stopping the tone for '" + c + "'");
469                stopTone();
470                return true;
471            }
472
473            return false;
474        }
475
476        /**
477         * Find the Dialer Key mapped to this event.
478         *
479         * @return The char value of the input event, otherwise
480         * 0 if no matching character was found.
481         */
482        private char lookup (KeyEvent event) {
483            // This code is similar to {@link DialerKeyListener#lookup(KeyEvent, Spannable) lookup}
484            int meta = event.getMetaState();
485            int number = event.getNumber();
486
487            if (!((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) || (number == 0)) {
488                int match = event.getMatch(getAcceptedChars(), meta);
489                number = (match != 0) ? match : number;
490            }
491
492            return (char) number;
493        }
494
495        /**
496         * Check to see if the keyEvent is dialable.
497         */
498        boolean isKeyEventAcceptable (KeyEvent event) {
499            return (ok(getAcceptedChars(), lookup(event)));
500        }
501
502        /**
503         * Overrides the characters used in {@link DialerKeyListener#CHARACTERS}
504         * These are the valid dtmf characters.
505         */
506        public final char[] DTMF_CHARACTERS = new char[] {
507            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*'
508        };
509    }
510
511    /**
512     * Our own handler to take care of the messages from the phone state changes
513     */
514    private Handler mHandler = new Handler () {
515        @Override
516        public void handleMessage(Message msg) {
517            switch (msg.what) {
518                // disconnect action
519                // make sure to close the dialer on ALL disconnect actions.
520                case PHONE_DISCONNECT:
521                    if (DBG) log("disconnect message recieved, shutting down.");
522                    // unregister since we are closing.
523                    mPhone.unregisterForDisconnect(this);
524                    closeDialer(false);
525                    break;
526            }
527        }
528    };
529
530
531    DTMFTwelveKeyDialer (InCallScreen parent) {
532        mInCallScreen = parent;
533        mPhone = ((PhoneApp) mInCallScreen.getApplication()).phone;
534        mDialerContainer = (SlidingDrawer) mInCallScreen.findViewById(R.id.dialer_container);
535
536        // mDialerContainer is only valid when we're looking at the portrait version of
537        // dtmf_twelve_key_dialer.
538        if (mDialerContainer != null) {
539            mDialerContainer.setOnDrawerOpenListener(this);
540            mDialerContainer.setOnDrawerCloseListener(this);
541        }
542
543        // setup the dialer display reference.
544        EditText display = parent.getDialerDisplay();
545        mDialerKeyListener = new DTMFKeyListener(display);
546        // if the display is visible, set the behaviour correctly.
547        if (display != null && InCallScreen.ConfigurationHelper.isLandscape()) {
548            display.setKeyListener(mDialerKeyListener);
549            display.setMovementMethod(new DTMFDisplayMovementMethod());
550
551            // remove the long-press context menus that support
552            // the edit (copy / paste / select) functions.
553            display.setLongClickable(false);
554        }
555    }
556
557    /**
558     * Called when we want to hide the DTMF Display field immediately.
559     *
560     * @param shouldHide if true, hide the display (and disable DTMF tones) immediately;
561     * otherwise, re-enable the display.
562     */
563    void hideDTMFDisplay(boolean shouldHide) {
564        DTMFKeyListener.DTMFDisplayAnimation animation = mDialerKeyListener.mDTMFDisplayAnimation;
565
566        // if the animation is in place
567        if (animation != null) {
568            View text = animation.mDTMFDisplay;
569
570            // and the display is available
571            if (text != null) {
572                // hide the display if necessary
573                text.setVisibility(shouldHide ? View.GONE : View.VISIBLE);
574                if (shouldHide) {
575                    // null the animation - this makes the display disappear faster
576                    text.setAnimation(null);
577                } else {
578                    // otherwise reset the animation to the initial state.
579                    animation.prepareFadeIn();
580                }
581            }
582        }
583    }
584
585    /**
586     * Null out our reference to the InCallScreen activity.
587     * This indicates that the InCallScreen activity has been destroyed.
588     * At the same time, get rid of listeners since we're not going to
589     * be valid anymore.
590     */
591    void clearInCallScreenReference() {
592        mInCallScreen = null;
593        mDialerKeyListener = null;
594        if (mDialerContainer != null) {
595            mDialerContainer.setOnDrawerOpenListener(null);
596            mDialerContainer.setOnDrawerCloseListener(null);
597        }
598        closeDialer(false);
599    }
600
601    LinearLayout getView() {
602        return mDialerView;
603    }
604
605    /**
606     * Dialer code that runs when the dialer is brought up.
607     * This includes layout changes, etc, and just prepares the dialer model for use.
608     */
609    void onDialerOpen() {
610        if (DBG) log("onDialerOpen()...");
611
612        // inflate the view.
613        mDialerView = (DTMFTwelveKeyDialerView) mInCallScreen.findViewById(R.id.dtmf_dialer);
614        mDialerView.setDialer(this);
615
616        // Have the WindowManager filter out cheek touch events
617        mInCallScreen.getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
618
619        mPhone.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
620
621        // set to a longer delay while the dialer is up.
622        PhoneApp app = PhoneApp.getInstance();
623        app.updateWakeState();
624
625        // setup the digit display
626        mDigits = (EditText) mDialerView.findViewById(R.id.dtmfDialerField);
627        mDigits.setKeyListener(new DTMFKeyListener(null));
628        mDigits.requestFocus();
629
630        // remove the long-press context menus that support
631        // the edit (copy / paste / select) functions.
632        mDigits.setLongClickable(false);
633
634        // Check for the presence of the keypad (portrait mode)
635        View view = mDialerView.findViewById(R.id.one);
636        if (view != null) {
637            if (DBG) log("portrait mode setup");
638            setupKeypad();
639        } else {
640            if (DBG) log("landscape mode setup");
641            // Adding hint text to the field to indicate that keyboard
642            // is needed while in landscape mode.
643            mDigits.setHint(R.string.dialerKeyboardHintText);
644        }
645
646        // setup the local tone generator.
647        startDialerSession();
648
649        // Give the InCallScreen a chance to do any necessary UI updates.
650        mInCallScreen.onDialerOpen();
651    }
652
653    /**
654     * Setup the local tone generator.  Should have corresponding calls to
655     * {@link onDialerPause}.
656     */
657    public void startDialerSession() {
658        // see if we need to play local tones.
659        mDTMFToneEnabled = Settings.System.getInt(mInCallScreen.getContentResolver(),
660                Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
661
662        // create the tone generator
663        // if the mToneGenerator creation fails, just continue without it.  It is
664        // a local audio signal, and is not as important as the dtmf tone itself.
665        if (mDTMFToneEnabled) {
666            synchronized (mToneGeneratorLock) {
667                if (mToneGenerator == null) {
668                    try {
669                        mToneGenerator = new ToneGenerator(AudioManager.STREAM_RING, 80);
670                    } catch (RuntimeException e) {
671                        if (DBG) log("Exception caught while creating local tone generator: " + e);
672                        mToneGenerator = null;
673                    }
674                }
675            }
676        }
677    }
678
679    /**
680     * Dialer code that runs when the dialer is closed.
681     * This releases resources acquired when we start the dialer.
682     */
683    public void onDialerClose() {
684        if (DBG) log("onDialerClose()...");
685
686        // reset back to a short delay for the poke lock.
687        PhoneApp app = PhoneApp.getInstance();
688        app.updateWakeState();
689
690        mPhone.unregisterForDisconnect(mHandler);
691
692        stopDialerSession();
693
694        // Give the InCallScreen a chance to do any necessary UI updates.
695        mInCallScreen.onDialerClose();
696    }
697
698    /**
699     * Tear down the local tone generator, corresponds to calls to
700     * {@link onDialerResume}
701     */
702    public void stopDialerSession() {
703        // release the tone generator.
704        synchronized (mToneGeneratorLock) {
705            if (mToneGenerator != null) {
706                mToneGenerator.release();
707                mToneGenerator = null;
708            }
709        }
710    }
711
712    /**
713     * upon completion of the query, update the name field in the status.
714     */
715    public void onQueryComplete(int token, Object cookie, CallerInfo ci){
716        if (DBG) log("callerinfo query complete, updating ui.");
717
718        ((TextView) cookie).setText(PhoneUtils.getCompactNameFromCallerInfo(ci, mInCallScreen));
719    }
720
721    /**
722     * Called externally (from InCallScreen) to play a DTMF Tone.
723     */
724    public boolean onDialerKeyDown(KeyEvent event) {
725        if (DBG) log("Notifying dtmf key down.");
726        return mDialerKeyListener.onKeyDown(event);
727    }
728
729    /**
730     * Called externally (from InCallScreen) to cancel the last DTMF Tone played.
731     */
732    public boolean onDialerKeyUp(KeyEvent event) {
733        if (DBG) log("Notifying dtmf key up.");
734        return mDialerKeyListener.onKeyUp(event);
735    }
736
737    /**
738     * setup the keys on the dialer activity, using the keymaps.
739     */
740    private void setupKeypad() {
741        // for each view id listed in the displaymap
742        View button;
743        for (int viewId : mDisplayMap.keySet()) {
744            // locate the view
745            button = mDialerView.findViewById(viewId);
746            // Setup the listeners for the buttons
747            button.setOnTouchListener(this);
748            button.setClickable(true);
749            button.setOnKeyListener(this);
750        }
751    }
752
753    /**
754     * catch the back and call buttons to return to the in call activity.
755     */
756    public boolean onKeyDown(int keyCode, KeyEvent event) {
757        switch (keyCode) {
758            // finish for these events
759            case KeyEvent.KEYCODE_BACK:
760            case KeyEvent.KEYCODE_CALL:
761                if (DBG) log("exit requested");
762                closeDialer(true);  // do the "closing" animation
763                return true;
764        }
765        return mInCallScreen.onKeyDown(keyCode, event);
766    }
767
768    /**
769     * catch the back and call buttons to return to the in call activity.
770     */
771    public boolean onKeyUp(int keyCode, KeyEvent event) {
772        return mInCallScreen.onKeyUp(keyCode, event);
773    }
774
775    /**
776     * Implemented for the TouchListener, process the touch events.
777     */
778    public boolean onTouch(View v, MotionEvent event) {
779        int viewId = v.getId();
780
781        // if the button is recognized
782        if (mDisplayMap.containsKey(viewId)) {
783            switch (event.getAction()) {
784                case MotionEvent.ACTION_DOWN:
785                    // Append the character mapped to this button, to the display.
786                    // start the tone
787                    appendDigit(mDisplayMap.get(viewId));
788                    break;
789                case MotionEvent.ACTION_UP:
790                case MotionEvent.ACTION_CANCEL:
791                    // stop the tone on ANY other event, except for MOVE.
792                    stopTone();
793                    break;
794            }
795            // do not return true [handled] here, since we want the
796            // press / click animation to be handled by the framework.
797        }
798        return false;
799    }
800
801    /**
802     * Implements View.OnKeyListener for the DTMF buttons.  Enables dialing with trackball/dpad.
803     */
804    public boolean onKey(View v, int keyCode, KeyEvent event) {
805        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
806            int viewId = v.getId();
807            if (mDisplayMap.containsKey(viewId)) {
808                switch (event.getAction()) {
809                case KeyEvent.ACTION_DOWN:
810                    if (event.getRepeatCount() == 0) {
811                        appendDigit(mDisplayMap.get(viewId));
812                    }
813                    break;
814                case KeyEvent.ACTION_UP:
815                    stopTone();
816                    break;
817                }
818                // do not return true [handled] here, since we want the
819                // press / click animation to be handled by the framework.
820            }
821        }
822        return false;
823    }
824
825    /**
826     * @return true if the dialer is currently opened (i.e. expanded).
827     */
828    public boolean isOpened() {
829        return mDialerContainer != null && mDialerContainer.isOpened();
830    }
831
832    /**
833     * Forces the dialer into the "open" state.
834     * Does nothing if the dialer is already open.
835     *
836     * @param animate if true, open the dialer with an animation.
837     */
838    public void openDialer(boolean animate) {
839        if (mDialerContainer != null && !mDialerContainer.isOpened()) {
840            if (animate) {
841                mDialerContainer.animateToggle();
842            } else {
843                mDialerContainer.toggle();
844            }
845        }
846    }
847
848    /**
849     * Forces the dialer into the "closed" state.
850     * Does nothing if the dialer is already closed.
851     *
852     * @param animate if true, close the dialer with an animation.
853     */
854    public void closeDialer(boolean animate) {
855        if (mDialerContainer != null && mDialerContainer.isOpened()) {
856            if (animate) {
857                mDialerContainer.animateToggle();
858            } else {
859                mDialerContainer.toggle();
860            }
861        }
862    }
863
864    /**
865     * Implemented for the SlidingDrawer open listener, prepare the dialer.
866     */
867    public void onDrawerOpened() {
868        onDialerOpen();
869    }
870
871    /**
872     * Implemented for the SlidingDrawer close listener, release the dialer.
873     */
874    public void onDrawerClosed() {
875        onDialerClose();
876    }
877
878    /**
879     * update the text area and playback the tone.
880     */
881    private final void appendDigit(char c) {
882        // if it is a valid key, then update the display and send the dtmf tone.
883        if (PhoneNumberUtils.is12Key(c)) {
884            if (DBG) log("updating display and sending dtmf tone for '" + c + "'");
885
886            if (mDigits != null) {
887                mDigits.getText().append(c);
888            }
889            // play the tone if it exists.
890            if (mToneMap.containsKey(c)) {
891                // begin tone playback.
892                playTone(c);
893            }
894        } else if (DBG) {
895            log("ignoring dtmf request for '" + c + "'");
896        }
897
898    }
899
900    /**
901     * Start playing a DTMF tone, also begin the local tone playback if it is
902     * enabled.
903     *
904     * @param tone a tone code from {@link ToneGenerator}
905     */
906    void playTone(char tone) {
907        if (DBG) log("starting remote tone.");
908        PhoneApp.getInstance().phone.startDtmf(tone);
909
910        // if local tone playback is enabled, start it.
911        if (mDTMFToneEnabled) {
912            synchronized (mToneGeneratorLock) {
913                if (mToneGenerator == null) {
914                    if (DBG) log("playTone: mToneGenerator == null, tone: " + tone);
915                } else {
916                    if (DBG) log("starting local tone " + tone);
917                    mToneGenerator.startTone(mToneMap.get(tone));
918                }
919            }
920        }
921    }
922
923    /**
924     * Stop playing the current DTMF tone.
925     *
926     * The ToneStopper class (similar to that in {@link TwelveKeyDialer#mToneStopper})
927     * has been removed in favor of synchronous start / stop calls since tone duration
928     * is now a function of the input.
929     */
930    void stopTone() {
931        if (DBG) log("stopping remote tone.");
932        PhoneApp.getInstance().phone.stopDtmf();
933
934        // if local tone playback is enabled, stop it.
935        if (DBG) log("trying to stop local tone...");
936        if (mDTMFToneEnabled) {
937            synchronized (mToneGeneratorLock) {
938                if (mToneGenerator == null) {
939                    if (DBG) log("stopTone: mToneGenerator == null");
940                } else {
941                    if (DBG) log("stopping local tone.");
942                    mToneGenerator.stopTone();
943                }
944            }
945        }
946    }
947
948    /**
949     * Check to see if the keyEvent is dialable.
950     */
951    boolean isKeyEventAcceptable (KeyEvent event) {
952        return (mDialerKeyListener != null && mDialerKeyListener.isKeyEventAcceptable(event));
953    }
954
955    /**
956     * static logging method
957     */
958    private static void log(String msg) {
959        Log.d(LOG_TAG, msg);
960    }
961}
962