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