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