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