FileDocCategorySizeDatePackage
GSMConnection.javaAPI DocAndroid 1.5 API23184Wed May 06 22:42:02 BST 2009com.android.internal.telephony.gsm

GSMConnection

public class GSMConnection extends Connection
{@hide}

Fields Summary
static final String
LOG_TAG
CallTracker
owner
GSMCall
parent
String
address
String
dialString
String
postDialString
boolean
isIncoming
boolean
disconnected
int
index
long
createTime
long
connectTime
long
disconnectTime
long
connectTimeReal
long
duration
long
holdingStartTime
int
nextPostDialChar
DisconnectCause
cause
PostDialState
postDialState
int
numberPresentation
android.os.Handler
h
private PowerManager.WakeLock
mPartialWakeLock
static final int
EVENT_DTMF_DONE
static final int
EVENT_PAUSE_DONE
static final int
EVENT_NEXT_POST_DIAL
static final int
EVENT_WAKE_LOCK_TIMEOUT
static final int
PAUSE_DELAY_FIRST_MILLIS
static final int
PAUSE_DELAY_MILLIS
static final int
WAKE_LOCK_TIMEOUT_MILLIS
Constructors Summary
GSMConnection(android.content.Context context, DriverCall dc, CallTracker ct, int index)
This is probably an MT call that we first saw in a CLCC response

        createWakeLock(context);
        acquireWakeLock();

        owner = ct;
        h = new MyHandler(owner.getLooper());

        address = dc.number;

        isIncoming = dc.isMT;
        createTime = System.currentTimeMillis();
        numberPresentation = dc.numberPresentation;

        this.index = index;

        parent = parentFromDCState (dc.state);
        parent.attach(this, dc);
    
GSMConnection(android.content.Context context, String dialString, CallTracker ct, GSMCall parent)
This is an MO call, created when dialing

        createWakeLock(context);
        acquireWakeLock();

        owner = ct;
        h = new MyHandler(owner.getLooper());

        this.dialString = dialString;

        this.address = PhoneNumberUtils.extractNetworkPortion(dialString);
        this.postDialString = PhoneNumberUtils.extractPostDialPortion(dialString);

        index = -1;

        isIncoming = false;
        createTime = System.currentTimeMillis();

        this.parent = parent;
        parent.attachFake(this, Call.State.DIALING);
    
Methods Summary
private voidacquireWakeLock()

        log("acquireWakeLock");
        mPartialWakeLock.acquire();
    
public voidcancelPostDial()

        setPostDialState(PostDialState.CANCELLED);
    
booleancompareTo(DriverCall c)

        // On mobile originated (MO) calls, the phone number may have changed
        // due to a SIM Toolkit call control modification.
        //
        // We assume we know when MO calls are created (since we created them)
        // and therefore don't need to compare the phone number anyway.
        if (! (isIncoming || c.isMT)) return true;

        // ... but we can compare phone numbers on MT calls, and we have
        // no control over when they begin, so we might as well

        String cAddress = PhoneNumberUtils.stringFromStringAndTOA(c.number, c.TOA);
        return isIncoming == c.isMT && equalsHandlesNulls(address, cAddress); 
    
private voidcreateWakeLock(android.content.Context context)

        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
    
DisconnectCausedisconnectCauseFromCode(int causeCode)

        /**
         * See 22.001 Annex F.4 for mapping of cause codes
         * to local tones
         */
    
        switch (causeCode) {
            case CallFailCause.USER_BUSY:
                return DisconnectCause.BUSY;

            case CallFailCause.NO_CIRCUIT_AVAIL:
            case CallFailCause.TEMPORARY_FAILURE:
            case CallFailCause.SWITCHING_CONGESTION:
            case CallFailCause.CHANNEL_NOT_AVAIL:
            case CallFailCause.QOS_NOT_AVAIL:
            case CallFailCause.BEARER_NOT_AVAIL:
                return DisconnectCause.CONGESTION;

            case CallFailCause.ACM_LIMIT_EXCEEDED:
                return DisconnectCause.LIMIT_EXCEEDED;

            case CallFailCause.CALL_BARRED:
                return DisconnectCause.CALL_BARRED;

            case CallFailCause.FDN_BLOCKED:
                return DisconnectCause.FDN_BLOCKED;

            case CallFailCause.ERROR_UNSPECIFIED:
            case CallFailCause.NORMAL_CLEARING: 
            default:
                GSMPhone phone = owner.phone;
                int serviceState = phone.getServiceState().getState();
                if (serviceState == ServiceState.STATE_POWER_OFF) {
                    return DisconnectCause.POWER_OFF;
                } else if (serviceState == ServiceState.STATE_OUT_OF_SERVICE
                        || serviceState == ServiceState.STATE_EMERGENCY_ONLY ) {
                    return DisconnectCause.OUT_OF_SERVICE;
                } else if (phone.getSimCard().getState() != GsmSimCard.State.READY) {
                    return DisconnectCause.SIM_ERROR;
                } else if (causeCode == CallFailCause.ERROR_UNSPECIFIED) {
                    if (phone.mSST.rs.isCsRestricted()) {
                        return DisconnectCause.CS_RESTRICTED; 
                    } else if (phone.mSST.rs.isCsEmergencyRestricted()) {
                        return DisconnectCause.CS_RESTRICTED_EMERGENCY;
                    } else if (phone.mSST.rs.isCsNormalRestricted()) {
                        return DisconnectCause.CS_RESTRICTED_NORMAL;
                    } else {
                        return DisconnectCause.NORMAL;
                    }
                } else {
                    return DisconnectCause.NORMAL;
                }
        }
    
