DTMFTwelveKeyDialer.java revision aa23e1c3c758bad23d8b6709147cc1ff7cd1e43c
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
19import android.media.AudioManager;
20import android.media.ToneGenerator;
21import android.os.Handler;
22import android.os.Message;
23import android.provider.Settings;
24import android.telephony.PhoneNumberUtils;
25import android.text.Editable;
26import android.text.Spannable;
27import android.text.method.DialerKeyListener;
28import android.text.method.MovementMethod;
29import android.util.Log;
30import android.view.KeyEvent;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.WindowManager;
34import android.view.animation.AlphaAnimation;
35import android.view.animation.Animation;
36import android.view.animation.Animation.AnimationListener;
37import android.view.animation.AnimationUtils;
38import android.widget.EditText;
39import android.widget.SlidingDrawer;
40import android.widget.TextView;
41
42import com.android.internal.telephony.CallerInfo;
43import com.android.internal.telephony.CallerInfoAsyncQuery;
44import com.android.internal.telephony.Phone;
45
46import java.util.HashMap;
47import java.util.LinkedList;
48import 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 */
55public 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_DTMF, 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                    // Start the new tone.
1112                    mToneGenerator.startTone(mToneMap.get(tone));
1113
1114                    // Stopped pending and Started new STOP_DTMF_TONE timer.
1115                    if (mDTMFToneType == CallFeaturesSetting.DTMF_TONE_TYPE_NORMAL) {
1116                        mHandler.removeMessages(STOP_DTMF_TONE);
1117                        mHandler.sendEmptyMessageDelayed(STOP_DTMF_TONE,DTMF_DURATION_MS);
1118                    }
1119                }
1120            }
1121        }
1122    }
1123
1124    /**
1125     * Sends the dtmf character over the network for short DTMF settings
1126     * When the characters are entered in quick succession,
1127     * the characters are queued before sending over the network.
1128     */
1129    private void sendShortDtmfToNetwork(char dtmfDigit) {
1130        synchronized (mDTMFQueue) {
1131            if (mDTMFBurstCnfPending == true) {
1132                // Insert the dtmf char to the queue
1133                mDTMFQueue.add(new Character(dtmfDigit));
1134            } else {
1135                String dtmfStr = Character.toString(dtmfDigit);
1136                Log.i(LOG_TAG,"dtmfsent = " + dtmfStr);
1137                mPhone.sendBurstDtmf(dtmfStr, 0, 0, mHandler.obtainMessage(DTMF_SEND_CNF));
1138                // Set flag to indicate wait for Telephony confirmation.
1139                mDTMFBurstCnfPending = true;
1140            }
1141        }
1142    }
1143
1144    /**
1145     * Stops the dtmf from being sent over the network for Long DTMF case and stops local DTMF key feedback tone.
1146     */
1147    private void stopToneCdma() {
1148        if (DBG) log("stopping remote tone.");
1149
1150        if (mDTMFToneType == CallFeaturesSetting.DTMF_TONE_TYPE_LONG) {
1151            mPhone.stopDtmf();
1152        }
1153
1154        // if local tone playback is enabled, stop it.
1155        if (DBG) log("trying to stop local tone...");
1156        if (mDTMFToneEnabled) {
1157            synchronized (mToneGeneratorLock) {
1158                if (mToneGenerator == null) {
1159                    if (DBG) log("stopToneCdma: mToneGenerator == null");
1160                } else {
1161                    if (DBG) log("stopping local tone.");
1162                    mToneGenerator.stopTone();
1163                }
1164            }
1165        }
1166    }
1167
1168    /**
1169     * Handles Burst Dtmf Confirmation from the Framework.
1170     */
1171    void handleBurstDtmfConfirmation() {
1172        Character dtmfChar = null;
1173        synchronized(mDTMFQueue) {
1174            mDTMFBurstCnfPending = false;
1175            if(!mDTMFQueue.isEmpty()) {
1176                dtmfChar = mDTMFQueue.remove();
1177                Log.i(LOG_TAG, "The dtmf character removed from queue" + dtmfChar);
1178            }
1179        }
1180        if (dtmfChar != null) {
1181            sendShortDtmfToNetwork(dtmfChar);
1182        }
1183    }
1184
1185}
1186