FileDocCategorySizeDatePackage
DTMFTwelveKeyDialer.javaAPI DocAndroid 1.5 API37151Wed May 06 22:42:46 BST 2009com.android.phone

DTMFTwelveKeyDialer

public class DTMFTwelveKeyDialer extends Object implements CallerInfoAsyncQuery.OnQueryCompleteListener, SlidingDrawer.OnDrawerOpenListener, SlidingDrawer.OnDrawerCloseListener, View.OnTouchListener, View.OnKeyListener
Dialer class that encapsulates the DTMF twelve key behaviour. This model backs up the UI behaviour in DTMFTwelveKeyDialerView.java.

Fields Summary
private static final String
LOG_TAG
private static final boolean
DBG
private static final int
PHONE_DISCONNECT
private static com.android.internal.telephony.Phone
mPhone
private android.media.ToneGenerator
mToneGenerator
private Object
mToneGeneratorLock
private boolean
mDTMFToneEnabled
private static final HashMap
mToneMap
Hash Map to map a character to a tone
private static final HashMap
mDisplayMap
Hash Map to map a view id to a character
private android.widget.EditText
mDialpadDigits
Set up the static maps
private android.widget.EditText
mInCallDigits
private InCallScreen
mInCallScreen
private android.widget.SlidingDrawer
mDialerContainer
private DTMFTwelveKeyDialerView
mDialerView
private DTMFKeyListener
mDialerKeyListener
private android.os.Handler
mHandler
Our own handler to take care of the messages from the phone state changes
Constructors Summary
public DTMFTwelveKeyDialer(InCallScreen parent)



       
        mInCallScreen = parent;
        mPhone = ((PhoneApp) mInCallScreen.getApplication()).phone;
        mDialerContainer = (SlidingDrawer) mInCallScreen.findViewById(R.id.dialer_container);

        // mDialerContainer is only valid when we're looking at the portrait version of
        // dtmf_twelve_key_dialer.
        if (mDialerContainer != null) {
            mDialerContainer.setOnDrawerOpenListener(this);
            mDialerContainer.setOnDrawerCloseListener(this);
        }

        // Set up the EditText widget that displays DTMF digits in
        // landscape mode.  (This widget belongs to the InCallScreen, as
        // opposed to mDialpadDigits, which is part of the full dialpad,
        // and is used in portrait mode.)
        mInCallDigits = mInCallScreen.getDialerDisplay();

        mDialerKeyListener = new DTMFKeyListener(mInCallDigits);
        // If the widget exists, set the behavior correctly.
        if (mInCallDigits != null && InCallScreen.ConfigurationHelper.isLandscape()) {
            mInCallDigits.setKeyListener(mDialerKeyListener);
            mInCallDigits.setMovementMethod(new DTMFDisplayMovementMethod());

            // remove the long-press context menus that support
            // the edit (copy / paste / select) functions.
            mInCallDigits.setLongClickable(false);
        }
    
