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