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