/* * Copyright (C) 2006 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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.internal.telephony.test; import android.os.Looper; import android.os.Message; import android.os.Handler; import android.telephony.PhoneNumberUtils; import com.android.internal.telephony.ATParseEx; import com.android.internal.telephony.DriverCall; import java.util.List; import java.util.ArrayList; import android.telephony.Rlog; class CallInfo { enum State { ACTIVE(0), HOLDING(1), DIALING(2), // MO call only ALERTING(3), // MO call only INCOMING(4), // MT call only WAITING(5); // MT call only State(int value) {mValue = value;} private final int mValue; public int value() {return mValue;} } boolean mIsMT; State mState; boolean mIsMpty; String mNumber; int mTOA; CallInfo (boolean isMT, State state, boolean isMpty, String number) { mIsMT = isMT; mState = state; mIsMpty = isMpty; mNumber = number; if (number.length() > 0 && number.charAt(0) == '+') { mTOA = PhoneNumberUtils.TOA_International; } else { mTOA = PhoneNumberUtils.TOA_Unknown; } } static CallInfo createOutgoingCall(String number) { return new CallInfo (false, State.DIALING, false, number); } static CallInfo createIncomingCall(String number) { return new CallInfo (true, State.INCOMING, false, number); } String toCLCCLine(int index) { return "+CLCC: " + index + "," + (mIsMT ? "1" : "0") +"," + mState.value() + ",0," + (mIsMpty ? "1" : "0") + ",\"" + mNumber + "\"," + mTOA; } DriverCall toDriverCall(int index) { DriverCall ret; ret = new DriverCall(); ret.index = index; ret.isMT = mIsMT; try { ret.state = DriverCall.stateFromCLCC(mState.value()); } catch (ATParseEx ex) { throw new RuntimeException("should never happen", ex); } ret.isMpty = mIsMpty; ret.number = mNumber; ret.TOA = mTOA; ret.isVoice = true; ret.als = 0; return ret; } boolean isActiveOrHeld() { return mState == State.ACTIVE || mState == State.HOLDING; } boolean isConnecting() { return mState == State.DIALING || mState == State.ALERTING; } boolean isRinging() { return mState == State.INCOMING || mState == State.WAITING; } } class InvalidStateEx extends Exception { InvalidStateEx() { } } class SimulatedGsmCallState extends Handler { //***** Instance Variables CallInfo mCalls[] = new CallInfo[MAX_CALLS]; private boolean mAutoProgressConnecting = true; private boolean mNextDialFailImmediately; //***** Event Constants static final int EVENT_PROGRESS_CALL_STATE = 1; //***** Constants static final int MAX_CALLS = 7; /** number of msec between dialing -> alerting and alerting->active */ static final int CONNECTING_PAUSE_MSEC = 5 * 100; //***** Overridden from Handler public SimulatedGsmCallState(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { synchronized(this) { switch (msg.what) { // PLEASE REMEMBER // calls may have hung up by the time delayed events happen case EVENT_PROGRESS_CALL_STATE: progressConnectingCallState(); break; }} } //***** Public Methods /** * Start the simulated phone ringing * true if succeeded, false if failed */ public boolean triggerRing(String number) { synchronized (this) { int empty = -1; boolean isCallWaiting = false; // ensure there aren't already calls INCOMING or WAITING for (int i = 0 ; i < mCalls.length ; i++) { CallInfo call = mCalls[i]; if (call == null && empty < 0) { empty = i; } else if (call != null && (call.mState == CallInfo.State.INCOMING || call.mState == CallInfo.State.WAITING) ) { Rlog.w("ModelInterpreter", "triggerRing failed; phone already ringing"); return false; } else if (call != null) { isCallWaiting = true; } } if (empty < 0 ) { Rlog.w("ModelInterpreter", "triggerRing failed; all full"); return false; } mCalls[empty] = CallInfo.createIncomingCall( PhoneNumberUtils.extractNetworkPortion(number)); if (isCallWaiting) { mCalls[empty].mState = CallInfo.State.WAITING; } } return true; } /** If a call is DIALING or ALERTING, progress it to the next state */ public void progressConnectingCallState() { synchronized (this) { for (int i = 0 ; i < mCalls.length ; i++) { CallInfo call = mCalls[i]; if (call != null && call.mState == CallInfo.State.DIALING) { call.mState = CallInfo.State.ALERTING; if (mAutoProgressConnecting) { sendMessageDelayed( obtainMessage(EVENT_PROGRESS_CALL_STATE, call), CONNECTING_PAUSE_MSEC); } break; } else if (call != null && call.mState == CallInfo.State.ALERTING ) { call.mState = CallInfo.State.ACTIVE; break; } } } } /** If a call is DIALING or ALERTING, progress it all the way to ACTIVE */ public void progressConnectingToActive() { synchronized (this) { for (int i = 0 ; i < mCalls.length ; i++) { CallInfo call = mCalls[i]; if (call != null && (call.mState == CallInfo.State.DIALING || call.mState == CallInfo.State.ALERTING) ) { call.mState = CallInfo.State.ACTIVE; break; } } } } /** automatically progress mobile originated calls to ACTIVE. * default to true */ public void setAutoProgressConnectingCall(boolean b) { mAutoProgressConnecting = b; } public void setNextDialFailImmediately(boolean b) { mNextDialFailImmediately = b; } /** * hangup ringing, dialing, or active calls * returns true if call was hung up, false if not */ public boolean triggerHangupForeground() { synchronized (this) { boolean found; found = false; for (int i = 0 ; i < mCalls.length ; i++) { CallInfo call = mCalls[i]; if (call != null && (call.mState == CallInfo.State.INCOMING || call.mState == CallInfo.State.WAITING) ) { mCalls[i] = null; found = true; } } for (int i = 0 ; i < mCalls.length ; i++) { CallInfo call = mCalls[i]; if (call != null && (call.mState == CallInfo.State.DIALING || call.mState == CallInfo.State.ACTIVE || call.mState == CallInfo.State.ALERTING) ) { mCalls[i] = null; found = true; } } return found; } } /** * hangup holding calls * returns true if call was hung up, false if not */ public boolean triggerHangupBackground() { synchronized (this) { boolean found = false; for (int i = 0 ; i < mCalls.length ; i++) { CallInfo call = mCalls[i]; if (call != null && call.mState == CallInfo.State.HOLDING) { mCalls[i] = null; found = true; } } return found; } } /** * hangup all * returns true if call was hung up, false if not */ public boolean triggerHangupAll() { synchronized(this) { boolean found = false; for (int i = 0 ; i < mCalls.length ; i++) { CallInfo call = mCalls[i]; if (mCalls[i] != null) { found = true; } mCalls[i] = null; } return found; } } public boolean onAnswer() { synchronized (this) { for (int i = 0 ; i < mCalls.length ; i++) { CallInfo call = mCalls[i]; if (call != null && (call.mState == CallInfo.State.INCOMING || call.mState == CallInfo.State.WAITING) ) { return switchActiveAndHeldOrWaiting(); } } } return false; } public boolean onHangup() { boolean found = false; for (int i = 0 ; i < mCalls.length ; i++) { CallInfo call = mCalls[i]; if (call != null && call.mState != CallInfo.State.WAITING) { mCalls[i] = null; found = true; } } return found; } public boolean onChld(char c0, char c1) { boolean ret; int callIndex = 0; if (c1 != 0) { callIndex = c1 - '1'; if (callIndex < 0 || callIndex >= mCalls.length) { return false; } } switch (c0) { case '0': ret = releaseHeldOrUDUB(); break; case '1': if (c1 <= 0) { ret = releaseActiveAcceptHeldOrWaiting(); } else { if (mCalls[callIndex] == null) { ret = false; } else { mCalls[callIndex] = null; ret = true; } } break; case '2': if (c1 <= 0) { ret = switchActiveAndHeldOrWaiting(); } else { ret = separateCall(callIndex); } break; case '3': ret = conference(); break; case '4': ret = explicitCallTransfer(); break; case '5': if (true) { //just so javac doesnt complain about break //CCBS not impled ret = false; } break; default: ret = false; } return ret; } public boolean releaseHeldOrUDUB() { boolean found = false; for (int i = 0 ; i < mCalls.length ; i++) { CallInfo c = mCalls[i]; if (c != null && c.isRinging()) { found = true; mCalls[i] = null; break; } } if (!found) { for (int i = 0 ; i < mCalls.length ; i++) { CallInfo c = mCalls[i]; if (c != null && c.mState == CallInfo.State.HOLDING) { found = true; mCalls[i] = null; // don't stop...there may be more than one } } } return true; } public boolean releaseActiveAcceptHeldOrWaiting() { boolean foundHeld = false; boolean foundActive = false; for (int i = 0 ; i < mCalls.length ; i++) { CallInfo c = mCalls[i]; if (c != null && c.mState == CallInfo.State.ACTIVE) { mCalls[i] = null; foundActive = true; } } if (!foundActive) { // FIXME this may not actually be how most basebands react // CHLD=1 may not hang up dialing/alerting calls for (int i = 0 ; i < mCalls.length ; i++) { CallInfo c = mCalls[i]; if (c != null && (c.mState == CallInfo.State.DIALING || c.mState == CallInfo.State.ALERTING) ) { mCalls[i] = null; foundActive = true; } } } for (int i = 0 ; i < mCalls.length ; i++) { CallInfo c = mCalls[i]; if (c != null && c.mState == CallInfo.State.HOLDING) { c.mState = CallInfo.State.ACTIVE; foundHeld = true; } } if (foundHeld) { return true; } for (int i = 0 ; i < mCalls.length ; i++) { CallInfo c = mCalls[i]; if (c != null && c.isRinging()) { c.mState = CallInfo.State.ACTIVE; return true; } } return true; } public boolean switchActiveAndHeldOrWaiting() { boolean hasHeld = false; // first, are there held calls? for (int i = 0 ; i < mCalls.length ; i++) { CallInfo c = mCalls[i]; if (c != null && c.mState == CallInfo.State.HOLDING) { hasHeld = true; break; } } // Now, switch for (int i = 0 ; i < mCalls.length ; i++) { CallInfo c = mCalls[i]; if (c != null) { if (c.mState == CallInfo.State.ACTIVE) { c.mState = CallInfo.State.HOLDING; } else if (c.mState == CallInfo.State.HOLDING) { c.mState = CallInfo.State.ACTIVE; } else if (!hasHeld && c.isRinging()) { c.mState = CallInfo.State.ACTIVE; } } } return true; } public boolean separateCall(int index) { try { CallInfo c; c = mCalls[index]; if (c == null || c.isConnecting() || countActiveLines() != 1) { return false; } c.mState = CallInfo.State.ACTIVE; c.mIsMpty = false; for (int i = 0 ; i < mCalls.length ; i++) { int countHeld=0, lastHeld=0; if (i != index) { CallInfo cb = mCalls[i]; if (cb != null && cb.mState == CallInfo.State.ACTIVE) { cb.mState = CallInfo.State.HOLDING; countHeld++; lastHeld = i; } } if (countHeld == 1) { // if there's only one left, clear the MPTY flag mCalls[lastHeld].mIsMpty = false; } } return true; } catch (InvalidStateEx ex) { return false; } } public boolean conference() { int countCalls = 0; // if there's connecting calls, we can't do this yet for (int i = 0 ; i < mCalls.length ; i++) { CallInfo c = mCalls[i]; if (c != null) { countCalls++; if (c.isConnecting()) { return false; } } } for (int i = 0 ; i < mCalls.length ; i++) { CallInfo c = mCalls[i]; if (c != null) { c.mState = CallInfo.State.ACTIVE; if (countCalls > 0) { c.mIsMpty = true; } } } return true; } public boolean explicitCallTransfer() { int countCalls = 0; // if there's connecting calls, we can't do this yet for (int i = 0 ; i < mCalls.length ; i++) { CallInfo c = mCalls[i]; if (c != null) { countCalls++; if (c.isConnecting()) { return false; } } } // disconnect the subscriber from both calls return triggerHangupAll(); } public boolean onDial(String address) { CallInfo call; int freeSlot = -1; Rlog.d("GSM", "SC> dial '" + address + "'"); if (mNextDialFailImmediately) { mNextDialFailImmediately = false; Rlog.d("GSM", "SC< dial fail (per request)"); return false; } String phNum = PhoneNumberUtils.extractNetworkPortion(address); if (phNum.length() == 0) { Rlog.d("GSM", "SC< dial fail (invalid ph num)"); return false; } // Ignore setting up GPRS if (phNum.startsWith("*99") && phNum.endsWith("#")) { Rlog.d("GSM", "SC< dial ignored (gprs)"); return true; } // There can be at most 1 active "line" when we initiate // a new call try { if (countActiveLines() > 1) { Rlog.d("GSM", "SC< dial fail (invalid call state)"); return false; } } catch (InvalidStateEx ex) { Rlog.d("GSM", "SC< dial fail (invalid call state)"); return false; } for (int i = 0 ; i < mCalls.length ; i++) { if (freeSlot < 0 && mCalls[i] == null) { freeSlot = i; } if (mCalls[i] != null && !mCalls[i].isActiveOrHeld()) { // Can't make outgoing calls when there is a ringing or // connecting outgoing call Rlog.d("GSM", "SC< dial fail (invalid call state)"); return false; } else if (mCalls[i] != null && mCalls[i].mState == CallInfo.State.ACTIVE) { // All active calls behome held mCalls[i].mState = CallInfo.State.HOLDING; } } if (freeSlot < 0) { Rlog.d("GSM", "SC< dial fail (invalid call state)"); return false; } mCalls[freeSlot] = CallInfo.createOutgoingCall(phNum); if (mAutoProgressConnecting) { sendMessageDelayed( obtainMessage(EVENT_PROGRESS_CALL_STATE, mCalls[freeSlot]), CONNECTING_PAUSE_MSEC); } Rlog.d("GSM", "SC< dial (slot = " + freeSlot + ")"); return true; } public List getDriverCalls() { ArrayList ret = new ArrayList(mCalls.length); for (int i = 0 ; i < mCalls.length ; i++) { CallInfo c = mCalls[i]; if (c != null) { DriverCall dc; dc = c.toDriverCall(i + 1); ret.add(dc); } } Rlog.d("GSM", "SC< getDriverCalls " + ret); return ret; } public List getClccLines() { ArrayList ret = new ArrayList(mCalls.length); for (int i = 0 ; i < mCalls.length ; i++) { CallInfo c = mCalls[i]; if (c != null) { ret.add((c.toCLCCLine(i + 1))); } } return ret; } private int countActiveLines() throws InvalidStateEx { boolean hasMpty = false; boolean hasHeld = false; boolean hasActive = false; boolean hasConnecting = false; boolean hasRinging = false; boolean mptyIsHeld = false; for (int i = 0 ; i < mCalls.length ; i++) { CallInfo call = mCalls[i]; if (call != null) { if (!hasMpty && call.mIsMpty) { mptyIsHeld = call.mState == CallInfo.State.HOLDING; } else if (call.mIsMpty && mptyIsHeld && call.mState == CallInfo.State.ACTIVE ) { Rlog.e("ModelInterpreter", "Invalid state"); throw new InvalidStateEx(); } else if (!call.mIsMpty && hasMpty && mptyIsHeld && call.mState == CallInfo.State.HOLDING ) { Rlog.e("ModelInterpreter", "Invalid state"); throw new InvalidStateEx(); } hasMpty |= call.mIsMpty; hasHeld |= call.mState == CallInfo.State.HOLDING; hasActive |= call.mState == CallInfo.State.ACTIVE; hasConnecting |= call.isConnecting(); hasRinging |= call.isRinging(); } } int ret = 0; if (hasHeld) ret++; if (hasActive) ret++; if (hasConnecting) ret++; if (hasRinging) ret++; return ret; } }