Methods Summary
public voidclearDigits()
Clears out the display of "DTMF digits typed so far" that's kept in either mDialpadDigits or mInCallDigits (depending on whether we're in portrait or landscape mode.) The InCallScreen is responsible for calling this method any time a new call becomes active (or, more simply, any time a call ends). This is how we make sure that the "history" of DTMF digits you type doesn't persist from one call to the next. TODO: it might be more elegent if the dialpad itself could remember the call that we're associated with, and clear the digits if the "current call" has changed since last time. (This would require some unique identifier that's different for each call. We can't just use the foreground Call object, since that's a singleton that lasts the whole life of the phone process. Instead, maybe look at the Connection object that comes back from getEarliestConnection()? Or getEarliestConnectTime()?) Or to be even fancier, we could keep a mapping of *multiple* "active calls" to DTMF strings. That way you could have two lines in use and swap calls multiple times, and we'd still remember the digits for each call. (But that's such an obscure use case that it's probably not worth the extra complexity.)

        if (DBG) log("clearDigits()...");

        if (mDialpadDigits != null) {
            mDialpadDigits.setText("");
        }
        if (mInCallDigits != null) {
            mInCallDigits.setText("");
        }
    
voidclearInCallScreenReference()
Null out our reference to the InCallScreen activity. This indicates that the InCallScreen activity has been destroyed. At the same time, get rid of listeners since we're not going to be valid anymore.

        mInCallScreen = null;
        mDialerKeyListener = null;
        if (mDialerContainer != null) {
            mDialerContainer.setOnDrawerOpenListener(null);
            mDialerContainer.setOnDrawerCloseListener(null);
        }
        closeDialer(false);
    
public voidcloseDialer(boolean animate)
Forces the dialer into the "closed" state. Does nothing if the dialer is already closed.

param
animate if true, close the dialer with an animation.

        if (mDialerContainer != null && mDialerContainer.isOpened()) {
            if (animate) {
                mDialerContainer.animateToggle();
            } else {
                mDialerContainer.toggle();
            }
        }
    
public voidhideDTMFDisplay(boolean shouldHide)
Called when we want to hide the DTMF Display field immediately.

param
shouldHide if true, hide the display (and disable DTMF tones) immediately; otherwise, re-enable the display.

        DTMFKeyListener.DTMFDisplayAnimation animation = mDialerKeyListener.mDTMFDisplayAnimation;

        // if the animation is in place
        if (animation != null) {
            View text = animation.mDTMFDisplay;

            // and the display is available
            if (text != null) {
                // hide the display if necessary
                text.setVisibility(shouldHide ? View.GONE : View.VISIBLE);
                if (shouldHide) {
                    // null the animation - this makes the display disappear faster
                    text.setAnimation(null);
                } else {
                    // otherwise reset the animation to the initial state.
                    animation.prepareFadeIn();
                }
            }
        }
    
booleanisKeyEventAcceptable(android.view.KeyEvent event)
Check to see if the keyEvent is dialable.

        return (mDialerKeyListener != null && mDialerKeyListener.isKeyEventAcceptable(event));
    
public booleanisOpened()

return
true if the dialer is currently opened (i.e. expanded).

        return mDialerContainer != null && mDialerContainer.isOpened();
    
private static voidlog(java.lang.String msg)
static logging method

        Log.d(LOG_TAG, msg);
    
private voidonDialerClose()
Dialer code that runs when the dialer is closed. This releases resources acquired when we start the dialer.

        if (DBG) log("onDialerClose()...");

        // reset back to a short delay for the poke lock.
        PhoneApp app = PhoneApp.getInstance();
        app.updateWakeState();

        mPhone.unregisterForDisconnect(mHandler);

        stopDialerSession();

        // Give the InCallScreen a chance to do any necessary UI updates.
        mInCallScreen.onDialerClose();
    
public booleanonDialerKeyDown(android.view.KeyEvent event)
Called externally (from InCallScreen) to play a DTMF Tone.

        if (DBG) log("Notifying dtmf key down.");
        return mDialerKeyListener.onKeyDown(event);
    
public booleanonDialerKeyUp(android.view.KeyEvent event)
Called externally (from InCallScreen) to cancel the last DTMF Tone played.

        if (DBG) log("Notifying dtmf key up.");
        return mDialerKeyListener.onKeyUp(event);
    
private voidonDialerOpen()
Dialer code that runs when the dialer is brought up. This includes layout changes, etc, and just prepares the dialer model for use.

        if (DBG) log("onDialerOpen()...");

        // inflate the view.
        mDialerView = (DTMFTwelveKeyDialerView) mInCallScreen.findViewById(R.id.dtmf_dialer);
        mDialerView.setDialer(this);

        // Have the WindowManager filter out cheek touch events
        mInCallScreen.getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);

        mPhone.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);

        // set to a longer delay while the dialer is up.
        PhoneApp app = PhoneApp.getInstance();
        app.updateWakeState();

        // setup the digit display
        mDialpadDigits = (EditText) mDialerView.findViewById(R.id.dtmfDialerField);
        mDialpadDigits.setKeyListener(new DTMFKeyListener(null));
        mDialpadDigits.requestFocus();

        // remove the long-press context menus that support
        // the edit (copy / paste / select) functions.
        mDialpadDigits.setLongClickable(false);

        // Check for the presence of the keypad (portrait mode)
        View view = mDialerView.findViewById(R.id.one);
        if (view != null) {
            if (DBG) log("portrait mode setup");
            setupKeypad();
        } else {
            if (DBG) log("landscape mode setup");
            // Adding hint text to the field to indicate that keyboard
            // is needed while in landscape mode.
            mDialpadDigits.setHint(R.string.dialerKeyboardHintText);
        }

        // setup the local tone generator.
        startDialerSession();

        // Give the InCallScreen a chance to do any necessary UI updates.
        mInCallScreen.onDialerOpen();
    