static booleanequalsHandlesNulls(java.lang.Object a, java.lang.Object b)

        return (a == null) ? (b == null) : a.equals (b);
    
voidfakeHoldBeforeDial()
Called when this Connection is in the foregroundCall when a dial is initiated. We know we're ACTIVE, and we know we're going to end up HOLDING in the backgroundCall

        if (parent != null) {
            parent.detach(this);
        }

        parent = owner.backgroundCall;
        parent.attachFake(this, Call.State.HOLDING);

        onStartedHolding();
    
protected voidfinalize()

        /**
         * It is understood that This finializer is not guaranteed
         * to be called and the release lock call is here just in
         * case there is some path that doesn't call onDisconnect
         * and or onConnectedInOrOut.
         */
        if (mPartialWakeLock.isHeld()) {
            Log.e(LOG_TAG, "[GSMConn] UNEXPECTED; mPartialWakeLock is held when finalizing.");
        }
        releaseWakeLock();
    
public java.lang.StringgetAddress()

        return address; 
    
public CallgetCall()

        return parent;
    
public longgetConnectTime()

        return connectTime;
    
public longgetCreateTime()

        return createTime;
    
public DisconnectCausegetDisconnectCause()

        return cause;
    
public longgetDisconnectTime()

        return disconnectTime;
    
public longgetDurationMillis()

        if (connectTimeReal == 0) {
            return 0;
        } else if (duration == 0) {
            return SystemClock.elapsedRealtime() - connectTimeReal;
        } else {
            return duration;
        }
    
intgetGSMIndex()

        if (index >= 0) {
            return index + 1;
        } else {
            throw new CallStateException ("GSM index not yet assigned");
        }
    
public longgetHoldDurationMillis()

        if (getState() != Call.State.HOLDING) {
            // If not holding, return 0
            return 0;
        } else {
            return SystemClock.elapsedRealtime() - holdingStartTime;
        }
    
public intgetNumberPresentation()

        return numberPresentation;
    
public PostDialStategetPostDialState()

        return postDialState;
    
public java.lang.StringgetRemainingPostDialString()

        if (postDialState == PostDialState.CANCELLED 
            || postDialState == PostDialState.COMPLETE
            || postDialString == null
            || postDialString.length() <= nextPostDialChar
        ) {
            return "";
        }

        return postDialString.substring(nextPostDialChar);
    
public Call.StategetState()

        if (disconnected) {
            return Call.State.DISCONNECTED;
        } else {   
            return super.getState();
        }
    
public voidhangup()

        if (!disconnected) {        
            owner.hangup(this);
        } else {
            throw new CallStateException ("disconnected");
        }
    
private booleanisConnectingInOrOut()
"connecting" means "has never been ACTIVE" for both incoming and outgoing calls

        return parent == null || parent == owner.ringingCall 
            || parent.state == Call.State.DIALING 
            || parent.state == Call.State.ALERTING;
    
public booleanisIncoming()

        return isIncoming;
    
private voidlog(java.lang.String msg)

        Log.d(LOG_TAG, "[GSMConn] " + msg);
    
voidonConnectedInOrOut()
An incoming or outgoing call has connected

        connectTime = System.currentTimeMillis();
        connectTimeReal = SystemClock.elapsedRealtime();
        duration = 0;

        // bug #678474: incoming call interpreted as missed call, even though
        // it sounds like the user has picked up the call.
        if (Phone.DEBUG_PHONE) {
            log("onConnectedInOrOut: connectTime=" + connectTime);
        }

        if (!isIncoming) {
            // outgoing calls only
            processNextPostDialChar();
        }
        releaseWakeLock();
    
