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