public voidonDrawerClosed()
Implemented for the SlidingDrawer close listener, release the dialer.

        onDialerClose();
    
public voidonDrawerOpened()
Implemented for the SlidingDrawer open listener, prepare the dialer.

        onDialerOpen();
    
public booleanonKey(android.view.View v, int keyCode, android.view.KeyEvent event)
Implements View.OnKeyListener for the DTMF buttons. Enables dialing with trackball/dpad.

        // if (DBG) log("onKey:  keyCode " + keyCode + ", view " + v);

        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            int viewId = v.getId();
            if (mDisplayMap.containsKey(viewId)) {
                switch (event.getAction()) {
                case KeyEvent.ACTION_DOWN:
                    if (event.getRepeatCount() == 0) {
                        processDtmf(mDisplayMap.get(viewId));
                    }
                    break;
                case KeyEvent.ACTION_UP:
                    stopTone();
                    break;
                }
                // do not return true [handled] here, since we want the
                // press / click animation to be handled by the framework.
            }
        }
        return false;
    
public booleanonKeyDown(int keyCode, android.view.KeyEvent event)
catch the back and call buttons to return to the in call activity.

        // if (DBG) log("onKeyDown:  keyCode " + keyCode);
        switch (keyCode) {
            // finish for these events
            case KeyEvent.KEYCODE_BACK:
            case KeyEvent.KEYCODE_CALL:
                if (DBG) log("exit requested");
                closeDialer(true);  // do the "closing" animation
                return true;
        }
        return mInCallScreen.onKeyDown(keyCode, event);
    
public booleanonKeyUp(int keyCode, android.view.KeyEvent event)
catch the back and call buttons to return to the in call activity.

        // if (DBG) log("onKeyUp:  keyCode " + keyCode);
        return mInCallScreen.onKeyUp(keyCode, event);
    
public voidonQueryComplete(int token, java.lang.Object cookie, com.android.internal.telephony.CallerInfo ci)
upon completion of the query, update the name field in the status.

        if (DBG) log("callerinfo query complete, updating ui.");

        ((TextView) cookie).setText(PhoneUtils.getCompactNameFromCallerInfo(ci, mInCallScreen));
    
public booleanonTouch(android.view.View v, android.view.MotionEvent event)
Implemented for the TouchListener, process the touch events.

        int viewId = v.getId();

        // if the button is recognized
        if (mDisplayMap.containsKey(viewId)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    // Append the character mapped to this button, to the display.
                    // start the tone
                    processDtmf(mDisplayMap.get(viewId));
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    // stop the tone on ANY other event, except for MOVE.
                    stopTone();
                    break;
            }
            // do not return true [handled] here, since we want the
            // press / click animation to be handled by the framework.
        }
        return false;
    
public voidopenDialer(boolean animate)
Forces the dialer into the "open" state. Does nothing if the dialer is already open.

param
animate if true, open the dialer with an animation.

        if (mDialerContainer != null && !mDialerContainer.isOpened()) {
            if (animate) {
                mDialerContainer.animateToggle();
            } else {
                mDialerContainer.toggle();
            }
        }
    
