blob: 554eb91fe2f2cbe3a8d290dba46478737db39890 [file] [log] [blame]
/*
* Copyright (C) 2010 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.sip;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.net.sip.SipAudioCall;
import android.net.sip.SipManager;
import android.net.sip.SipProfile;
import android.net.sip.SipSessionState;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Registrant;
import android.os.RegistrantList;
import android.os.SystemProperties;
import android.preference.PreferenceManager;
import android.provider.Telephony;
import android.telephony.CellLocation;
import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.text.TextUtils;
import android.util.Log;
import static com.android.internal.telephony.CommandsInterface.CF_ACTION_DISABLE;
import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ERASURE;
import static com.android.internal.telephony.CommandsInterface.CF_ACTION_REGISTRATION;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL_CONDITIONAL;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_NO_REPLY;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_NOT_REACHABLE;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_BUSY;
import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL;
import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
import static com.android.internal.telephony.TelephonyProperties.PROPERTY_BASEBAND_VERSION;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.DataConnection;
import com.android.internal.telephony.IccCard;
import com.android.internal.telephony.IccFileHandler;
import com.android.internal.telephony.IccPhoneBookInterfaceManager;
import com.android.internal.telephony.IccSmsInterfaceManager;
import com.android.internal.telephony.MmiCode;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.PhoneNotifier;
import com.android.internal.telephony.PhoneProxy;
import com.android.internal.telephony.PhoneSubInfo;
import com.android.internal.telephony.TelephonyProperties;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import javax.sip.SipException;
/**
* {@hide}
*/
public class SipPhone extends SipPhoneBase {
private static final String LOG_TAG = "SipPhone";
private static final boolean LOCAL_DEBUG = true;
private List<SipCall.SipConnection> connections =
new ArrayList<SipCall.SipConnection>();
// A call that is ringing or (call) waiting
private SipCall ringingCall = new SipCall();
private SipCall foregroundCall = new SipCall();
private SipCall backgroundCall = new SipCall();
private SipManager mSipManager;
private SipProfile mProfile;
SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) {
super(context, notifier);
Log.v(LOG_TAG, " +++++++++++++++++++++ new SipPhone: " + profile.getUriString());
ringingCall = new SipCall();
foregroundCall = new SipCall();
backgroundCall = new SipCall();
mProfile = profile;
mSipManager = SipManager.getInstance(context);
// FIXME: what's this for SIP?
//Change the system property
//SystemProperties.set(TelephonyProperties.CURRENT_ACTIVE_PHONE,
// new Integer(Phone.PHONE_TYPE_GSM).toString());
}
public ServiceState getServiceState() {
// FIXME: we may need to provide this when data connectivity is lost
// or when server is down
return super.getServiceState();
}
public String getPhoneName() {
// FIXME: profile's name?
return "SIP";
}
public void acceptCall() throws CallStateException {
//mCT.acceptCall();
}
public void rejectCall() throws CallStateException {
//mCT.rejectCall();
}
public void switchHoldingAndActive() throws CallStateException {
// TODO
}
public boolean canConference() {
//TODO
//return mCT.canConference();
return false;
}
public void conference() throws CallStateException {
// TODO
}
public void clearDisconnected() {
ringingCall.clearDisconnected();
foregroundCall.clearDisconnected();
backgroundCall.clearDisconnected();
updatePhoneState();
notifyPreciseCallStateChanged();
}
public boolean canTransfer() {
// TODO
//return mCT.canTransfer();
return false;
}
public void explicitCallTransfer() throws CallStateException {
//mCT.explicitCallTransfer();
}
public Connection dial(String dialString) throws CallStateException {
// TODO: parse SIP URL?
// Need to make sure dialString gets parsed properly
//String newDialString = PhoneNumberUtils.stripSeparators(dialString);
//return mCT.dial(newDialString);
clearDisconnected();
if (!canDial()) {
throw new CallStateException("cannot dial in current state");
}
if (foregroundCall.getState() == SipCall.State.ACTIVE) {
switchHoldingAndActive();
}
if (foregroundCall.getState() != SipCall.State.IDLE) {
//we should have failed in !canDial() above before we get here
throw new CallStateException("cannot dial in current state");
}
setMute(false);
//cm.dial(pendingMO.address, clirMode, obtainCompleteMessage());
try {
Connection c = foregroundCall.dial(dialString);
updatePhoneState();
notifyPreciseCallStateChanged();
return c;
} catch (SipException e) {
throw new CallStateException("dial error: " + e);
}
}
public void sendDtmf(char c) {
if (!PhoneNumberUtils.is12Key(c)) {
Log.e(LOG_TAG,
"sendDtmf called with invalid character '" + c + "'");
} else {
if (getState() == State.OFFHOOK) {
// FIXME: use mCT instead of mCM
//mCM.sendDtmf(c, null);
}
}
}
public void startDtmf(char c) {
if (!PhoneNumberUtils.is12Key(c)) {
Log.e(LOG_TAG,
"startDtmf called with invalid character '" + c + "'");
} else {
// FIXME: use mCT instead of mCM
//mCM.startDtmf(c, null);
}
}
public void stopDtmf() {
// FIXME: use mCT instead of mCM
//mCM.stopDtmf(null);
}
public void sendBurstDtmf(String dtmfString) {
Log.e(LOG_TAG, "[SipPhone] sendBurstDtmf() is a CDMA method");
}
public void getOutgoingCallerIdDisplay(Message onComplete) {
// FIXME: what to reply?
AsyncResult.forMessage(onComplete, null, null);
onComplete.sendToTarget();
}
public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
Message onComplete) {
// FIXME: what's this for SIP?
AsyncResult.forMessage(onComplete, null, null);
onComplete.sendToTarget();
}
public void getCallWaiting(Message onComplete) {
// FIXME: what to reply?
AsyncResult.forMessage(onComplete, null, null);
onComplete.sendToTarget();
}
public void setCallWaiting(boolean enable, Message onComplete) {
// FIXME: what to reply?
Log.e(LOG_TAG, "call waiting not supported");
}
public void setMute(boolean muted) {
// TODO
//mCT.setMute(muted);
}
public boolean getMute() {
// TODO
//return mCT.getMute();
return false;
}
public Call getForegroundCall() {
return foregroundCall;
}
public Call getBackgroundCall() {
return backgroundCall;
}
public Call getRingingCall() {
return ringingCall;
}
private class SipCall extends SipCallBase {
public Phone getPhone() {
return SipPhone.this;
}
public Connection dial(String calleeSipUri) throws SipException {
try {
SipProfile callee =
new SipProfile.Builder(calleeSipUri).build();
SipConnection c = new SipConnection(callee);
c.dial();
connections.add(c);
state = Call.State.DIALING;
return c;
} catch (ParseException e) {
throw new SipException("dial", e);
}
}
// TODO: if this is the foreground call and a background call exists,
// resume the background call
public void hangup() throws CallStateException {
Log.v(LOG_TAG, "hang up call: " + getState() + ": " + this
+ " on phone " + getPhone());
CallStateException excp = null;
for (Connection c : connections) {
try {
c.hangup();
} catch (CallStateException e) {
excp = e;
}
}
if (excp != null) throw excp;
}
private void onConnectionStateChanged(SipConnection conn) {
// this can be called back when a conf call is formed
// TODO: how to synchronize this?
// TODO: who to notify?
state = conn.getState();
Log.v(LOG_TAG, "++******++ call state changed: " + getState() + ": "
+ this + ": on phone " + getPhone() + " "
+ connections.size());
notifyPreciseCallStateChangedP();
}
private void onConnectionEnded(SipConnection conn) {
// this can be called back when a conf call is formed
// TODO: how to synchronize this?
// TODO: who to notify?
state = conn.getState();
Log.v(LOG_TAG, "-------- call ended: " + getState() + ": " + this
+ ": on phone " + getPhone() + " " + connections.size());
notifyDisconnectP(conn);
}
class SipConnection extends SipConnectionBase {
private SipCall mOwner = SipCall.this;
// could be different in a conf call
private SipAudioCall mSipAudioCall;
private Call.State mState = Call.State.IDLE;
private SipProfile mPeer;
private SipAudioCallAdapter mAdapter = new SipAudioCallAdapter() {
protected void onCallEnded() {
mState = Call.State.DISCONNECTED;
mOwner.onConnectionEnded(SipConnection.this);
Log.v(LOG_TAG, "-------- connection ended: " + mPeer.getUriString() + ": " + mSipAudioCall.getState() + ": on phone " + getPhone());
}
public void onChanged(SipAudioCall call) {
mState = getCallStateFrom(call);
mOwner.onConnectionStateChanged(SipConnection.this);
Log.v(LOG_TAG, "++******++ connection state changed: " + mPeer.getUriString() + ": " + call.getState() + ": on phone " + getPhone());
}
};
public SipConnection(SipProfile callee) {
super(callee.getSipDomain(), callee.getUriString());
mPeer = callee;
}
public Call.State getState() {
return mState;
}
public void dial() throws SipException {
mState = Call.State.DIALING;
mSipAudioCall = mSipManager.makeAudioCall(mContext, mProfile,
mPeer, mAdapter);
}
public SipCall getCall() {
return mOwner;
}
protected Phone getPhone() {
return mOwner.getPhone();
}
public void hangup() throws CallStateException {
Log.v(LOG_TAG, "hangup conn: " + mPeer.getUriString() + ": "
+ ": on phone " + getPhone());
try {
mSipAudioCall.endCall();
} catch (SipException e) {
throw new CallStateException("hangup(): " + e);
}
}
public void separate() throws CallStateException {
// TODO: what's this for SIP?
/*
if (!disconnected) {
owner.separate(this);
} else {
throw new CallStateException ("disconnected");
}
*/
}
}
}
private static Call.State getCallStateFrom(SipAudioCall sipAudioCall) {
SipSessionState sessionState = sipAudioCall.getState();
switch (sessionState) {
case READY_TO_CALL: return Call.State.IDLE;
case INCOMING_CALL:
case INCOMING_CALL_ANSWERING: return Call.State.INCOMING;
case OUTGOING_CALL: return Call.State.DIALING;
case OUTGOING_CALL_RING_BACK: return Call.State.ALERTING;
case OUTGOING_CALL_CANCELING: return Call.State.DISCONNECTING;
case IN_CALL: return Call.State.ACTIVE;
default:
Log.w(LOG_TAG, "illegal connection state: " + sessionState);
return Call.State.DISCONNECTED;
}
}
private abstract class SipAudioCallAdapter extends SipAudioCall.Adapter {
private SipException mError;
protected abstract void onCallEnded();
public void onCallEnded(SipAudioCall call) {
onCallEnded();
}
public void onError(SipAudioCall call, String errorMessage) {
mError = new SipException(errorMessage);
onCallEnded();
}
public SipException getError() {
return mError;
}
}
}