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