blob: 07e4f8fa96a36a8ba79a7b0bc1fce170c17fb01d [file] [log] [blame]
/*
* Copyright (C) 2013 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.imsphone;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.Registrant;
import android.os.RegistrantList;
import android.os.SystemProperties;
import android.preference.PreferenceManager;
import android.telecomm.VideoCallProfile;
import android.telephony.DisconnectCause;
import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
import android.telephony.Rlog;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.CallTracker;
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.EventLogTags;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyProperties;
import com.android.ims.ImsCall;
import com.android.ims.ImsCallProfile;
import com.android.ims.ImsConnectionStateListener;
import com.android.ims.ImsException;
import com.android.ims.ImsManager;
import com.android.ims.ImsReasonInfo;
import com.android.ims.ImsServiceClass;
import com.android.ims.ImsUtInterface;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
import java.util.ArrayList;
/**
* {@hide}
*/
public final class ImsPhoneCallTracker extends CallTracker {
static final String LOG_TAG = "ImsPhoneCallTracker";
private static final boolean DBG = true;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ImsManager.ACTION_IMS_INCOMING_CALL)) {
if (DBG) log("onReceive : incoming call intent");
if (mImsManager == null) return;
if (mServiceId < 0) return;
try {
// Network initiated USSD will be treated by mImsUssdListener
boolean isUssd = intent.getBooleanExtra(ImsManager.EXTRA_USSD, false);
if (isUssd) {
if (DBG) log("onReceive : USSD");
mUssdSession = mImsManager.takeCall(mServiceId, intent, mImsUssdListener);
if (mUssdSession != null) {
mUssdSession.accept();
}
return;
}
// Normal MT call
ImsCall imsCall = mImsManager.takeCall(mServiceId, intent, mImsCallListener);
ImsPhoneConnection conn = new ImsPhoneConnection(mPhone.getContext(), imsCall,
ImsPhoneCallTracker.this, mRingingCall);
addConnection(conn);
if ((mForegroundCall.getState() != ImsPhoneCall.State.IDLE) ||
(mBackgroundCall.getState() != ImsPhoneCall.State.IDLE)) {
conn.update(imsCall, ImsPhoneCall.State.WAITING);
}
mPhone.notifyNewRingingConnection(conn);
mPhone.notifyIncomingRing();
updatePhoneState();
mPhone.notifyPreciseCallStateChanged();
} catch (ImsException e) {
loge("onReceive : exception " + e);
}
}
}
};
//***** Constants
static final int MAX_CONNECTIONS = 7;
static final int MAX_CONNECTIONS_PER_CALL = 5;
private static final int EVENT_HANGUP_PENDINGMO = 18;
private static final int EVENT_RESUME_BACKGROUND = 19;
private static final int EVENT_DIAL_PENDINGMO = 20;
private static final int TIMEOUT_HANGUP_PENDINGMO = 500;
//***** Instance Variables
private ArrayList<ImsPhoneConnection> mConnections = new ArrayList<ImsPhoneConnection>();
private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList();
private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList();
ImsPhoneCall mRingingCall = new ImsPhoneCall(this);
ImsPhoneCall mForegroundCall = new ImsPhoneCall(this);
ImsPhoneCall mBackgroundCall = new ImsPhoneCall(this);
ImsPhoneCall mHandoverCall = new ImsPhoneCall(this);
private ImsPhoneConnection mPendingMO;
private int mClirMode = CommandsInterface.CLIR_DEFAULT;
private Object mSyncHold = new Object();
private ImsCall mUssdSession = null;
private Message mPendingUssd = null;
ImsPhone mPhone;
private boolean mDesiredMute = false; // false = mute off
private boolean mOnHoldToneStarted = false;
PhoneConstants.State mState = PhoneConstants.State.IDLE;
private ImsManager mImsManager;
private int mServiceId = -1;
private Call.SrvccState mSrvccState = Call.SrvccState.NONE;
//***** Events
//***** Constructors
ImsPhoneCallTracker(ImsPhone phone) {
this.mPhone = phone;
IntentFilter intentfilter = new IntentFilter();
intentfilter.addAction(ImsManager.ACTION_IMS_INCOMING_CALL);
mPhone.getContext().registerReceiver(mReceiver, intentfilter);
Thread t = new Thread() {
public void run() {
getImsService();
}
};
t.start();
}
private PendingIntent createIncomingCallPendingIntent() {
Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL);
return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
private void getImsService() {
if (DBG) log("getImsService");
mImsManager = ImsManager.getInstance(mPhone.getContext(), mPhone.getSubId());
try {
mServiceId = mImsManager.open(ImsServiceClass.MMTEL,
createIncomingCallPendingIntent(),
mImsConnectionStateListener);
} catch (ImsException e) {
loge("getImsService: " + e);
//Leave mImsManager as null, then CallStateException will be thrown when dialing
mImsManager = null;
}
}
public void dispose() {
if (DBG) log("dispose");
mRingingCall.dispose();
mBackgroundCall.dispose();
mForegroundCall.dispose();
mHandoverCall.dispose();
clearDisconnected();
mPhone.getContext().unregisterReceiver(mReceiver);
}
@Override
protected void finalize() {
log("ImsPhoneCallTracker finalized");
}
//***** Instance Methods
//***** Public Methods
@Override
public void registerForVoiceCallStarted(Handler h, int what, Object obj) {
Registrant r = new Registrant(h, what, obj);
mVoiceCallStartedRegistrants.add(r);
}
@Override
public void unregisterForVoiceCallStarted(Handler h) {
mVoiceCallStartedRegistrants.remove(h);
}
@Override
public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
Registrant r = new Registrant(h, what, obj);
mVoiceCallEndedRegistrants.add(r);
}
@Override
public void unregisterForVoiceCallEnded(Handler h) {
mVoiceCallEndedRegistrants.remove(h);
}
Connection
dial(String dialString, int videoState) throws CallStateException {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
int oirMode = sp.getInt(PhoneBase.CLIR_KEY, CommandsInterface.CLIR_DEFAULT);
return dial(dialString, oirMode, videoState);
}
/**
* oirMode is one of the CLIR_ constants
*/
synchronized Connection
dial(String dialString, int clirMode, int videoState) throws CallStateException {
if (DBG) log("dial clirMode=" + clirMode);
// note that this triggers call state changed notif
clearDisconnected();
if (mImsManager == null) {
throw new CallStateException("service not available");
}
if (!canDial()) {
throw new CallStateException("cannot dial in current state");
}
boolean holdBeforeDial = false;
// The new call must be assigned to the foreground call.
// That call must be idle, so place anything that's
// there on hold
if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
//we should have failed in !canDial() above before we get here
throw new CallStateException("cannot dial in current state");
}
// foreground call is empty for the newly dialed connection
holdBeforeDial = true;
switchWaitingOrHoldingAndActive();
}
ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;
mClirMode = clirMode;
synchronized (mSyncHold) {
if (holdBeforeDial) {
fgState = mForegroundCall.getState();
bgState = mBackgroundCall.getState();
//holding foreground call failed
if (fgState == ImsPhoneCall.State.ACTIVE) {
throw new CallStateException("cannot dial in current state");
}
//holding foreground call succeeded
if (bgState == ImsPhoneCall.State.HOLDING) {
holdBeforeDial = false;
}
}
mPendingMO = new ImsPhoneConnection(mPhone.getContext(),
checkForTestEmergencyNumber(dialString), this, mForegroundCall);
}
addConnection(mPendingMO);
if (!holdBeforeDial) {
dialInternal(mPendingMO, clirMode, videoState);
}
updatePhoneState();
mPhone.notifyPreciseCallStateChanged();
return mPendingMO;
}
private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState) {
if (conn == null) {
return;
}
if (conn.getAddress()== null || conn.getAddress().length() == 0
|| conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
// Phone number is invalid
conn.setDisconnectCause(DisconnectCause.INVALID_NUMBER);
sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
return;
}
// Always unmute when initiating a new call
setMute(false);
int serviceType = PhoneNumberUtils.isEmergencyNumber(conn.getAddress()) ?
ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL;
int callType = getCallType(videoState);
try {
String[] callees = new String[] { conn.getAddress() };
ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
serviceType, callType);
profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
ImsCall imsCall = mImsManager.makeCall(mServiceId, profile,
callees, mImsCallListener);
conn.setImsCall(imsCall);
} catch (ImsException e) {
loge("dialInternal : " + e);
conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
}
}
/**
* Converts from the video state values defined in {@link VideoCallProfile} to the call types
* defined in {@link ImsCallProfile}.
*
* @param videoState The video state.
* @return The call type.
*/
private int getCallType(int videoState) {
boolean videoTx = isVideoStateSet(videoState, VideoCallProfile.VIDEO_STATE_TX_ENABLED);
boolean videoRx = isVideoStateSet(videoState, VideoCallProfile.VIDEO_STATE_RX_ENABLED);
boolean isPaused = isVideoStateSet(videoState, VideoCallProfile.VIDEO_STATE_PAUSED);
if (isPaused) {
return ImsCallProfile.CALL_TYPE_VT_NODIR;
} else if (videoTx && !videoRx) {
return ImsCallProfile.CALL_TYPE_VT_TX;
} else if (!videoTx && videoRx) {
return ImsCallProfile.CALL_TYPE_VT_RX;
} else if (videoTx && videoRx) {
return ImsCallProfile.CALL_TYPE_VT;
}
return ImsCallProfile.CALL_TYPE_VOICE;
}
/**
* Determines if a video state is set in a video state bit-mask.
*
* @param videoState The video state bit mask.
* @param videoStateToCheck The particular video state to check.
* @return True if the video state is set in the bit-mask.
*/
private boolean isVideoStateSet(int videoState, int videoStateToCheck) {
return (videoState & videoStateToCheck) == videoStateToCheck;
}
void
acceptCall () throws CallStateException {
if (DBG) log("acceptCall");
if (mForegroundCall.getState().isAlive()
&& mBackgroundCall.getState().isAlive()) {
throw new CallStateException("cannot accept call");
}
if ((mRingingCall.getState() == ImsPhoneCall.State.WAITING)
&& mForegroundCall.getState().isAlive()) {
setMute(false);
switchWaitingOrHoldingAndActive();
} else if (mRingingCall.getState().isRinging()) {
if (DBG) log("acceptCall: incoming...");
// Always unmute when answering a new call
setMute(false);
try {
ImsCall imsCall = mRingingCall.getImsCall();
if (imsCall != null) {
imsCall.accept();
} else {
throw new CallStateException("no valid ims call");
}
} catch (ImsException e) {
throw new CallStateException("cannot accept call");
}
} else {
throw new CallStateException("phone not ringing");
}
}
void
rejectCall () throws CallStateException {
if (DBG) log("rejectCall");
if (mRingingCall.getState().isRinging()) {
hangup(mRingingCall);
} else {
throw new CallStateException("phone not ringing");
}
}
void
switchWaitingOrHoldingAndActive() throws CallStateException {
if (DBG) log("switchWaitingOrHoldingAndActive");
if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) {
throw new CallStateException("cannot be in the incoming state");
}
if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
ImsCall imsCall = mForegroundCall.getImsCall();
if (imsCall == null) {
throw new CallStateException("no ims call");
}
mForegroundCall.switchWith(mBackgroundCall);
try {
imsCall.hold();
} catch (ImsException e) {
mForegroundCall.switchWith(mBackgroundCall);
throw new CallStateException(e.getMessage());
}
} else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
resumeWaitingOrHolding();
}
}
void
conference() {
if (DBG) log("conference");
ImsCall fgImsCall = mForegroundCall.getImsCall();
if (fgImsCall == null) {
log("conference no foreground ims call");
return;
}
ImsCall bgImsCall = mBackgroundCall.getImsCall();
if (bgImsCall == null) {
log("conference no background ims call");
return;
}
try {
fgImsCall.merge(bgImsCall);
} catch (ImsException e) {
log("conference " + e.getMessage());
}
}
void
explicitCallTransfer() {
//TODO : implement
}
void
clearDisconnected() {
if (DBG) log("clearDisconnected");
internalClearDisconnected();
updatePhoneState();
mPhone.notifyPreciseCallStateChanged();
}
boolean
canConference() {
return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
&& mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING
&& !mBackgroundCall.isFull()
&& !mForegroundCall.isFull();
}
boolean
canDial() {
boolean ret;
int serviceState = mPhone.getServiceState().getState();
String disableCall = SystemProperties.get(
TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
ret = (serviceState != ServiceState.STATE_POWER_OFF)
&& mPendingMO == null
&& !mRingingCall.isRinging()
&& !disableCall.equals("true")
&& (!mForegroundCall.getState().isAlive()
|| !mBackgroundCall.getState().isAlive());
return ret;
}
boolean
canTransfer() {
return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
&& mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING;
}
//***** Private Instance Methods
private void
internalClearDisconnected() {
mRingingCall.clearDisconnected();
mForegroundCall.clearDisconnected();
mBackgroundCall.clearDisconnected();
mHandoverCall.clearDisconnected();
}
private void
updatePhoneState() {
PhoneConstants.State oldState = mState;
if (mRingingCall.isRinging()) {
mState = PhoneConstants.State.RINGING;
} else if (mPendingMO != null ||
!(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
mState = PhoneConstants.State.OFFHOOK;
} else {
mState = PhoneConstants.State.IDLE;
}
if (mState == PhoneConstants.State.IDLE && oldState != mState) {
mVoiceCallEndedRegistrants.notifyRegistrants(
new AsyncResult(null, null, null));
} else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
mVoiceCallStartedRegistrants.notifyRegistrants (
new AsyncResult(null, null, null));
}
if (DBG) log("updatePhoneState oldState=" + oldState + ", newState=" + mState);
if (mState != oldState) {
mPhone.notifyPhoneStateChanged();
}
}
private void
handleRadioNotAvailable() {
// handlePollCalls will clear out its
// call list when it gets the CommandException
// error result from this
pollCallsWhenSafe();
}
private void
dumpState() {
List l;
log("Phone State:" + mState);
log("Ringing call: " + mRingingCall.toString());
l = mRingingCall.getConnections();
for (int i = 0, s = l.size(); i < s; i++) {
log(l.get(i).toString());
}
log("Foreground call: " + mForegroundCall.toString());
l = mForegroundCall.getConnections();
for (int i = 0, s = l.size(); i < s; i++) {
log(l.get(i).toString());
}
log("Background call: " + mBackgroundCall.toString());
l = mBackgroundCall.getConnections();
for (int i = 0, s = l.size(); i < s; i++) {
log(l.get(i).toString());
}
}
//***** Called from ImsPhone
/*package*/ void
setMute(boolean mute) {
mDesiredMute = mute;
mForegroundCall.setMute(mute);
}
/*package*/ boolean
getMute() {
return mDesiredMute;
}
/*package*/ void
sendDtmf(char c) {
if (DBG) log("sendDtmf");
ImsCall imscall = mForegroundCall.getImsCall();
if (imscall != null) {
imscall.sendDtmf(convertDtmf(c));
}
}
private int convertDtmf(char c) {
int code = c - '0';
if ((code < 0) || (code > 9)) {
switch (c) {
case '*': return 10;
case '#': return 11;
case 'A': return 12;
case 'B': return 13;
case 'C': return 14;
case 'D': return 15;
default:
throw new IllegalArgumentException(
"invalid DTMF char: " + (int) c);
}
}
return code;
}
//***** Called from ImsPhoneConnection
/*package*/ void
hangup (ImsPhoneConnection conn) throws CallStateException {
if (DBG) log("hangup connection");
if (conn.getOwner() != this) {
throw new CallStateException ("ImsPhoneConnection " + conn
+ "does not belong to ImsPhoneCallTracker " + this);
}
hangup(conn.getCall());
}
//***** Called from ImsPhoneCall
/* package */ void
hangup (ImsPhoneCall call) throws CallStateException {
if (DBG) log("hangup call");
if (call.getConnections().size() == 0) {
throw new CallStateException("no connections");
}
ImsCall imsCall = call.getImsCall();
boolean rejectCall = false;
if (call == mRingingCall) {
if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming");
rejectCall = true;
} else if (call == mForegroundCall) {
if (call.isDialingOrAlerting()) {
if (Phone.DEBUG_PHONE) {
log("(foregnd) hangup dialing or alerting...");
}
} else {
if (Phone.DEBUG_PHONE) {
log("(foregnd) hangup foreground");
}
//held call will be resumed by onCallTerminated
}
} else if (call == mBackgroundCall) {
if (Phone.DEBUG_PHONE) {
log("(backgnd) hangup waiting or background");
}
} else {
throw new RuntimeException ("ImsPhoneCall " + call +
"does not belong to ImsPhoneCallTracker " + this);
}
call.onHangupLocal();
try {
if (imsCall != null) {
if (rejectCall) imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
else imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
} else if (mPendingMO != null && call == mForegroundCall) {
// is holding a foreground call
mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED);
mPendingMO.onDisconnect();
removeConnection(mPendingMO);
mPendingMO = null;
updatePhoneState();
removeMessages(EVENT_DIAL_PENDINGMO);
}
} catch (ImsException e) {
throw new CallStateException(e.getMessage());
}
mPhone.notifyPreciseCallStateChanged();
}
/* package */
void resumeWaitingOrHolding() throws CallStateException {
if (DBG) log("resumeWaitingOrHolding");
try {
if (mForegroundCall.getState().isAlive()) {
//resume foreground call after holding background call
//they were switched before holding
ImsCall imsCall = mForegroundCall.getImsCall();
if (imsCall != null) imsCall.resume();
} else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
//accept waiting call after holding background call
ImsCall imsCall = mRingingCall.getImsCall();
if (imsCall != null) imsCall.accept();
} else {
//Just resume background call.
//To distinguish resuming call with swapping calls
//we do not switch calls.here
//ImsPhoneConnection.update will chnage the parent when completed
ImsCall imsCall = mBackgroundCall.getImsCall();
if (imsCall != null) imsCall.resume();
}
} catch (ImsException e) {
throw new CallStateException(e.getMessage());
}
}
/* package */
void sendUSSD (String ussdString, Message response) {
if (DBG) log("sendUSSD");
try {
if (mUssdSession != null) {
mUssdSession.sendUssd(ussdString);
AsyncResult.forMessage(response, null, null);
response.sendToTarget();
return;
}
String[] callees = new String[] { ussdString };
ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE);
profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING,
ImsCallProfile.DIALSTRING_USSD);
mUssdSession = mImsManager.makeCall(mServiceId, profile,
callees, mImsUssdListener);
} catch (ImsException e) {
loge("sendUSSD : " + e);
mPhone.sendErrorResponse(response, e);
}
}
/* package */
void cancelUSSD() {
if (mUssdSession == null) return;
try {
mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
} catch (ImsException e) {
}
}
private synchronized ImsPhoneConnection findConnection(ImsCall imsCall) {
for (ImsPhoneConnection conn : mConnections) {
if (conn.getImsCall() == imsCall) {
return conn;
}
}
return null;
}
private synchronized void removeConnection(ImsPhoneConnection conn) {
mConnections.remove(conn);
}
private synchronized void addConnection(ImsPhoneConnection conn) {
mConnections.add(conn);
}
private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
if (DBG) log("processCallStateChange state=" + state + " cause=" + cause);
if (imsCall == null) return;
boolean changed = false;
ImsPhoneConnection conn = findConnection(imsCall);
if (conn == null) {
// TODO : what should be done?
return;
}
changed = conn.update(imsCall, state);
if (state == ImsPhoneCall.State.DISCONNECTED) {
changed = conn.onDisconnect(cause) || changed;
removeConnection(conn);
}
if (changed) {
if (conn.getCall() == mHandoverCall) return;
updatePhoneState();
mPhone.notifyPreciseCallStateChanged();
}
}
private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
int cause = DisconnectCause.ERROR_UNSPECIFIED;
//int type = reasonInfo.getReasonType();
int code = reasonInfo.getCode();
switch (code) {
case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
return DisconnectCause.NUMBER_UNREACHABLE;
case ImsReasonInfo.CODE_SIP_BUSY:
return DisconnectCause.BUSY;
case ImsReasonInfo.CODE_USER_TERMINATED:
return DisconnectCause.LOCAL;
case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
return DisconnectCause.NORMAL;
case ImsReasonInfo.CODE_SIP_REDIRECTED:
case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
case ImsReasonInfo.CODE_SIP_FORBIDDEN:
case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
case ImsReasonInfo.CODE_SIP_USER_REJECTED:
case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
return DisconnectCause.SERVER_ERROR;
case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
case ImsReasonInfo.CODE_SIP_NOT_FOUND:
case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
return DisconnectCause.SERVER_UNREACHABLE;
case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE:
case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED:
case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE:
case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE:
case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING:
return DisconnectCause.OUT_OF_SERVICE;
case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT:
case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING:
case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER:
case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
return DisconnectCause.TIMED_OUT;
case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
return DisconnectCause.POWER_OFF;
default:
}
return cause;
}
/**
* Listen to the IMS call state change
*/
private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
@Override
public void onCallProgressing(ImsCall imsCall) {
if (DBG) log("onCallProgressing");
mPendingMO = null;
processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING,
DisconnectCause.NOT_DISCONNECTED);
}
@Override
public void onCallStarted(ImsCall imsCall) {
if (DBG) log("onCallStarted");
mPendingMO = null;
processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
DisconnectCause.NOT_DISCONNECTED);
}
/**
* onCallStartFailed will be invoked when:
* case 1) Dialing fails
* case 2) Ringing call is disconnected by local or remote user
*/
@Override
public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
if (mPendingMO != null) {
// To initiate dialing circuit-switched call
if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
&& mBackgroundCall.getState() == ImsPhoneCall.State.IDLE
&& mRingingCall.getState() == ImsPhoneCall.State.IDLE) {
mForegroundCall.detach(mPendingMO);
removeConnection(mPendingMO);
mPendingMO.finalize();
mPendingMO = null;
mPhone.initiateSilentRedial();
return;
}
mPendingMO = null;
}
onCallTerminated(imsCall, reasonInfo);
}
@Override
public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
ImsPhoneCall.State oldState = mForegroundCall.getState();
processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED,
getDisconnectCauseFromReasonInfo(reasonInfo));
if (reasonInfo.getCode() == ImsReasonInfo.CODE_USER_TERMINATED) {
if ((oldState == ImsPhoneCall.State.DISCONNECTING)
&& (mForegroundCall.getState() == ImsPhoneCall.State.DISCONNECTED)
&& (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING)) {
sendEmptyMessage(EVENT_RESUME_BACKGROUND);
}
}
}
@Override
public void onCallHeld(ImsCall imsCall) {
if (DBG) log("onCallHeld");
synchronized (mSyncHold) {
ImsPhoneCall.State oldState = mBackgroundCall.getState();
processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING,
DisconnectCause.NOT_DISCONNECTED);
if (oldState == ImsPhoneCall.State.ACTIVE) {
if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
|| (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
sendEmptyMessage(EVENT_RESUME_BACKGROUND);
} else {
//when multiple connections belong to background call,
//only the first callback reaches here
//otherwise the oldState is already HOLDING
if (mPendingMO != null) {
sendEmptyMessage(EVENT_DIAL_PENDINGMO);
}
}
}
}
}
@Override
public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode());
synchronized (mSyncHold) {
ImsPhoneCall.State bgState = mBackgroundCall.getState();
if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) {
// disconnected while processing hold
if (mPendingMO != null) {
sendEmptyMessage(EVENT_DIAL_PENDINGMO);
}
} else if (bgState == ImsPhoneCall.State.ACTIVE) {
mForegroundCall.switchWith(mBackgroundCall);
if (mPendingMO != null) {
mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
}
}
}
}
@Override
public void onCallResumed(ImsCall imsCall) {
if (DBG) log("onCallResumed");
processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
DisconnectCause.NOT_DISCONNECTED);
}
@Override
public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
// TODO : What should be done?
}
@Override
public void onCallResumeReceived(ImsCall imsCall) {
if (DBG) log("onCallResumeReceived");
if (mOnHoldToneStarted) {
mPhone.stopOnHoldTone();
mOnHoldToneStarted = false;
}
}
@Override
public void onCallHoldReceived(ImsCall imsCall) {
if (DBG) log("onCallHoldReceived");
ImsPhoneConnection conn = findConnection(imsCall);
if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) {
if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) {
mPhone.startOnHoldTone();
mOnHoldToneStarted = true;
}
}
}
@Override
public void onCallMerged(ImsCall call, ImsCall newCall) {
if (DBG) log("onCallMerged");
mForegroundCall.merge(mBackgroundCall, mForegroundCall.getState());
updatePhoneState();
mPhone.notifyPreciseCallStateChanged();
}
@Override
public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
if (DBG) log("onCallMergeFailed reasonCode=" + reasonInfo.getCode());
mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
}
};
/**
* Listen to the IMS call state change
*/
private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
@Override
public void onCallStarted(ImsCall imsCall) {
if (DBG) log("mImsUssdListener onCallStarted");
if (imsCall == mUssdSession) {
if (mPendingUssd != null) {
AsyncResult.forMessage(mPendingUssd);
mPendingUssd.sendToTarget();
mPendingUssd = null;
}
}
}
@Override
public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
onCallTerminated(imsCall, reasonInfo);
}
@Override
public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
if (imsCall == mUssdSession) {
mUssdSession = null;
if (mPendingUssd != null) {
CommandException ex =
new CommandException(CommandException.Error.GENERIC_FAILURE);
AsyncResult.forMessage(mPendingUssd, null, ex);
mPendingUssd.sendToTarget();
mPendingUssd = null;
}
}
imsCall.close();
}
@Override
public void onCallUssdMessageReceived(ImsCall call,
int mode, String ussdMessage) {
if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
int ussdMode = -1;
switch(mode) {
case ImsCall.USSD_MODE_REQUEST:
ussdMode = CommandsInterface.USSD_MODE_REQUEST;
break;
case ImsCall.USSD_MODE_NOTIFY:
ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
break;
}
mPhone.onIncomingUSSD(ussdMode, ussdMessage);
}
};
/**
* Listen to the IMS service state change
*
*/
private ImsConnectionStateListener mImsConnectionStateListener =
new ImsConnectionStateListener() {
@Override
public void onImsConnected() {
if (DBG) log("onImsConnected");
mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
}
@Override
public void onImsDisconnected() {
if (DBG) log("onImsDisconnected");
mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
}
@Override
public void onImsResumed() {
if (DBG) log("onImsResumed");
mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
}
@Override
public void onImsSuspended() {
if (DBG) log("onImsSuspended");
mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
}
};
/* package */
ImsUtInterface getUtInterface() throws ImsException {
if (mImsManager == null) {
throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
}
ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId);
return ut;
}
/* package */
void notifySrvccState(Call.SrvccState state) {
if (DBG) log("notifySrvccState state=" + state);
mSrvccState = state;
if (mSrvccState == Call.SrvccState.COMPLETED) {
if (mForegroundCall.getConnections().size() > 0) {
mHandoverCall.switchWith(mForegroundCall);
} else if (mBackgroundCall.getConnections().size() > 0) {
mHandoverCall.switchWith(mBackgroundCall);
}
}
}
//****** Overridden from Handler
@Override
public void
handleMessage (Message msg) {
AsyncResult ar;
if (DBG) log("handleMessage what=" + msg.what);
switch (msg.what) {
case EVENT_HANGUP_PENDINGMO:
if (mPendingMO != null) {
mPendingMO.onDisconnect();
removeConnection(mPendingMO);
mPendingMO = null;
}
updatePhoneState();
mPhone.notifyPreciseCallStateChanged();
break;
case EVENT_RESUME_BACKGROUND:
try {
resumeWaitingOrHolding();
} catch (CallStateException e) {
if (Phone.DEBUG_PHONE) {
loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
}
}
break;
case EVENT_DIAL_PENDINGMO:
dialInternal(mPendingMO, mClirMode, VideoCallProfile.VIDEO_STATE_AUDIO_ONLY);
break;
}
}
@Override
protected void log(String msg) {
Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
}
protected void loge(String msg) {
Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("ImsPhoneCallTracker extends:");
super.dump(fd, pw, args);
pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
pw.println(" mRingingCall=" + mRingingCall);
pw.println(" mForegroundCall=" + mForegroundCall);
pw.println(" mBackgroundCall=" + mBackgroundCall);
pw.println(" mHandoverCall=" + mHandoverCall);
pw.println(" mPendingMO=" + mPendingMO);
//pw.println(" mHangupPendingMO=" + mHangupPendingMO);
pw.println(" mPhone=" + mPhone);
pw.println(" mDesiredMute=" + mDesiredMute);
pw.println(" mState=" + mState);
}
@Override
protected void handlePollCalls(AsyncResult ar) {
}
}