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