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