voidonDisconnect(DisconnectCause cause)
Called when the radio indicates the connection has been disconnected

        this.cause = cause;
        
        if (!disconnected) {        
            index = -1;
            
            disconnectTime = System.currentTimeMillis();
            duration = SystemClock.elapsedRealtime() - connectTimeReal;
            disconnected = true;

            if (Config.LOGD) Log.d(LOG_TAG,
                    "[GSMConn] onDisconnect: cause=" + cause);

            owner.phone.notifyDisconnect(this);

            if (parent != null) {
                parent.connectionDisconnected(this);            
            }
        }
        releaseWakeLock();
    
voidonHangupLocal()
Called when this Connection is being hung up locally (eg, user pressed "end") Note that at this point, the hangup request has been dispatched to the radio but no response has yet been received so update() has not yet been called

        cause = DisconnectCause.LOCAL;
    
voidonRemoteDisconnect(int causeCode)

        onDisconnect(disconnectCauseFromCode(causeCode));
    
private voidonStartedHolding()

        holdingStartTime = SystemClock.elapsedRealtime();
    
private GSMCallparentFromDCState(DriverCall.State state)

        switch (state) {
            case ACTIVE:
            case DIALING:
            case ALERTING:
                return owner.foregroundCall;
            //break;

            case HOLDING:
                return owner.backgroundCall;
            //break;

            case INCOMING:
            case WAITING:
                return owner.ringingCall;
            //break;

            default:
                throw new RuntimeException("illegal call state: " + state);
        }
    
public voidproceedAfterWaitChar()

        if (postDialState != PostDialState.WAIT) {
            Log.w(LOG_TAG, "Connection.proceedAfterWaitChar(): Expected " 
                + "getPostDialState() to be WAIT but was " + postDialState);
            return;
        }

        setPostDialState(PostDialState.STARTED);

        processNextPostDialChar();
    
public voidproceedAfterWildChar(java.lang.String str)

        if (postDialState != PostDialState.WILD) {
            Log.w(LOG_TAG, "Connection.proceedAfterWaitChar(): Expected " 
                + "getPostDialState() to be WILD but was " + postDialState);
            return;
        }

        setPostDialState(PostDialState.STARTED);

        if (false) {
            boolean playedTone = false;
            int len = (str != null ? str.length() : 0);

            for (int i=0; i<len; i++) {
                char c = str.charAt(i);
                Message msg = null;

                if (i == len-1) {
                    msg = h.obtainMessage(EVENT_DTMF_DONE);
                }

                if (PhoneNumberUtils.is12Key(c)) {
                    owner.cm.sendDtmf(c, msg);
                    playedTone = true;
                }
            }

            if (!playedTone) {
                processNextPostDialChar();
            }
        } else {
            // make a new postDialString, with the wild char replacement string
            // at the beginning, followed by the remaining postDialString.

            StringBuilder buf = new StringBuilder(str);
            buf.append(postDialString.substring(nextPostDialChar));
            postDialString = buf.toString();
            nextPostDialChar = 0;
            if (Phone.DEBUG_PHONE) {
                log("proceedAfterWildChar: new postDialString is " + 
                        postDialString);
            }

            processNextPostDialChar();
        }
    
private voidprocessNextPostDialChar()

        char c = 0;
        Registrant postDialHandler;

        if (postDialState == PostDialState.CANCELLED) {
            //Log.v("GSM", "##### processNextPostDialChar: postDialState == CANCELLED, bail");
            return;
        }

        if (postDialString == null ||
                postDialString.length() <= nextPostDialChar) {
            setPostDialState(PostDialState.COMPLETE);

            // notifyMessage.arg1 is 0 on complete
            c = 0;
        } else {
            boolean isValid;
            
            setPostDialState(PostDialState.STARTED);

            c = postDialString.charAt(nextPostDialChar++);

            isValid = processPostDialChar(c);

            if (!isValid) {
                // Will call processNextPostDialChar
                h.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget();
                // Don't notify application
                Log.e("GSM", "processNextPostDialChar: c=" + c + " isn't valid!");
                return;
            }
        }

        postDialHandler = owner.phone.mPostDialHandler;

        Message notifyMessage;

        if (postDialHandler != null && (notifyMessage = postDialHandler.messageForRegistrant()) != null) {
            // The AsyncResult.result is the Connection object
            PostDialState state = postDialState;
            AsyncResult ar = AsyncResult.forMessage(notifyMessage);
            ar.result = this;
            ar.userObj = state;

            // arg1 is the character that was/is being processed
            notifyMessage.arg1 = c;

            //Log.v("GSM", "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c);
            notifyMessage.sendToTarget();
        }
        /*
        else {
            if (postDialHandler == null)
                Log.v("GSM", "##### processNextPostDialChar: postDialHandler is NULL!");
            else
                Log.v("GSM", "##### processNextPostDialChar: postDialHandler.messageForRegistrant() returned NULL!");
        }
        */
    
