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