| /* |
| * 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.telephony.Rlog; |
| import android.telephony.DisconnectCause; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.Call; |
| import com.android.internal.telephony.CallStateException; |
| import com.android.internal.telephony.Connection; |
| import com.android.internal.telephony.Phone; |
| import com.android.ims.ImsCall; |
| import com.android.ims.ImsException; |
| import com.android.ims.ImsStreamMediaProfile; |
| |
| import java.util.List; |
| |
| /** |
| * {@hide} |
| */ |
| public class ImsPhoneCall extends Call { |
| /*************************** Instance Variables **************************/ |
| |
| private static final String LOG_TAG = "ImsPhoneCall"; |
| |
| /*package*/ ImsPhoneCallTracker mOwner; |
| |
| private boolean mRingbackTonePlayed = false; |
| |
| /****************************** Constructors *****************************/ |
| /*package*/ |
| ImsPhoneCall() { |
| } |
| |
| /*package*/ |
| ImsPhoneCall(ImsPhoneCallTracker owner) { |
| mOwner = owner; |
| } |
| |
| public void dispose() { |
| try { |
| mOwner.hangup(this); |
| } catch (CallStateException ex) { |
| //Rlog.e(LOG_TAG, "dispose: unexpected error on hangup", ex); |
| //while disposing, ignore the exception and clean the connections |
| } finally { |
| for(int i = 0, s = mConnections.size(); i < s; i++) { |
| ImsPhoneConnection c = (ImsPhoneConnection) mConnections.get(i); |
| c.onDisconnect(DisconnectCause.LOST_SIGNAL); |
| } |
| } |
| } |
| |
| /************************** Overridden from Call *************************/ |
| |
| @Override |
| public List<Connection> |
| getConnections() { |
| return mConnections; |
| } |
| |
| @Override |
| public Phone |
| getPhone() { |
| return mOwner.mPhone; |
| } |
| |
| @Override |
| public boolean |
| isMultiparty() { |
| ImsCall imsCall = getImsCall(); |
| if (imsCall == null) { |
| return false; |
| } |
| |
| return imsCall.isMultiparty(); |
| } |
| |
| /** Please note: if this is the foreground call and a |
| * background call exists, the background call will be resumed. |
| */ |
| @Override |
| public void |
| hangup() throws CallStateException { |
| mOwner.hangup(this); |
| } |
| |
| @Override |
| public String |
| toString() { |
| return mState.toString(); |
| } |
| |
| //***** Called from ImsPhoneConnection |
| |
| /*package*/ void |
| attach(Connection conn) { |
| clearDisconnected(); |
| mConnections.add(conn); |
| } |
| |
| /*package*/ void |
| attach(Connection conn, State state) { |
| this.attach(conn); |
| mState = state; |
| } |
| |
| /*package*/ void |
| attachFake(Connection conn, State state) { |
| attach(conn, state); |
| } |
| |
| /** |
| * Called by ImsPhoneConnection when it has disconnected |
| */ |
| boolean |
| connectionDisconnected(ImsPhoneConnection conn) { |
| if (mState != State.DISCONNECTED) { |
| /* If only disconnected connections remain, we are disconnected*/ |
| |
| boolean hasOnlyDisconnectedConnections = true; |
| |
| for (int i = 0, s = mConnections.size() ; i < s; i ++) { |
| if (mConnections.get(i).getState() != State.DISCONNECTED) { |
| hasOnlyDisconnectedConnections = false; |
| break; |
| } |
| } |
| |
| if (hasOnlyDisconnectedConnections) { |
| mState = State.DISCONNECTED; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /*package*/ void |
| detach(ImsPhoneConnection conn) { |
| mConnections.remove(conn); |
| clearDisconnected(); |
| } |
| |
| /** |
| * @return true if there's no space in this call for additional |
| * connections to be added via "conference" |
| */ |
| /*package*/ boolean |
| isFull() { |
| return mConnections.size() == ImsPhoneCallTracker.MAX_CONNECTIONS_PER_CALL; |
| } |
| |
| //***** Called from ImsPhoneCallTracker |
| /** |
| * Called when this Call is being hung up locally (eg, user pressed "end") |
| */ |
| void |
| onHangupLocal() { |
| for (int i = 0, s = mConnections.size(); i < s; i++) { |
| ImsPhoneConnection cn = (ImsPhoneConnection)mConnections.get(i); |
| cn.onHangupLocal(); |
| } |
| mState = State.DISCONNECTING; |
| } |
| |
| /** |
| * Called when it's time to clean up disconnected Connection objects |
| */ |
| void |
| clearDisconnected() { |
| for (int i = mConnections.size() - 1 ; i >= 0 ; i--) { |
| ImsPhoneConnection cn = (ImsPhoneConnection)mConnections.get(i); |
| |
| if (cn.getState() == State.DISCONNECTED) { |
| mConnections.remove(i); |
| } |
| } |
| |
| if (mConnections.size() == 0) { |
| mState = State.IDLE; |
| } |
| } |
| |
| /*package*/ ImsPhoneConnection |
| getFirstConnection() { |
| if (mConnections.size() == 0) return null; |
| |
| return (ImsPhoneConnection) mConnections.get(0); |
| } |
| |
| /*package*/ void |
| setMute(boolean mute) { |
| ImsCall imsCall = getFirstConnection() == null ? |
| null : getFirstConnection().getImsCall(); |
| if (imsCall != null) { |
| try { |
| imsCall.setMute(mute); |
| } catch (ImsException e) { |
| Rlog.e(LOG_TAG, "setMute failed : " + e.getMessage()); |
| } |
| } |
| } |
| |
| /* package */ void |
| merge(ImsPhoneCall that, State state) { |
| // This call is the conference host and the "that" call is the one being merged in. |
| // Set the connect time for the conference; this will have been determined when the |
| // conference was initially created. |
| ImsPhoneConnection imsPhoneConnection = getFirstConnection(); |
| if (imsPhoneConnection != null) { |
| long conferenceConnectTime = imsPhoneConnection.getConferenceConnectTime(); |
| if (conferenceConnectTime > 0) { |
| imsPhoneConnection.setConnectTime(conferenceConnectTime); |
| } |
| } |
| |
| ImsPhoneConnection[] cc = that.mConnections.toArray( |
| new ImsPhoneConnection[that.mConnections.size()]); |
| for (ImsPhoneConnection c : cc) { |
| c.update(null, state); |
| } |
| } |
| |
| /** |
| * Retrieves the {@link ImsCall} for the current {@link ImsPhoneCall}. |
| * <p> |
| * Marked as {@code VisibleForTesting} so that the |
| * {@link com.android.internal.telephony.TelephonyTester} class can inject a test conference |
| * event package into a regular ongoing IMS call. |
| * |
| * @return The {@link ImsCall}. |
| */ |
| @VisibleForTesting |
| public ImsCall |
| getImsCall() { |
| return (getFirstConnection() == null) ? null : getFirstConnection().getImsCall(); |
| } |
| |
| /*package*/ static boolean isLocalTone(ImsCall imsCall) { |
| if ((imsCall == null) || (imsCall.getCallProfile() == null) |
| || (imsCall.getCallProfile().mMediaProfile == null)) { |
| return false; |
| } |
| |
| ImsStreamMediaProfile mediaProfile = imsCall.getCallProfile().mMediaProfile; |
| |
| return (mediaProfile.mAudioDirection == ImsStreamMediaProfile.DIRECTION_INACTIVE) |
| ? true : false; |
| } |
| |
| /*package*/ boolean |
| update (ImsPhoneConnection conn, ImsCall imsCall, State state) { |
| State newState = state; |
| boolean changed = false; |
| |
| //ImsCall.Listener.onCallProgressing can be invoked several times |
| //and ringback tone mode can be changed during the call setup procedure |
| if (state == State.ALERTING) { |
| if (mRingbackTonePlayed && !isLocalTone(imsCall)) { |
| mOwner.mPhone.stopRingbackTone(); |
| mRingbackTonePlayed = false; |
| } else if (!mRingbackTonePlayed && isLocalTone(imsCall)) { |
| mOwner.mPhone.startRingbackTone(); |
| mRingbackTonePlayed = true; |
| } |
| } else { |
| if (mRingbackTonePlayed) { |
| mOwner.mPhone.stopRingbackTone(); |
| mRingbackTonePlayed = false; |
| } |
| } |
| |
| if ((newState != mState) && (state != State.DISCONNECTED)) { |
| mState = newState; |
| changed = true; |
| } else if (state == State.DISCONNECTED) { |
| changed = true; |
| } |
| |
| return changed; |
| } |
| |
| /* package */ ImsPhoneConnection |
| getHandoverConnection() { |
| return (ImsPhoneConnection) getEarliestConnection(); |
| } |
| |
| void switchWith(ImsPhoneCall that) { |
| synchronized (ImsPhoneCall.class) { |
| ImsPhoneCall tmp = new ImsPhoneCall(); |
| tmp.takeOver(this); |
| this.takeOver(that); |
| that.takeOver(tmp); |
| } |
| } |
| |
| private void takeOver(ImsPhoneCall that) { |
| mConnections = that.mConnections; |
| mState = that.mState; |
| for (Connection c : mConnections) { |
| ((ImsPhoneConnection) c).changeParent(this); |
| } |
| } |
| } |