private booleanprocessPostDialChar(char c)
Performs the appropriate action for a post-dial char, but does not notify application. returns false if the character is invalid and should be ignored

        if (PhoneNumberUtils.is12Key(c)) {
            owner.cm.sendDtmf(c, h.obtainMessage(EVENT_DTMF_DONE));
        } else if (c == PhoneNumberUtils.PAUSE) {
            // From TS 22.101:

            // "The first occurrence of the "DTMF Control Digits Separator" 
            //  shall be used by the ME to distinguish between the addressing 
            //  digits (i.e. the phone number) and the DTMF digits...."

            if (nextPostDialChar == 1) {
                // The first occurrence.
                // We don't need to pause here, but wait for just a bit anyway
                h.sendMessageDelayed(h.obtainMessage(EVENT_PAUSE_DONE), 
                                            PAUSE_DELAY_FIRST_MILLIS);
            } else {
                // It continues...
                // "Upon subsequent occurrences of the separator, the UE shall 
                //  pause again for 3 seconds (\u00B1 20 %) before sending any 
                //  further DTMF digits."
                h.sendMessageDelayed(h.obtainMessage(EVENT_PAUSE_DONE), 
                                            PAUSE_DELAY_MILLIS);
            }
        } else if (c == PhoneNumberUtils.WAIT) {
            setPostDialState(PostDialState.WAIT);
        } else if (c == PhoneNumberUtils.WILD) {
            setPostDialState(PostDialState.WILD);
        } else {
            return false;
        }

        return true;
    
private voidreleaseWakeLock()

        synchronized(mPartialWakeLock) {
            if (mPartialWakeLock.isHeld()) {
                log("releaseWakeLock");
                mPartialWakeLock.release();
            }
        }
    
public voidseparate()

        if (!disconnected) {        
            owner.separate(this);
        } else {
            throw new CallStateException ("disconnected");
        }
    
private voidsetPostDialState(PostDialState s)
Set post dial state and acquire wake lock while switching to "started" state, the wake lock will be released if state switches out of "started" state or after WAKE_LOCK_TIMEOUT_MILLIS.

param
s new PostDialState

        if (postDialState != PostDialState.STARTED 
                && s == PostDialState.STARTED) {
            acquireWakeLock();
            Message msg = h.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
            h.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS);
        } else if (postDialState == PostDialState.STARTED 
                && s != PostDialState.STARTED) {
            h.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
            releaseWakeLock();
        }
        postDialState = s;
    
public java.lang.StringtoString()

        return (isIncoming ? "incoming" : "outgoing");
    
booleanupdate(DriverCall dc)

        GSMCall newParent;
        boolean changed = false;
        boolean wasConnectingInOrOut = isConnectingInOrOut();
        boolean wasHolding = (getState() == Call.State.HOLDING);

        newParent = parentFromDCState(dc.state);

        if (!equalsHandlesNulls(address, dc.number)) {
            if (Phone.DEBUG_PHONE) log("update: phone # changed!");
            address = dc.number;
            changed = true;
        }

        if (newParent != parent) {
            if (parent != null) {
                parent.detach(this);
            }
            newParent.attach(this, dc);
            parent = newParent;
            changed = true;
        } else {
            boolean parentStateChange;
            parentStateChange = parent.update (this, dc);
            changed = changed || parentStateChange;
        }

        /** Some state-transition events */

        if (Phone.DEBUG_PHONE) log(
                "update: parent=" + parent +
                ", hasNewParent=" + (newParent != parent) +
                ", wasConnectingInOrOut=" + wasConnectingInOrOut +
                ", wasHolding=" + wasHolding +
                ", isConnectingInOrOut=" + isConnectingInOrOut() +
                ", changed=" + changed);


        if (wasConnectingInOrOut && !isConnectingInOrOut()) {
            onConnectedInOrOut();
        }

        if (changed && !wasHolding && (getState() == Call.State.HOLDING)) {
            // We've transitioned into HOLDING
            onStartedHolding();
        }

        return changed;