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