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