http://www.androidadb.com/source/platform/packages/apps/Phone/src/com/android/phone/DTMFTwelveKeyDialer.java.html




/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package com.android.phone;
 
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.os.Handler;
import android.os.Message;
import android.os.SystemProperties;
import android.provider.Settings;
import android.telephony.PhoneNumberUtils;
import android.text.Editable;
import android.text.Spannable;
import android.text.method.DialerKeyListener;
import android.text.method.MovementMethod;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.SlidingDrawer;
import android.widget.TextView;
 
import com.android.internal.telephony.CallManager;
import com.android.internal.telephony.Phone;
 
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
 
 
/**
 * Dialer class that encapsulates the DTMF twelve key behaviour.
 * This model backs up the UI behaviour in DTMFTwelveKeyDialerView.java.
 */
public class DTMFTwelveKeyDialer implements
        SlidingDrawer.OnDrawerOpenListener,
        SlidingDrawer.OnDrawerCloseListener,
        View.OnTouchListener,
        View.OnKeyListener {
    private static final String LOG_TAG = "DTMFTwelveKeyDialer";
    private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
 
    // events
    private static final int PHONE_DISCONNECT = 100;
    private static final int DTMF_SEND_CNF = 101;
 
    private CallManager mCM;
    private ToneGenerator mToneGenerator;
    private Object mToneGeneratorLock = new Object();
 
    // indicate if we want to enable the DTMF tone playback.
    private boolean mDTMFToneEnabled;
 
    // DTMF tone type
    private int mDTMFToneType;
 
    // indicate if the confirmation from TelephonyFW is pending.
    private boolean mDTMFBurstCnfPending = false;
 
    // Queue to queue the short dtmf characters.
    private Queue<Character> mDTMFQueue = new LinkedList<Character>();
 
    //  Short Dtmf tone duration
    private static final int DTMF_DURATION_MS = 120;
 
 
    /** Hash Map to map a character to a tone*/
    private static final HashMap<Character, Integer> mToneMap =
        new HashMap<Character, Integer>();
    /** Hash Map to map a view id to a character*/
    private static final HashMap<Integer, Character> mDisplayMap =
        new HashMap<Integer, Character>();
    /** Set up the static maps*/
    static {
        // Map the key characters to tones
        mToneMap.put('1', ToneGenerator.TONE_DTMF_1);
        mToneMap.put('2', ToneGenerator.TONE_DTMF_2);
        mToneMap.put('3', ToneGenerator.TONE_DTMF_3);
        mToneMap.put('4', ToneGenerator.TONE_DTMF_4);
        mToneMap.put('5', ToneGenerator.TONE_DTMF_5);
        mToneMap.put('6', ToneGenerator.TONE_DTMF_6);
        mToneMap.put('7', ToneGenerator.TONE_DTMF_7);
        mToneMap.put('8', ToneGenerator.TONE_DTMF_8);
        mToneMap.put('9', ToneGenerator.TONE_DTMF_9);
        mToneMap.put('0', ToneGenerator.TONE_DTMF_0);
        mToneMap.put('#', ToneGenerator.TONE_DTMF_P);
        mToneMap.put('*', ToneGenerator.TONE_DTMF_S);
 
        // Map the buttons to the display characters
        mDisplayMap.put(R.id.one, '1');
        mDisplayMap.put(R.id.two, '2');
        mDisplayMap.put(R.id.three, '3');
        mDisplayMap.put(R.id.four, '4');
        mDisplayMap.put(R.id.five, '5');
        mDisplayMap.put(R.id.six, '6');
        mDisplayMap.put(R.id.seven, '7');
        mDisplayMap.put(R.id.eight, '8');
        mDisplayMap.put(R.id.nine, '9');
        mDisplayMap.put(R.id.zero, '0');
        mDisplayMap.put(R.id.pound, '#');
        mDisplayMap.put(R.id.star, '*');
    }
 
    // EditText field used to display the DTMF digits sent so far.
    // Note this is null in some modes (like during the CDMA OTA call,
    // where there's no onscreen "digits" display.)
    private EditText mDialpadDigits;
 
    // InCallScreen reference.
    private InCallScreen mInCallScreen;
 
    // The SlidingDrawer containing mDialerView, or null if the current UI
    // doesn't use a SlidingDrawer.
    private SlidingDrawer mDialerDrawer;
 
    // The DTMFTwelveKeyDialerView we use to display the dialpad.
    private DTMFTwelveKeyDialerView mDialerView;
 
    // KeyListener used with the "dialpad digits" EditText widget.
    private DTMFKeyListener mDialerKeyListener;
 
    /**
     * Create an input method just so that the textview can display the cursor.
     * There is no selecting / positioning on the dialer field, only number input.
     */
    private static class DTMFDisplayMovementMethod implements MovementMethod {
 
        /**Return false since we are NOT consuming the input.*/
        public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
            return false;
        }
 
        /**Return false since we are NOT consuming the input.*/
        public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
            return false;
        }
 
        /**Return false since we are NOT consuming the input.*/
        public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
            return false;
        }
 
        /**Return false since we are NOT consuming the input.*/
        public boolean onTrackballEvent(TextView widget, Spannable buffer, MotionEvent event) {
            return false;
        }
 
        /**Return false since we are NOT consuming the input.*/
        public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
            return false;
        }
 
        public void initialize(TextView widget, Spannable text) {
        }
 
        public void onTakeFocus(TextView view, Spannable text, int dir) {
        }
 
        /**Disallow arbitrary selection.*/
        public boolean canSelectArbitrarily() {
            return false;
        }
    }
 
    /**
     * Our own key listener, specialized for dealing with DTMF codes.
     *   1. Ignore the backspace since it is irrelevant.
     *   2. Allow ONLY valid DTMF characters to generate a tone and be
     *      sent as a DTMF code.
     *   3. All other remaining characters are handled by the superclass.
     *
     * This code is purely here to handle events from the hardware keyboard
     * while the DTMF dialpad is up.
     */
    private class DTMFKeyListener extends DialerKeyListener {
 
        private DTMFKeyListener() {
            super();
        }
 
        /**
         * Overriden to return correct DTMF-dialable characters.
         */
        @Override
        protected char[] getAcceptedChars(){
            return DTMF_CHARACTERS;
        }
 
        /** special key listener ignores backspace. */
        @Override
        public boolean backspace(View view, Editable content, int keyCode,
                KeyEvent event) {
            return false;
        }
 
        /**
         * Return true if the keyCode is an accepted modifier key for the
         * dialer (ALT or SHIFT).
         */
        private boolean isAcceptableModifierKey(int keyCode) {
            switch (keyCode) {
                case KeyEvent.KEYCODE_ALT_LEFT:
                case KeyEvent.KEYCODE_ALT_RIGHT:
                case KeyEvent.KEYCODE_SHIFT_LEFT:
                case KeyEvent.KEYCODE_SHIFT_RIGHT:
                    return true;
                default:
                    return false;
            }
        }
 
        /**
         * Overriden so that with each valid button press, we start sending
         * a dtmf code and play a local dtmf tone.
         */
        @Override
        public boolean onKeyDown(View view, Editable content,
                                 int keyCode, KeyEvent event) {
            // if (DBG) log("DTMFKeyListener.onKeyDown, keyCode " + keyCode + ", view " + view);
 
            // find the character
            char c = (char) lookup(event, content);
 
            // if not a long press, and parent onKeyDown accepts the input
            if (event.getRepeatCount() == 0 && super.onKeyDown(view, content, keyCode, event)) {
 
                boolean keyOK = ok(getAcceptedChars(), c);
 
                // if the character is a valid dtmf code, start playing the tone and send the
                // code.
                if (keyOK) {
                    if (DBG) log("DTMFKeyListener reading '" + c + "' from input.");
                    processDtmf(c);
                } else if (DBG) {
                    log("DTMFKeyListener rejecting '" + c + "' from input.");
                }
                return true;
            }
            return false;
        }
 
        /**
         * Overriden so that with each valid button up, we stop sending
         * a dtmf code and the dtmf tone.
         */
        @Override
        public boolean onKeyUp(View view, Editable content,
                                 int keyCode, KeyEvent event) {
            // if (DBG) log("DTMFKeyListener.onKeyUp, keyCode " + keyCode + ", view " + view);
 
            super.onKeyUp(view, content, keyCode, event);
 
            // find the character
            char c = (char) lookup(event, content);
 
            boolean keyOK = ok(getAcceptedChars(), c);
 
            if (keyOK) {
                if (DBG) log("Stopping the tone for '" + c + "'");
                stopTone();
                return true;
            }
 
            return false;
        }
 
        /**
         * Handle individual keydown events when we DO NOT have an Editable handy.
         */
        public boolean onKeyDown(KeyEvent event) {
            char c = lookup(event);
            if (DBG) log("DTMFKeyListener.onKeyDown: event '" + c + "'");
 
            // if not a long press, and parent onKeyDown accepts the input
            if (event.getRepeatCount() == 0 && c != 0) {
                // if the character is a valid dtmf code, start playing the tone and send the
                // code.
                if (ok(getAcceptedChars(), c)) {
                    if (DBG) log("DTMFKeyListener reading '" + c + "' from input.");
                    processDtmf(c);
                    return true;
                } else if (DBG) {
                    log("DTMFKeyListener rejecting '" + c + "' from input.");
                }
            }
            return false;
        }
 
        /**
         * Handle individual keyup events.
         *
         * @param event is the event we are trying to stop.  If this is null,
         * then we just force-stop the last tone without checking if the event
         * is an acceptable dialer event.
         */
        public boolean onKeyUp(KeyEvent event) {
            if (event == null) {
                //the below piece of code sends stopDTMF event unnecessarily even when a null event
                //is received, hence commenting it.
                /*if (DBG) log("Stopping the last played tone.");
                stopTone();*/
                return true;
            }
 
            char c = lookup(event);
            if (DBG) log("DTMFKeyListener.onKeyUp: event '" + c + "'");
 
            // TODO: stopTone does not take in character input, we may want to
            // consider checking for this ourselves.
            if (ok(getAcceptedChars(), c)) {
                if (DBG) log("Stopping the tone for '" + c + "'");
                stopTone();
                return true;
            }
 
            return false;
        }
 
        /**
         * Find the Dialer Key mapped to this event.
         *
         * @return The char value of the input event, otherwise
         * 0 if no matching character was found.
         */
        private char lookup(KeyEvent event) {
            // This code is similar to {@link DialerKeyListener#lookup(KeyEvent, Spannable) lookup}
            int meta = event.getMetaState();
            int number = event.getNumber();
 
            if (!((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) || (number == 0)) {
                int match = event.getMatch(getAcceptedChars(), meta);
                number = (match != 0) ? match : number;
            }
 
            return (char) number;
        }
 
        /**
         * Check to see if the keyEvent is dialable.
         */
        boolean isKeyEventAcceptable (KeyEvent event) {
            return (ok(getAcceptedChars(), lookup(event)));
        }
 
        /**
         * Overrides the characters used in {@link DialerKeyListener#CHARACTERS}
         * These are the valid dtmf characters.
         */
        public final char[] DTMF_CHARACTERS = new char[] {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*'
        };
    }
 
    /**
     * Our own handler to take care of the messages from the phone state changes
     */
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                // disconnect action
                // make sure to close the dialer on ALL disconnect actions.
                case PHONE_DISCONNECT:
                    if (DBG) log("disconnect message recieved, shutting down.");
                    // unregister since we are closing.
                    mCM.unregisterForDisconnect(this);
                    closeDialer(false);
                    break;
                case DTMF_SEND_CNF:
                    if (DBG) log("dtmf confirmation received from FW.");
                    // handle burst dtmf confirmation
                    handleBurstDtmfConfirmation();
                    break;
            }
        }
    };
 
 
    /**
     * DTMFTwelveKeyDialer constructor.
     *
     * @param parent the InCallScreen instance that owns us.
     * @param dialerView the DTMFTwelveKeyDialerView we should use to display the dialpad.
     * @param dialerDrawer the SlidingDrawer widget that contains dialerView, or
     *                     null if this device doesn't use a SlidingDrawer
     *                     as a container for the dialpad.
     */
    public DTMFTwelveKeyDialer(InCallScreen parent,
                               DTMFTwelveKeyDialerView dialerView,
                               SlidingDrawer dialerDrawer) {
        if (DBG) log("DTMFTwelveKeyDialer constructor... this = " + this);
 
        mInCallScreen = parent;
        mCM = PhoneApp.getInstance().mCM;
 
        // The passed-in DTMFTwelveKeyDialerView *should* always be
        // non-null, now that the in-call UI uses only portrait mode.
        if (dialerView == null) {
            Log.e(LOG_TAG, "DTMFTwelveKeyDialer: null dialerView!", new IllegalStateException());
            // ...continue as best we can, although things will
            // be pretty broken without the mDialerView UI elements!
        }
        mDialerView = dialerView;
        if (DBG) log("- Got passed-in mDialerView: " + mDialerView);
 
        mDialerDrawer = dialerDrawer;
        if (DBG) log("- Got passed-in mDialerDrawer: " + mDialerDrawer);
 
        if (mDialerView != null) {
            mDialerView.setDialer(this);
 
            // In the normal in-call DTMF dialpad, mDialpadDigits is an
            // EditText used to display the digits the user has typed so
            // far.  But some other modes (like the OTA call) have no
            // "digits" display at all, in which case mDialpadDigits will
            // be null.
            mDialpadDigits = (EditText) mDialerView.findViewById(R.id.dtmfDialerField);
            if (mDialpadDigits != null) {