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