private final voidprocessDtmf(char c)
Processes the specified digit as a DTMF key, by playing the appropriate DTMF tone, and appending the digit to the EditText field that displays the DTMF digits sent so far.

        // if it is a valid key, then update the display and send the dtmf tone.
        if (PhoneNumberUtils.is12Key(c)) {
            if (DBG) log("updating display and sending dtmf tone for '" + c + "'");

            if (mDialpadDigits != null) {
                mDialpadDigits.getText().append(c);
            }

            // Note we *don't* need to manually append this digit to the
            // landscape-mode EditText field (mInCallDigits), since it
            // gets key events directly and automatically appends whetever
            // the user types.

            // Play the tone if it exists.
            if (mToneMap.containsKey(c)) {
                // begin tone playback.
                startTone(c);
            }
        } else if (DBG) {
            log("ignoring dtmf request for '" + c + "'");
        }

        // Any DTMF keypress counts as explicit "user activity".
        PhoneApp.getInstance().pokeUserActivity();
    
private voidsetupKeypad()
setup the keys on the dialer activity, using the keymaps.

        // for each view id listed in the displaymap
        View button;
        for (int viewId : mDisplayMap.keySet()) {
            // locate the view
            button = mDialerView.findViewById(viewId);
            // Setup the listeners for the buttons
            button.setOnTouchListener(this);
            button.setClickable(true);
            button.setOnKeyListener(this);
        }
    
public voidstartDialerSession()
Setup the local tone generator. Should have corresponding calls to {@link onDialerPause}.

        // see if we need to play local tones.
        mDTMFToneEnabled = Settings.System.getInt(mInCallScreen.getContentResolver(),
                Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;

        // create the tone generator
        // if the mToneGenerator creation fails, just continue without it.  It is
        // a local audio signal, and is not as important as the dtmf tone itself.
        if (mDTMFToneEnabled) {
            synchronized (mToneGeneratorLock) {
                if (mToneGenerator == null) {
                    try {
                        mToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 80);
                    } catch (RuntimeException e) {
                        if (DBG) log("Exception caught while creating local tone generator: " + e);
                        mToneGenerator = null;
                    }
                }
            }
        }
    
private voidstartTone(char tone)
Starts playing a DTMF tone. Also begins the local tone playback, if enabled.

param
tone a tone code from {@link ToneGenerator}

        if (DBG) log("startTone()...");
        PhoneApp.getInstance().phone.startDtmf(tone);

        // if local tone playback is enabled, start it.
        if (mDTMFToneEnabled) {
            synchronized (mToneGeneratorLock) {
                if (mToneGenerator == null) {
                    if (DBG) log("startTone: mToneGenerator == null, tone: " + tone);
                } else {
                    if (DBG) log("starting local tone " + tone);
                    mToneGenerator.startTone(mToneMap.get(tone));
                }
            }
        }
    
public voidstopDialerSession()
Tear down the local tone generator, corresponds to calls to {@link onDialerResume}

        // release the tone generator.
        synchronized (mToneGeneratorLock) {
            if (mToneGenerator != null) {
                mToneGenerator.release();
                mToneGenerator = null;
            }
        }
    
private voidstopTone()
Stops playing the current DTMF tone. The ToneStopper class (similar to that in {@link TwelveKeyDialer#mToneStopper}) has been removed in favor of synchronous start / stop calls since tone duration is now a function of the input.

        if (DBG) log("stopTone()...");
        PhoneApp.getInstance().phone.stopDtmf();

        // if local tone playback is enabled, stop it.
        if (DBG) log("trying to stop local tone...");
        if (mDTMFToneEnabled) {
            synchronized (mToneGeneratorLock) {
                if (mToneGenerator == null) {
                    if (DBG) log("stopTone: mToneGenerator == null");
                } else {
                    if (DBG) log("stopping local tone.");
                    mToneGenerator.stopTone();
                }
            }
        }