• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.phone;
18 
19 import android.media.AudioManager;
20 import android.media.ToneGenerator;
21 import android.os.Handler;
22 import android.os.Message;
23 import android.provider.Settings;
24 import android.telephony.PhoneNumberUtils;
25 import android.text.Editable;
26 import android.text.Spannable;
27 import android.text.method.DialerKeyListener;
28 import android.text.method.MovementMethod;
29 import android.util.Log;
30 import android.view.KeyEvent;
31 import android.view.MotionEvent;
32 import android.view.View;
33 import android.widget.EditText;
34 import android.widget.TextView;
35 
36 import com.android.internal.telephony.CallManager;
37 import com.android.internal.telephony.Phone;
38 
39 import java.util.HashMap;
40 import java.util.LinkedList;
41 import 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  */
48 public 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
mDisplayMap.put(R.id.one, '1')99         mDisplayMap.put(R.id.one, '1');
mDisplayMap.put(R.id.two, '2')100         mDisplayMap.put(R.id.two, '2');
mDisplayMap.put(R.id.three, '3')101         mDisplayMap.put(R.id.three, '3');
mDisplayMap.put(R.id.four, '4')102         mDisplayMap.put(R.id.four, '4');
mDisplayMap.put(R.id.five, '5')103         mDisplayMap.put(R.id.five, '5');
mDisplayMap.put(R.id.six, '6')104         mDisplayMap.put(R.id.six, '6');
mDisplayMap.put(R.id.seven, '7')105         mDisplayMap.put(R.id.seven, '7');
mDisplayMap.put(R.id.eight, '8')106         mDisplayMap.put(R.id.eight, '8');
mDisplayMap.put(R.id.nine, '9')107         mDisplayMap.put(R.id.nine, '9');
mDisplayMap.put(R.id.zero, '0')108         mDisplayMap.put(R.id.zero, '0');
mDisplayMap.put(R.id.pound, '#')109         mDisplayMap.put(R.id.pound, '#');
mDisplayMap.put(R.id.star, '*')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 
DTMFKeyListener()139         private DTMFKeyListener() {
140             super();
141         }
142 
143         /**
144          * Overriden to return correct DTMF-dialable characters.
145          */
146         @Override
getAcceptedChars()147         protected char[] getAcceptedChars(){
148             return DTMF_CHARACTERS;
149         }
150 
151         /** special key listener ignores backspace. */
152         @Override
backspace(View view, Editable content, int keyCode, KeyEvent event)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          */
isAcceptableModifierKey(int keyCode)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
onKeyDown(View view, Editable content, int keyCode, KeyEvent event)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
onKeyUp(View view, Editable content, int keyCode, KeyEvent event)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          */
onKeyDown(KeyEvent event)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          */
onKeyUp(KeyEvent event)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          */
lookup(KeyEvent event)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          */
isKeyEventAcceptable(KeyEvent event)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      */
DTMFTwelveKeyDialer(InCallScreen parent, DTMFTwelveKeyDialerView dialerView)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      */
clearInCallScreenReference()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      */
onDialerOpen()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      */
startDialerSession()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      */
onDialerClose()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      */
stopDialerSession()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      */
onDialerKeyDown(KeyEvent event)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      */
onDialerKeyUp(KeyEvent event)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      */
setupKeypad(DTMFTwelveKeyDialerView dialerView)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      */
onKeyDown(int keyCode, KeyEvent event)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      */
onKeyUp(int keyCode, KeyEvent event)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      */
onTouch(View v, MotionEvent event)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      */
onKey(View v, int keyCode, KeyEvent event)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".
isOpened()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      */
openDialer(boolean animate)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      */
closeDialer(boolean animate)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      */
processDtmf(char c)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      */
clearDigits()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      */
startTone(char c)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      */
startLocalToneIfNeeded(char c)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      */
isKeyEventAcceptable(KeyEvent event)787     boolean isKeyEventAcceptable (KeyEvent event) {
788         return (mDialerKeyListener != null && mDialerKeyListener.isKeyEventAcceptable(event));
789     }
790 
791     /**
792      * static logging method
793      */
log(String msg)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      */
stopTone()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      */
stopLocalToneIfNeeded()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      */
sendShortDtmfToNetwork(char dtmfDigit)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      */
handleBurstDtmfConfirmation()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