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