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