/* * 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.util.Log; 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) {this.value = value;} private final int value; public int value() {return value;}; }; boolean isMT; State state; boolean isMpty; String number; int TOA; CallInfo (boolean isMT, State state, boolean isMpty, String number) { this.isMT = isMT; this.state = state; this.isMpty = isMpty; this.number = number; if (number.length() > 0 && number.charAt(0) == '+') { TOA = PhoneNumberUtils.TOA_International; } else { TOA = 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 + "," + (isMT ? "1" : "0") +"," + state.value() + ",0," + (isMpty ? "1" : "0") + ",\"" + number + "\"," + TOA; } DriverCall toDriverCall(int index) { DriverCall ret; ret = new DriverCall(); ret.index = index; ret.isMT = isMT; try { ret.state = DriverCall.stateFromCLCC(state.value()); } catch (ATParseEx ex) { throw new RuntimeException("should never happen", ex); } ret.isMpty = isMpty; ret.number = number; ret.TOA = TOA; ret.isVoice = true; ret.als = 0; return ret; } boolean isActiveOrHeld() { return state == State.ACTIVE || state == State.HOLDING; } boolean isConnecting() { return state == State.DIALING || state == State.ALERTING; } boolean isRinging() { return state == State.INCOMING || state == State.WAITING; } } class InvalidStateEx extends Exception { InvalidStateEx() { } } class SimulatedGsmCallState extends Handler { //***** Instance Variables CallInfo calls[] = new CallInfo[MAX_CALLS]; private boolean autoProgressConnecting = true; private boolean nextDialFailImmediately; //***** 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); } 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 < calls.length ; i++) { CallInfo call = calls[i]; if (call == null && empty < 0) { empty = i; } else if (call != null && (call.state == CallInfo.State.INCOMING || call.state == CallInfo.State.WAITING) ) { Log.w("ModelInterpreter", "triggerRing failed; phone already ringing"); return false; } else if (call != null) { isCallWaiting = true; } } if (empty < 0 ) { Log.w("ModelInterpreter", "triggerRing failed; all full"); return false; } calls[empty] = CallInfo.createIncomingCall( PhoneNumberUtils.extractNetworkPortion(number)); if (isCallWaiting) { calls[empty].state = 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 < calls.length ; i++) { CallInfo call = calls[i]; if (call != null && call.state == CallInfo.State.DIALING) { call.state = CallInfo.State.ALERTING; if (autoProgressConnecting) { sendMessageDelayed( obtainMessage(EVENT_PROGRESS_CALL_STATE, call), CONNECTING_PAUSE_MSEC); } break; } else if (call != null && call.state == CallInfo.State.ALERTING ) { call.state = 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 < calls.length ; i++) { CallInfo call = calls[i]; if (call != null && (call.state == CallInfo.State.DIALING || call.state == CallInfo.State.ALERTING) ) { call.state = CallInfo.State.ACTIVE; break; } } } } /** automatically progress mobile originated calls to ACTIVE. * default to true */ public void setAutoProgressConnectingCall(boolean b) { autoProgressConnecting = b; } public void setNextDialFailImmediately(boolean b) { nextDialFailImmediately = 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 < calls.length ; i++) { CallInfo call = calls[i]; if (call != null && (call.state == CallInfo.State.INCOMING || call.state == CallInfo.State.WAITING) ) { calls[i] = null; found = true; } } for (int i = 0 ; i < calls.length ; i++) { CallInfo call = calls[i]; if (call != null && (call.state == CallInfo.State.DIALING || call.state == CallInfo.State.ACTIVE || call.state == CallInfo.State.ALERTING) ) { calls[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 < calls.length ; i++) { CallInfo call = calls[i]; if (call != null && call.state == CallInfo.State.HOLDING) { calls[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 < calls.length ; i++) { CallInfo call = calls[i]; if (calls[i] != null) { found = true; } calls[i] = null; } return found; } } public boolean onAnswer() { synchronized (this) { for (int i = 0 ; i < calls.length ; i++) { CallInfo call = calls[i]; if (call != null && (call.state == CallInfo.State.INCOMING || call.state == CallInfo.State.WAITING) ) { return switchActiveAndHeldOrWaiting(); } } } return false; } public boolean onHangup() { boolean found = false; for (int i = 0 ; i < calls.length ; i++) { CallInfo call = calls[i]; if (call != null && call.state != CallInfo.State.WAITING) { calls[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 >= calls.length) { return false; } } switch (c0) { case '0': ret = releaseHeldOrUDUB(); break; case '1': if (c1 <= 0) { ret = releaseActiveAcceptHeldOrWaiting(); } else { if (calls[callIndex] == null) { ret = false; } else { calls[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 < calls.length ; i++) { CallInfo c = calls[i]; if (c != null && c.isRinging()) { found = true; calls[i] = null; break; } } if (!found) { for (int i = 0 ; i < calls.length ; i++) { CallInfo c = calls[i]; if (c != null && c.state == CallInfo.State.HOLDING) { found = true; calls[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 < calls.length ; i++) { CallInfo c = calls[i]; if (c != null && c.state == CallInfo.State.ACTIVE) { calls[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 < calls.length ; i++) { CallInfo c = calls[i]; if (c != null && (c.state == CallInfo.State.DIALING || c.state == CallInfo.State.ALERTING) ) { calls[i] = null; foundActive = true; } } } for (int i = 0 ; i < calls.length ; i++) { CallInfo c = calls[i]; if (c != null && c.state == CallInfo.State.HOLDING) { c.state = CallInfo.State.ACTIVE; foundHeld = true; } } if (foundHeld) { return true; } for (int i = 0 ; i < calls.length ; i++) { CallInfo c = calls[i]; if (c != null && c.isRinging()) { c.state = CallInfo.State.ACTIVE; return true; } } return true; } public boolean switchActiveAndHeldOrWaiting() { boolean hasHeld = false; // first, are there held calls? for (int i = 0 ; i < calls.length ; i++) { CallInfo c = calls[i]; if (c != null && c.state == CallInfo.State.HOLDING) { hasHeld = true; break; } } // Now, switch for (int i = 0 ; i < calls.length ; i++) { CallInfo c = calls[i]; if (c != null) { if (c.state == CallInfo.State.ACTIVE) { c.state = CallInfo.State.HOLDING; } else if (c.state == CallInfo.State.HOLDING) { c.state = CallInfo.State.ACTIVE; } else if (!hasHeld && c.isRinging()) { c.state = CallInfo.State.ACTIVE; } } } return true; } public boolean separateCall(int index) { try { CallInfo c; c = calls[index]; if (c == null || c.isConnecting() || countActiveLines() != 1) { return false; } c.state = CallInfo.State.ACTIVE; c.isMpty = false; for (int i = 0 ; i < calls.length ; i++) { int countHeld=0, lastHeld=0; if (i != index) { CallInfo cb = calls[i]; if (cb != null && cb.state == CallInfo.State.ACTIVE) { cb.state = CallInfo.State.HOLDING; countHeld++; lastHeld = i; } } if (countHeld == 1) { // if there's only one left, clear the MPTY flag calls[lastHeld].isMpty = 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 < calls.length ; i++) { CallInfo c = calls[i]; if (c != null) { countCalls++; if (c.isConnecting()) { return false; } } } for (int i = 0 ; i < calls.length ; i++) { CallInfo c = calls[i]; if (c != null) { c.state = CallInfo.State.ACTIVE; if (countCalls > 0) { c.isMpty = 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 < calls.length ; i++) { CallInfo c = calls[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; Log.d("GSM", "SC> dial '" + address + "'"); if (nextDialFailImmediately) { nextDialFailImmediately = false; Log.d("GSM", "SC< dial fail (per request)"); return false; } String phNum = PhoneNumberUtils.extractNetworkPortion(address); if (phNum.length() == 0) { Log.d("GSM", "SC< dial fail (invalid ph num)"); return false; } // Ignore setting up GPRS if (phNum.startsWith("*99") && phNum.endsWith("#")) { Log.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) { Log.d("GSM", "SC< dial fail (invalid call state)"); return false; } } catch (InvalidStateEx ex) { Log.d("GSM", "SC< dial fail (invalid call state)"); return false; } for (int i = 0 ; i < calls.length ; i++) { if (freeSlot < 0 && calls[i] == null) { freeSlot = i; } if (calls[i] != null && !calls[i].isActiveOrHeld()) { // Can't make outgoing calls when there is a ringing or // connecting outgoing call Log.d("GSM", "SC< dial fail (invalid call state)"); return false; } else if (calls[i] != null && calls[i].state == CallInfo.State.ACTIVE) { // All active calls behome held calls[i].state = CallInfo.State.HOLDING; } } if (freeSlot < 0) { Log.d("GSM", "SC< dial fail (invalid call state)"); return false; } calls[freeSlot] = CallInfo.createOutgoingCall(phNum); if (autoProgressConnecting) { sendMessageDelayed( obtainMessage(EVENT_PROGRESS_CALL_STATE, calls[freeSlot]), CONNECTING_PAUSE_MSEC); } Log.d("GSM", "SC< dial (slot = " + freeSlot + ")"); return true; } public List<DriverCall> getDriverCalls() { ArrayList<DriverCall> ret = new ArrayList<DriverCall>(calls.length); for (int i = 0 ; i < calls.length ; i++) { CallInfo c = calls[i]; if (c != null) { DriverCall dc; dc = c.toDriverCall(i + 1); ret.add(dc); } } Log.d("GSM", "SC< getDriverCalls " + ret); return ret; } public List<String> getClccLines() { ArrayList<String> ret = new ArrayList<String>(calls.length); for (int i = 0 ; i < calls.length ; i++) { CallInfo c = calls[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 < calls.length ; i++) { CallInfo call = calls[i]; if (call != null) { if (!hasMpty && call.isMpty) { mptyIsHeld = call.state == CallInfo.State.HOLDING; } else if (call.isMpty && mptyIsHeld && call.state == CallInfo.State.ACTIVE ) { Log.e("ModelInterpreter", "Invalid state"); throw new InvalidStateEx(); } else if (!call.isMpty && hasMpty && mptyIsHeld && call.state == CallInfo.State.HOLDING ) { Log.e("ModelInterpreter", "Invalid state"); throw new InvalidStateEx(); } hasMpty |= call.isMpty; hasHeld |= call.state == CallInfo.State.HOLDING; hasActive |= call.state == 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; } }