| /* |
| * 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.ims; |
| |
| import com.android.internal.R; |
| |
| import java.util.Iterator; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| import android.content.Context; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Message; |
| import android.telecom.ConferenceParticipant; |
| import android.telephony.Rlog; |
| |
| import com.android.ims.internal.CallGroup; |
| import com.android.ims.internal.CallGroupManager; |
| import com.android.ims.internal.ICall; |
| import com.android.ims.internal.ImsCallSession; |
| import com.android.ims.internal.ImsStreamMediaSession; |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| /** |
| * Handles an IMS voice / video call over LTE. You can instantiate this class with |
| * {@link ImsManager}. |
| * |
| * @hide |
| */ |
| public class ImsCall implements ICall { |
| public static final int CALL_STATE_ACTIVE_TO_HOLD = 1; |
| public static final int CALL_STATE_HOLD_TO_ACTIVE = 2; |
| |
| // Mode of USSD message |
| public static final int USSD_MODE_NOTIFY = 0; |
| public static final int USSD_MODE_REQUEST = 1; |
| |
| private static final String TAG = "ImsCall"; |
| private static final boolean DBG = true; |
| |
| /** |
| * Listener for events relating to an IMS call, such as when a call is being |
| * recieved ("on ringing") or a call is outgoing ("on calling"). |
| * <p>Many of these events are also received by {@link ImsCallSession.Listener}.</p> |
| */ |
| public static class Listener { |
| /** |
| * Called when a request is sent out to initiate a new call |
| * and 1xx response is received from the network. |
| * The default implementation calls {@link #onCallStateChanged}. |
| * |
| * @param call the call object that carries out the IMS call |
| */ |
| public void onCallProgressing(ImsCall call) { |
| onCallStateChanged(call); |
| } |
| |
| /** |
| * Called when the call is established. |
| * The default implementation calls {@link #onCallStateChanged}. |
| * |
| * @param call the call object that carries out the IMS call |
| */ |
| public void onCallStarted(ImsCall call) { |
| onCallStateChanged(call); |
| } |
| |
| /** |
| * Called when the call setup is failed. |
| * The default implementation calls {@link #onCallError}. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param reasonInfo detailed reason of the call setup failure |
| */ |
| public void onCallStartFailed(ImsCall call, ImsReasonInfo reasonInfo) { |
| onCallError(call, reasonInfo); |
| } |
| |
| /** |
| * Called when the call is terminated. |
| * The default implementation calls {@link #onCallStateChanged}. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param reasonInfo detailed reason of the call termination |
| */ |
| public void onCallTerminated(ImsCall call, ImsReasonInfo reasonInfo) { |
| // Store the call termination reason |
| |
| onCallStateChanged(call); |
| } |
| |
| /** |
| * Called when the call is in hold. |
| * The default implementation calls {@link #onCallStateChanged}. |
| * |
| * @param call the call object that carries out the IMS call |
| */ |
| public void onCallHeld(ImsCall call) { |
| onCallStateChanged(call); |
| } |
| |
| /** |
| * Called when the call hold is failed. |
| * The default implementation calls {@link #onCallError}. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param reasonInfo detailed reason of the call hold failure |
| */ |
| public void onCallHoldFailed(ImsCall call, ImsReasonInfo reasonInfo) { |
| onCallError(call, reasonInfo); |
| } |
| |
| /** |
| * Called when the call hold is received from the remote user. |
| * The default implementation calls {@link #onCallStateChanged}. |
| * |
| * @param call the call object that carries out the IMS call |
| */ |
| public void onCallHoldReceived(ImsCall call) { |
| onCallStateChanged(call); |
| } |
| |
| /** |
| * Called when the call is in call. |
| * The default implementation calls {@link #onCallStateChanged}. |
| * |
| * @param call the call object that carries out the IMS call |
| */ |
| public void onCallResumed(ImsCall call) { |
| onCallStateChanged(call); |
| } |
| |
| /** |
| * Called when the call resume is failed. |
| * The default implementation calls {@link #onCallError}. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param reasonInfo detailed reason of the call resume failure |
| */ |
| public void onCallResumeFailed(ImsCall call, ImsReasonInfo reasonInfo) { |
| onCallError(call, reasonInfo); |
| } |
| |
| /** |
| * Called when the call resume is received from the remote user. |
| * The default implementation calls {@link #onCallStateChanged}. |
| * |
| * @param call the call object that carries out the IMS call |
| */ |
| public void onCallResumeReceived(ImsCall call) { |
| onCallStateChanged(call); |
| } |
| |
| /** |
| * Called when the call is in call. |
| * The default implementation calls {@link #onCallStateChanged}. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param newCall the call object that is merged with an active & hold call |
| */ |
| public void onCallMerged(ImsCall call) { |
| onCallStateChanged(call); |
| } |
| |
| /** |
| * Called when the call merge is failed. |
| * The default implementation calls {@link #onCallError}. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param reasonInfo detailed reason of the call merge failure |
| */ |
| public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) { |
| onCallError(call, reasonInfo); |
| } |
| |
| /** |
| * Called when the call is updated (except for hold/unhold). |
| * The default implementation calls {@link #onCallStateChanged}. |
| * |
| * @param call the call object that carries out the IMS call |
| */ |
| public void onCallUpdated(ImsCall call) { |
| onCallStateChanged(call); |
| } |
| |
| /** |
| * Called when the call update is failed. |
| * The default implementation calls {@link #onCallError}. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param reasonInfo detailed reason of the call update failure |
| */ |
| public void onCallUpdateFailed(ImsCall call, ImsReasonInfo reasonInfo) { |
| onCallError(call, reasonInfo); |
| } |
| |
| /** |
| * Called when the call update is received from the remote user. |
| * |
| * @param call the call object that carries out the IMS call |
| */ |
| public void onCallUpdateReceived(ImsCall call) { |
| // no-op |
| } |
| |
| /** |
| * Called when the call is extended to the conference call. |
| * The default implementation calls {@link #onCallStateChanged}. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param newCall the call object that is extended to the conference from the active call |
| */ |
| public void onCallConferenceExtended(ImsCall call, ImsCall newCall) { |
| onCallStateChanged(call); |
| } |
| |
| /** |
| * Called when the conference extension is failed. |
| * The default implementation calls {@link #onCallError}. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param reasonInfo detailed reason of the conference extension failure |
| */ |
| public void onCallConferenceExtendFailed(ImsCall call, |
| ImsReasonInfo reasonInfo) { |
| onCallError(call, reasonInfo); |
| } |
| |
| /** |
| * Called when the conference extension is received from the remote user. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param newCall the call object that is extended to the conference from the active call |
| */ |
| public void onCallConferenceExtendReceived(ImsCall call, ImsCall newCall) { |
| onCallStateChanged(call); |
| } |
| |
| /** |
| * Called when the invitation request of the participants is delivered to |
| * the conference server. |
| * |
| * @param call the call object that carries out the IMS call |
| */ |
| public void onCallInviteParticipantsRequestDelivered(ImsCall call) { |
| // no-op |
| } |
| |
| /** |
| * Called when the invitation request of the participants is failed. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param reasonInfo detailed reason of the conference invitation failure |
| */ |
| public void onCallInviteParticipantsRequestFailed(ImsCall call, |
| ImsReasonInfo reasonInfo) { |
| // no-op |
| } |
| |
| /** |
| * Called when the removal request of the participants is delivered to |
| * the conference server. |
| * |
| * @param call the call object that carries out the IMS call |
| */ |
| public void onCallRemoveParticipantsRequestDelivered(ImsCall call) { |
| // no-op |
| } |
| |
| /** |
| * Called when the removal request of the participants is failed. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param reasonInfo detailed reason of the conference removal failure |
| */ |
| public void onCallRemoveParticipantsRequestFailed(ImsCall call, |
| ImsReasonInfo reasonInfo) { |
| // no-op |
| } |
| |
| /** |
| * Called when the conference state is updated. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param state state of the participant who is participated in the conference call |
| */ |
| public void onCallConferenceStateUpdated(ImsCall call, ImsConferenceState state) { |
| // no-op |
| } |
| |
| /** |
| * Called when the state of an IMS conference participant has changed. |
| * |
| * @param call the call object that carries out the IMS call. |
| * @param participant the participant and its new state information. |
| */ |
| public void onConferenceParticipantStateChanged(ImsCall call, |
| ConferenceParticipant participant) { |
| // no-op |
| } |
| |
| /** |
| * Called when the USSD message is received from the network. |
| * |
| * @param mode mode of the USSD message (REQUEST / NOTIFY) |
| * @param ussdMessage USSD message |
| */ |
| public void onCallUssdMessageReceived(ImsCall call, |
| int mode, String ussdMessage) { |
| // no-op |
| } |
| |
| /** |
| * Called when an error occurs. The default implementation is no op. |
| * overridden. The default implementation is no op. Error events are |
| * not re-directed to this callback and are handled in {@link #onCallError}. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param reasonInfo detailed reason of this error |
| * @see ImsReasonInfo |
| */ |
| public void onCallError(ImsCall call, ImsReasonInfo reasonInfo) { |
| // no-op |
| } |
| |
| /** |
| * Called when an event occurs and the corresponding callback is not |
| * overridden. The default implementation is no op. Error events are |
| * not re-directed to this callback and are handled in {@link #onCallError}. |
| * |
| * @param call the call object that carries out the IMS call |
| */ |
| public void onCallStateChanged(ImsCall call) { |
| // no-op |
| } |
| |
| /** |
| * Called when the call moves the hold state to the conversation state. |
| * For example, when merging the active & hold call, the state of all the hold call |
| * will be changed from hold state to conversation state. |
| * This callback method can be invoked even though the application does not trigger |
| * any operations. |
| * |
| * @param call the call object that carries out the IMS call |
| * @param state the detailed state of call state changes; |
| * Refer to CALL_STATE_* in {@link ImsCall} |
| */ |
| public void onCallStateChanged(ImsCall call, int state) { |
| // no-op |
| } |
| } |
| |
| |
| |
| // List of update operation for IMS call control |
| private static final int UPDATE_NONE = 0; |
| private static final int UPDATE_HOLD = 1; |
| private static final int UPDATE_HOLD_MERGE = 2; |
| private static final int UPDATE_RESUME = 3; |
| private static final int UPDATE_MERGE = 4; |
| private static final int UPDATE_EXTEND_TO_CONFERENCE = 5; |
| private static final int UPDATE_UNSPECIFIED = 6; |
| |
| // For synchronization of private variables |
| private Object mLockObj = new Object(); |
| private Context mContext; |
| |
| // true if the call is established & in the conversation state |
| private boolean mInCall = false; |
| // true if the call is on hold |
| // If it is triggered by the local, mute the call. Otherwise, play local hold tone |
| // or network generated media. |
| private boolean mHold = false; |
| // true if the call is on mute |
| private boolean mMute = false; |
| // It contains the exclusive call update request. Refer to UPDATE_*. |
| private int mUpdateRequest = UPDATE_NONE; |
| |
| private ImsCall.Listener mListener = null; |
| // It is for managing the multiple calls |
| // when the multiparty call is extended to the conference. |
| private CallGroup mCallGroup = null; |
| |
| // Wrapper call session to interworking the IMS service (server). |
| private ImsCallSession mSession = null; |
| // Call profile of the current session. |
| // It can be changed at anytime when the call is updated. |
| private ImsCallProfile mCallProfile = null; |
| // Call profile to be updated after the application's action (accept/reject) |
| // to the call update. After the application's action (accept/reject) is done, |
| // it will be set to null. |
| private ImsCallProfile mProposedCallProfile = null; |
| private ImsReasonInfo mLastReasonInfo = null; |
| |
| // Media session to control media (audio/video) operations for an IMS call |
| private ImsStreamMediaSession mMediaSession = null; |
| |
| // The temporary ImsCallSession that could represent the merged call once |
| // we receive notification that the merge was successful. |
| private ImsCallSession mTransientConferenceSession = null; |
| // While a merge is progressing, we bury any session termination requests |
| // made on the original ImsCallSession until we have closure on the merge request |
| // If the request ultimately fails, we need to act on the termination request |
| // that we buried temporarily. We do this because we feel that timing issues could |
| // cause the termination request to occur just because the merge is succeeding. |
| private boolean mSessionEndDuringMerge = false; |
| // Just like mSessionEndDuringMerge, we need to keep track of the reason why the |
| // termination request was made on the original session in case we need to act |
| // on it in the case of a merge failure. |
| private ImsReasonInfo mSessionEndDuringMergeReasonInfo = null; |
| |
| /** |
| * Create an IMS call object. |
| * |
| * @param context the context for accessing system services |
| * @param profile the call profile to make/take a call |
| */ |
| public ImsCall(Context context, ImsCallProfile profile) { |
| mContext = context; |
| mCallProfile = profile; |
| } |
| |
| /** |
| * Closes this object. This object is not usable after being closed. |
| */ |
| @Override |
| public void close() { |
| synchronized(mLockObj) { |
| destroyCallGroup(); |
| |
| if (mSession != null) { |
| mSession.close(); |
| mSession = null; |
| } |
| |
| mCallProfile = null; |
| mProposedCallProfile = null; |
| mLastReasonInfo = null; |
| mMediaSession = null; |
| } |
| } |
| |
| /** |
| * Checks if the call has a same remote user identity or not. |
| * |
| * @param userId the remote user identity |
| * @return true if the remote user identity is equal; otherwise, false |
| */ |
| @Override |
| public boolean checkIfRemoteUserIsSame(String userId) { |
| if (userId == null) { |
| return false; |
| } |
| |
| return userId.equals(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_REMOTE_URI, "")); |
| } |
| |
| /** |
| * Checks if the call is equal or not. |
| * |
| * @param call the call to be compared |
| * @return true if the call is equal; otherwise, false |
| */ |
| @Override |
| public boolean equalsTo(ICall call) { |
| if (call == null) { |
| return false; |
| } |
| |
| if (call instanceof ImsCall) { |
| return this.equals(call); |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Gets the negotiated (local & remote) call profile. |
| * |
| * @return a {@link ImsCallProfile} object that has the negotiated call profile |
| */ |
| public ImsCallProfile getCallProfile() { |
| synchronized(mLockObj) { |
| return mCallProfile; |
| } |
| } |
| |
| /** |
| * Gets the local call profile (local capabilities). |
| * |
| * @return a {@link ImsCallProfile} object that has the local call profile |
| */ |
| public ImsCallProfile getLocalCallProfile() throws ImsException { |
| synchronized(mLockObj) { |
| if (mSession == null) { |
| throw new ImsException("No call session", |
| ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); |
| } |
| |
| try { |
| return mSession.getLocalCallProfile(); |
| } catch (Throwable t) { |
| loge("getLocalCallProfile :: ", t); |
| throw new ImsException("getLocalCallProfile()", t, 0); |
| } |
| } |
| } |
| |
| /** |
| * Gets the call profile proposed by the local/remote user. |
| * |
| * @return a {@link ImsCallProfile} object that has the proposed call profile |
| */ |
| public ImsCallProfile getProposedCallProfile() { |
| synchronized(mLockObj) { |
| if (!isInCall()) { |
| return null; |
| } |
| |
| return mProposedCallProfile; |
| } |
| } |
| |
| /** |
| * Gets the state of the {@link ImsCallSession} that carries this call. |
| * The value returned must be one of the states in {@link ImsCallSession#State}. |
| * |
| * @return the session state |
| */ |
| public int getState() { |
| synchronized(mLockObj) { |
| if (mSession == null) { |
| return ImsCallSession.State.IDLE; |
| } |
| |
| return mSession.getState(); |
| } |
| } |
| |
| /** |
| * Gets the {@link ImsCallSession} that carries this call. |
| * |
| * @return the session object that carries this call |
| * @hide |
| */ |
| public ImsCallSession getCallSession() { |
| synchronized(mLockObj) { |
| return mSession; |
| } |
| } |
| |
| /** |
| * Gets the {@link ImsStreamMediaSession} that handles the media operation of this call. |
| * Almost interface APIs are for the VT (Video Telephony). |
| * |
| * @return the media session object that handles the media operation of this call |
| * @hide |
| */ |
| public ImsStreamMediaSession getMediaSession() { |
| synchronized(mLockObj) { |
| return mMediaSession; |
| } |
| } |
| |
| /** |
| * Gets the specified property of this call. |
| * |
| * @param name key to get the extra call information defined in {@link ImsCallProfile} |
| * @return the extra call information as string |
| */ |
| public String getCallExtra(String name) throws ImsException { |
| // Lookup the cache |
| |
| synchronized(mLockObj) { |
| // If not found, try to get the property from the remote |
| if (mSession == null) { |
| throw new ImsException("No call session", |
| ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); |
| } |
| |
| try { |
| return mSession.getProperty(name); |
| } catch (Throwable t) { |
| loge("getCallExtra :: ", t); |
| throw new ImsException("getCallExtra()", t, 0); |
| } |
| } |
| } |
| |
| /** |
| * Gets the last reason information when the call is not established, cancelled or terminated. |
| * |
| * @return the last reason information |
| */ |
| public ImsReasonInfo getLastReasonInfo() { |
| synchronized(mLockObj) { |
| return mLastReasonInfo; |
| } |
| } |
| |
| /** |
| * Checks if the call has a pending update operation. |
| * |
| * @return true if the call has a pending update operation |
| */ |
| public boolean hasPendingUpdate() { |
| synchronized(mLockObj) { |
| return (mUpdateRequest != UPDATE_NONE); |
| } |
| } |
| |
| /** |
| * Checks if the call is established. |
| * |
| * @return true if the call is established |
| */ |
| public boolean isInCall() { |
| synchronized(mLockObj) { |
| return mInCall; |
| } |
| } |
| |
| /** |
| * Checks if the call is muted. |
| * |
| * @return true if the call is muted |
| */ |
| public boolean isMuted() { |
| synchronized(mLockObj) { |
| return mMute; |
| } |
| } |
| |
| /** |
| * Checks if the call is on hold. |
| * |
| * @return true if the call is on hold |
| */ |
| public boolean isOnHold() { |
| synchronized(mLockObj) { |
| return mHold; |
| } |
| } |
| |
| /** |
| * Determines if the call is a multiparty call. |
| * |
| * @return {@code True} if the call is a multiparty call. |
| */ |
| public boolean isMultiparty() { |
| synchronized(mLockObj) { |
| if (mSession == null) { |
| return false; |
| } |
| |
| return mSession.isMultiparty(); |
| } |
| } |
| |
| /** |
| * Sets the listener to listen to the IMS call events. |
| * The method calls {@link #setListener setListener(listener, false)}. |
| * |
| * @param listener to listen to the IMS call events of this object; null to remove listener |
| * @see #setListener(Listener, boolean) |
| */ |
| public void setListener(ImsCall.Listener listener) { |
| setListener(listener, false); |
| } |
| |
| /** |
| * Sets the listener to listen to the IMS call events. |
| * A {@link ImsCall} can only hold one listener at a time. Subsequent calls |
| * to this method override the previous listener. |
| * |
| * @param listener to listen to the IMS call events of this object; null to remove listener |
| * @param callbackImmediately set to true if the caller wants to be called |
| * back immediately on the current state |
| */ |
| public void setListener(ImsCall.Listener listener, boolean callbackImmediately) { |
| boolean inCall; |
| boolean onHold; |
| int state; |
| ImsReasonInfo lastReasonInfo; |
| |
| synchronized(mLockObj) { |
| mListener = listener; |
| |
| if ((listener == null) || !callbackImmediately) { |
| return; |
| } |
| |
| inCall = mInCall; |
| onHold = mHold; |
| state = getState(); |
| lastReasonInfo = mLastReasonInfo; |
| } |
| |
| try { |
| if (lastReasonInfo != null) { |
| listener.onCallError(this, lastReasonInfo); |
| } else if (inCall) { |
| if (onHold) { |
| listener.onCallHeld(this); |
| } else { |
| listener.onCallStarted(this); |
| } |
| } else { |
| switch (state) { |
| case ImsCallSession.State.ESTABLISHING: |
| listener.onCallProgressing(this); |
| break; |
| case ImsCallSession.State.TERMINATED: |
| listener.onCallTerminated(this, lastReasonInfo); |
| break; |
| default: |
| // Ignore it. There is no action in the other state. |
| break; |
| } |
| } |
| } catch (Throwable t) { |
| loge("setListener()", t); |
| } |
| } |
| |
| /** |
| * Mutes or unmutes the mic for the active call. |
| * |
| * @param muted true if the call is muted, false otherwise |
| */ |
| public void setMute(boolean muted) throws ImsException { |
| synchronized(mLockObj) { |
| if (mMute != muted) { |
| mMute = muted; |
| |
| try { |
| mSession.setMute(muted); |
| } catch (Throwable t) { |
| loge("setMute :: ", t); |
| throwImsException(t, 0); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Attaches an incoming call to this call object. |
| * |
| * @param session the session that receives the incoming call |
| * @throws ImsException if the IMS service fails to attach this object to the session |
| */ |
| public void attachSession(ImsCallSession session) throws ImsException { |
| if (DBG) { |
| log("attachSession :: session=" + session); |
| } |
| |
| synchronized(mLockObj) { |
| mSession = session; |
| |
| try { |
| mSession.setListener(createCallSessionListener()); |
| } catch (Throwable t) { |
| loge("attachSession :: ", t); |
| throwImsException(t, 0); |
| } |
| } |
| } |
| |
| /** |
| * Initiates an IMS call with the call profile which is provided |
| * when creating a {@link ImsCall}. |
| * |
| * @param session the {@link ImsCallSession} for carrying out the call |
| * @param callee callee information to initiate an IMS call |
| * @throws ImsException if the IMS service fails to initiate the call |
| */ |
| public void start(ImsCallSession session, String callee) |
| throws ImsException { |
| if (DBG) { |
| log("start(1) :: session=" + session + ", callee=" + callee); |
| } |
| |
| synchronized(mLockObj) { |
| mSession = session; |
| |
| try { |
| session.setListener(createCallSessionListener()); |
| session.start(callee, mCallProfile); |
| } catch (Throwable t) { |
| loge("start(1) :: ", t); |
| throw new ImsException("start(1)", t, 0); |
| } |
| } |
| } |
| |
| /** |
| * Initiates an IMS conferenca call with the call profile which is provided |
| * when creating a {@link ImsCall}. |
| * |
| * @param session the {@link ImsCallSession} for carrying out the call |
| * @param participants participant list to initiate an IMS conference call |
| * @throws ImsException if the IMS service fails to initiate the call |
| */ |
| public void start(ImsCallSession session, String[] participants) |
| throws ImsException { |
| if (DBG) { |
| log("start(n) :: session=" + session + ", callee=" + participants); |
| } |
| |
| synchronized(mLockObj) { |
| mSession = session; |
| |
| try { |
| session.setListener(createCallSessionListener()); |
| session.start(participants, mCallProfile); |
| } catch (Throwable t) { |
| loge("start(n) :: ", t); |
| throw new ImsException("start(n)", t, 0); |
| } |
| } |
| } |
| |
| /** |
| * Accepts a call. |
| * |
| * @see Listener#onCallStarted |
| * |
| * @param callType The call type the user agreed to for accepting the call. |
| * @throws ImsException if the IMS service fails to accept the call |
| */ |
| public void accept(int callType) throws ImsException { |
| if (DBG) { |
| log("accept :: session=" + mSession); |
| } |
| |
| accept(callType, new ImsStreamMediaProfile()); |
| } |
| |
| /** |
| * Accepts a call. |
| * |
| * @param callType call type to be answered in {@link ImsCallProfile} |
| * @param profile a media profile to be answered (audio/audio & video, direction, ...) |
| * @see Listener#onCallStarted |
| * @throws ImsException if the IMS service fails to accept the call |
| */ |
| public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException { |
| if (DBG) { |
| log("accept :: session=" + mSession |
| + ", callType=" + callType + ", profile=" + profile); |
| } |
| |
| synchronized(mLockObj) { |
| if (mSession == null) { |
| throw new ImsException("No call to answer", |
| ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); |
| } |
| |
| try { |
| mSession.accept(callType, profile); |
| } catch (Throwable t) { |
| loge("accept :: ", t); |
| throw new ImsException("accept()", t, 0); |
| } |
| |
| if (mInCall && (mProposedCallProfile != null)) { |
| if (DBG) { |
| log("accept :: call profile will be updated"); |
| } |
| |
| mCallProfile = mProposedCallProfile; |
| mProposedCallProfile = null; |
| } |
| |
| // Other call update received |
| if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) { |
| mUpdateRequest = UPDATE_NONE; |
| } |
| } |
| } |
| |
| /** |
| * Rejects a call. |
| * |
| * @param reason reason code to reject an incoming call |
| * @see Listener#onCallStartFailed |
| * @throws ImsException if the IMS service fails to accept the call |
| */ |
| public void reject(int reason) throws ImsException { |
| if (DBG) { |
| log("reject :: session=" + mSession + ", reason=" + reason); |
| } |
| |
| synchronized(mLockObj) { |
| if (mSession != null) { |
| mSession.reject(reason); |
| } |
| |
| if (mInCall && (mProposedCallProfile != null)) { |
| if (DBG) { |
| log("reject :: call profile is not updated; destroy it..."); |
| } |
| |
| mProposedCallProfile = null; |
| } |
| |
| // Other call update received |
| if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) { |
| mUpdateRequest = UPDATE_NONE; |
| } |
| } |
| } |
| |
| /** |
| * Terminates an IMS call. |
| * |
| * @param reason reason code to terminate a call |
| * @throws ImsException if the IMS service fails to terminate the call |
| */ |
| public void terminate(int reason) throws ImsException { |
| if (DBG) { |
| log("terminate :: session=" + mSession + ", reason=" + reason); |
| } |
| |
| synchronized(mLockObj) { |
| mHold = false; |
| mInCall = false; |
| CallGroup callGroup = getCallGroup(); |
| |
| if (mSession != null) { |
| if (callGroup != null && !callGroup.isOwner(ImsCall.this)) { |
| log("terminate owner of the call group"); |
| ImsCall owner = (ImsCall) callGroup.getOwner(); |
| if (owner != null) { |
| owner.terminate(reason); |
| return; |
| } |
| } |
| mSession.terminate(reason); |
| } |
| } |
| } |
| |
| |
| /** |
| * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called. |
| * |
| * @see Listener#onCallHeld, Listener#onCallHoldFailed |
| * @throws ImsException if the IMS service fails to hold the call |
| */ |
| public void hold() throws ImsException { |
| if (DBG) { |
| log("hold :: session=" + mSession); |
| } |
| |
| // perform operation on owner before doing any local checks: local |
| // call may not have its status updated |
| synchronized (mLockObj) { |
| CallGroup callGroup = mCallGroup; |
| if (callGroup != null && !callGroup.isOwner(ImsCall.this)) { |
| log("hold owner of the call group"); |
| ImsCall owner = (ImsCall) callGroup.getOwner(); |
| if (owner != null) { |
| owner.hold(); |
| return; |
| } |
| } |
| } |
| |
| if (isOnHold()) { |
| if (DBG) { |
| log("hold :: call is already on hold"); |
| } |
| return; |
| } |
| |
| synchronized(mLockObj) { |
| if (mUpdateRequest != UPDATE_NONE) { |
| loge("hold :: update is in progress; request=" + mUpdateRequest); |
| throw new ImsException("Call update is in progress", |
| ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); |
| } |
| |
| if (mSession == null) { |
| loge("hold :: "); |
| throw new ImsException("No call session", |
| ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); |
| } |
| |
| mSession.hold(createHoldMediaProfile()); |
| // FIXME: update the state on the callback? |
| mHold = true; |
| mUpdateRequest = UPDATE_HOLD; |
| } |
| } |
| |
| /** |
| * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called. |
| * |
| * @see Listener#onCallResumed, Listener#onCallResumeFailed |
| * @throws ImsException if the IMS service fails to resume the call |
| */ |
| public void resume() throws ImsException { |
| if (DBG) { |
| log("resume :: session=" + mSession); |
| } |
| |
| // perform operation on owner before doing any local checks: local |
| // call may not have its status updated |
| synchronized (mLockObj) { |
| CallGroup callGroup = mCallGroup; |
| if (callGroup != null && !callGroup.isOwner(ImsCall.this)) { |
| log("resume owner of the call group"); |
| ImsCall owner = (ImsCall) callGroup.getOwner(); |
| if (owner != null) { |
| owner.resume(); |
| return; |
| } |
| } |
| } |
| |
| if (!isOnHold()) { |
| if (DBG) { |
| log("resume :: call is in conversation"); |
| } |
| return; |
| } |
| |
| synchronized(mLockObj) { |
| if (mUpdateRequest != UPDATE_NONE) { |
| loge("resume :: update is in progress; request=" + mUpdateRequest); |
| throw new ImsException("Call update is in progress", |
| ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); |
| } |
| |
| if (mSession == null) { |
| loge("resume :: "); |
| throw new ImsException("No call session", |
| ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); |
| } |
| |
| mSession.resume(createResumeMediaProfile()); |
| // FIXME: update the state on the callback? |
| mHold = false; |
| mUpdateRequest = UPDATE_RESUME; |
| } |
| } |
| |
| /** |
| * Merges the active & hold call. |
| * |
| * @see Listener#onCallMerged, Listener#onCallMergeFailed |
| * @throws ImsException if the IMS service fails to merge the call |
| */ |
| public void merge() throws ImsException { |
| if (DBG) { |
| log("merge :: session=" + mSession); |
| } |
| |
| synchronized(mLockObj) { |
| if (mUpdateRequest != UPDATE_NONE) { |
| loge("merge :: update is in progress; request=" + mUpdateRequest); |
| throw new ImsException("Call update is in progress", |
| ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); |
| } |
| |
| if (mSession == null) { |
| loge("merge :: "); |
| throw new ImsException("No call session", |
| ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); |
| } |
| |
| // if skipHoldBeforeMerge = true, IMS service implementation will |
| // merge without explicitly holding the call. |
| if (mHold || (mContext.getResources().getBoolean( |
| com.android.internal.R.bool.skipHoldBeforeMerge))) { |
| mSession.merge(); |
| |
| // Check to see if there is an owner to a valid call group. If this is the |
| // case, then we already have a conference call. |
| if (mCallGroup != null && mCallGroup.getOwner() == null) { |
| // We only set UPDATE_MERGE when we are adding the first |
| // calls to the Conference. If there is already a conference |
| // no special handling is needed.The existing conference |
| // session will just go active and any other sessions will be terminated |
| // if needed. There will be no merge failed callback. |
| mUpdateRequest = UPDATE_MERGE; |
| } |
| } else { |
| // This code basically says, we need to explicitly hold before requesting a merge |
| // when we get the callback that the hold was successful (or failed), we should |
| // automatically request a merge. |
| mSession.hold(createHoldMediaProfile()); |
| mHold = true; |
| mUpdateRequest = UPDATE_HOLD_MERGE; |
| } |
| } |
| } |
| |
| /** |
| * Merges the active & hold call. |
| * |
| * @param bgCall the background (holding) call |
| * @see Listener#onCallMerged, Listener#onCallMergeFailed |
| * @throws ImsException if the IMS service fails to merge the call |
| */ |
| public void merge(ImsCall bgCall) throws ImsException { |
| if (DBG) { |
| log("merge(1) :: session=" + mSession); |
| } |
| |
| if (bgCall == null) { |
| throw new ImsException("No background call", |
| ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT); |
| } |
| |
| synchronized(mLockObj) { |
| createCallGroup(bgCall); |
| } |
| |
| merge(); |
| } |
| |
| /** |
| * Updates the current call's properties (ex. call mode change: video upgrade / downgrade). |
| */ |
| public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException { |
| if (DBG) { |
| log("update :: session=" + mSession); |
| } |
| |
| if (isOnHold()) { |
| if (DBG) { |
| log("update :: call is on hold"); |
| } |
| throw new ImsException("Not in a call to update call", |
| ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); |
| } |
| |
| synchronized(mLockObj) { |
| if (mUpdateRequest != UPDATE_NONE) { |
| if (DBG) { |
| log("update :: update is in progress; request=" + mUpdateRequest); |
| } |
| throw new ImsException("Call update is in progress", |
| ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); |
| } |
| |
| if (mSession == null) { |
| loge("update :: "); |
| throw new ImsException("No call session", |
| ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); |
| } |
| |
| mSession.update(callType, mediaProfile); |
| mUpdateRequest = UPDATE_UNSPECIFIED; |
| } |
| } |
| |
| /** |
| * Extends this call (1-to-1 call) to the conference call |
| * inviting the specified participants to. |
| * |
| */ |
| public void extendToConference(String[] participants) throws ImsException { |
| if (DBG) { |
| log("extendToConference :: session=" + mSession); |
| } |
| |
| if (isOnHold()) { |
| if (DBG) { |
| log("extendToConference :: call is on hold"); |
| } |
| throw new ImsException("Not in a call to extend a call to conference", |
| ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); |
| } |
| |
| synchronized(mLockObj) { |
| if (mUpdateRequest != UPDATE_NONE) { |
| if (DBG) { |
| log("extendToConference :: update is in progress; request=" + mUpdateRequest); |
| } |
| throw new ImsException("Call update is in progress", |
| ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE); |
| } |
| |
| if (mSession == null) { |
| loge("extendToConference :: "); |
| throw new ImsException("No call session", |
| ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); |
| } |
| |
| mSession.extendToConference(participants); |
| mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE; |
| } |
| } |
| |
| /** |
| * Requests the conference server to invite an additional participants to the conference. |
| * |
| */ |
| public void inviteParticipants(String[] participants) throws ImsException { |
| if (DBG) { |
| log("inviteParticipants :: session=" + mSession); |
| } |
| |
| synchronized(mLockObj) { |
| if (mSession == null) { |
| loge("inviteParticipants :: "); |
| throw new ImsException("No call session", |
| ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); |
| } |
| |
| mSession.inviteParticipants(participants); |
| } |
| } |
| |
| /** |
| * Requests the conference server to remove the specified participants from the conference. |
| * |
| */ |
| public void removeParticipants(String[] participants) throws ImsException { |
| if (DBG) { |
| log("removeParticipants :: session=" + mSession); |
| } |
| |
| synchronized(mLockObj) { |
| if (mSession == null) { |
| loge("removeParticipants :: "); |
| throw new ImsException("No call session", |
| ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); |
| } |
| |
| mSession.removeParticipants(participants); |
| } |
| } |
| |
| |
| /** |
| * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>, |
| * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, |
| * and event flash to 16. Currently, event flash is not supported. |
| * |
| * @param char that represents the DTMF digit to send. |
| */ |
| public void sendDtmf(char c) { |
| sendDtmf(c, null); |
| } |
| |
| /** |
| * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>, |
| * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, |
| * and event flash to 16. Currently, event flash is not supported. |
| * |
| * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. |
| * @param result the result message to send when done. |
| */ |
| public void sendDtmf(char c, Message result) { |
| if (DBG) { |
| log("sendDtmf :: session=" + mSession + ", code=" + c); |
| } |
| |
| synchronized(mLockObj) { |
| if (mSession != null) { |
| mSession.sendDtmf(c); |
| } |
| } |
| |
| if (result != null) { |
| result.sendToTarget(); |
| } |
| } |
| |
| /** |
| * Sends an USSD message. |
| * |
| * @param ussdMessage USSD message to send |
| */ |
| public void sendUssd(String ussdMessage) throws ImsException { |
| if (DBG) { |
| log("sendUssd :: session=" + mSession + ", ussdMessage=" + ussdMessage); |
| } |
| |
| synchronized(mLockObj) { |
| if (mSession == null) { |
| loge("sendUssd :: "); |
| throw new ImsException("No call session", |
| ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED); |
| } |
| |
| mSession.sendUssd(ussdMessage); |
| } |
| } |
| |
| private void clear(ImsReasonInfo lastReasonInfo) { |
| mInCall = false; |
| mHold = false; |
| mUpdateRequest = UPDATE_NONE; |
| mLastReasonInfo = lastReasonInfo; |
| destroyCallGroup(); |
| } |
| |
| private void createCallGroup(ImsCall neutralReferrer) { |
| CallGroup referrerCallGroup = neutralReferrer.getCallGroup(); |
| |
| if (mCallGroup == null) { |
| if (referrerCallGroup == null) { |
| mCallGroup = CallGroupManager.getInstance().createCallGroup(new ImsCallGroup()); |
| } else { |
| mCallGroup = referrerCallGroup; |
| } |
| |
| if (mCallGroup != null) { |
| mCallGroup.setNeutralReferrer(neutralReferrer); |
| } |
| } else { |
| mCallGroup.setNeutralReferrer(neutralReferrer); |
| |
| if ((referrerCallGroup != null) |
| && (mCallGroup != referrerCallGroup)) { |
| loge("fatal :: call group is mismatched; call is corrupted..."); |
| } |
| } |
| } |
| |
| private void updateCallGroup(ImsCall owner) { |
| if (mCallGroup == null) { |
| return; |
| } |
| |
| ImsCall neutralReferrer = (ImsCall)mCallGroup.getNeutralReferrer(); |
| |
| if (owner == null) { |
| // Maintain the call group if the current call has been merged in the past. |
| if (!mCallGroup.hasReferrer()) { |
| CallGroupManager.getInstance().destroyCallGroup(mCallGroup); |
| mCallGroup = null; |
| } |
| } else { |
| mCallGroup.addReferrer(this); |
| |
| if (neutralReferrer != null) { |
| if (neutralReferrer.getCallGroup() == null) { |
| neutralReferrer.setCallGroup(mCallGroup); |
| mCallGroup.addReferrer(neutralReferrer); |
| } |
| |
| neutralReferrer.enforceConversationMode(); |
| } |
| |
| // Close the existing owner call if present |
| ImsCall exOwner = (ImsCall)mCallGroup.getOwner(); |
| |
| mCallGroup.setOwner(owner); |
| |
| if (exOwner != null) { |
| exOwner.close(); |
| } |
| } |
| } |
| |
| private void destroyCallGroup() { |
| if (mCallGroup == null) { |
| return; |
| } |
| |
| mCallGroup.removeReferrer(this); |
| |
| if (!mCallGroup.hasReferrer()) { |
| CallGroupManager.getInstance().destroyCallGroup(mCallGroup); |
| } |
| |
| mCallGroup = null; |
| } |
| |
| public CallGroup getCallGroup() { |
| synchronized(mLockObj) { |
| return mCallGroup; |
| } |
| } |
| |
| private void setCallGroup(CallGroup callGroup) { |
| synchronized(mLockObj) { |
| mCallGroup = callGroup; |
| } |
| } |
| |
| /** |
| * Creates an IMS call session listener. |
| */ |
| private ImsCallSession.Listener createCallSessionListener() { |
| return new ImsCallSessionListenerProxy(); |
| } |
| |
| private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) { |
| ImsCall call = new ImsCall(mContext, profile); |
| |
| try { |
| call.attachSession(session); |
| } catch (ImsException e) { |
| if (call != null) { |
| call.close(); |
| call = null; |
| } |
| } |
| |
| // Do additional operations... |
| |
| return call; |
| } |
| |
| private ImsStreamMediaProfile createHoldMediaProfile() { |
| ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(); |
| |
| if (mCallProfile == null) { |
| return mediaProfile; |
| } |
| |
| mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality; |
| mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality; |
| mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND; |
| |
| if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) { |
| mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND; |
| } |
| |
| return mediaProfile; |
| } |
| |
| private ImsStreamMediaProfile createResumeMediaProfile() { |
| ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(); |
| |
| if (mCallProfile == null) { |
| return mediaProfile; |
| } |
| |
| mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality; |
| mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality; |
| mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE; |
| |
| if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) { |
| mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE; |
| } |
| |
| return mediaProfile; |
| } |
| |
| private void enforceConversationMode() { |
| if (mInCall) { |
| mHold = false; |
| mUpdateRequest = UPDATE_NONE; |
| } |
| } |
| |
| private void mergeInternal() { |
| if (DBG) { |
| log("mergeInternal :: session=" + mSession); |
| } |
| |
| mSession.merge(); |
| mUpdateRequest = UPDATE_MERGE; |
| } |
| |
| private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) { |
| ImsCall.Listener listener; |
| if (mCallGroup.isOwner(ImsCall.this)) { |
| log("Group Owner! Size of referrers list = " + mCallGroup.getReferrers().size()); |
| while (mCallGroup.hasReferrer()) { |
| ImsCall call = (ImsCall) mCallGroup.getReferrers().get(0); |
| log("onCallTerminated to be called for the call:: " + call); |
| |
| if (call == null) { |
| continue; |
| } |
| |
| listener = call.mListener; |
| call.clear(reasonInfo); |
| |
| if (listener != null) { |
| try { |
| listener.onCallTerminated(call, reasonInfo); |
| } catch (Throwable t) { |
| loge("notifyConferenceSessionTerminated :: ", t); |
| } |
| } |
| } |
| } else if (!mCallGroup.isReferrer(ImsCall.this)) { |
| return; |
| } |
| |
| listener = mListener; |
| clear(reasonInfo); |
| |
| if (listener != null) { |
| try { |
| listener.onCallTerminated(this, reasonInfo); |
| } catch (Throwable t) { |
| loge("notifyConferenceSessionTerminated :: ", t); |
| } |
| } |
| } |
| |
| private void notifyConferenceStateUpdatedThroughGroupOwner(int update) { |
| ImsCall.Listener listener; |
| |
| if (mCallGroup.isOwner(ImsCall.this)) { |
| log("Group Owner! Size of referrers list = " + mCallGroup.getReferrers().size()); |
| for (ICall icall : mCallGroup.getReferrers()) { |
| ImsCall call = (ImsCall) icall; |
| log("notifyConferenceStateUpdatedThroughGroupOwner to be called for the call:: " + |
| call); |
| |
| if (call == null) { |
| continue; |
| } |
| |
| listener = call.mListener; |
| |
| if (listener != null) { |
| try { |
| switch (update) { |
| case UPDATE_HOLD: |
| listener.onCallHeld(call); |
| break; |
| case UPDATE_RESUME: |
| listener.onCallResumed(call); |
| break; |
| default: |
| loge("notifyConferenceStateUpdatedThroughGroupOwner :: not " + |
| "handled update " + update); |
| } |
| } catch (Throwable t) { |
| loge("notifyConferenceStateUpdatedThroughGroupOwner :: ", t); |
| } |
| } |
| } |
| } |
| } |
| |
| private void notifyConferenceStateUpdated(ImsConferenceState state) { |
| Set<Entry<String, Bundle>> paticipants = state.mParticipants.entrySet(); |
| |
| if (paticipants == null) { |
| return; |
| } |
| |
| Iterator<Entry<String, Bundle>> iterator = paticipants.iterator(); |
| |
| while (iterator.hasNext()) { |
| Entry<String, Bundle> entry = iterator.next(); |
| |
| String key = entry.getKey(); |
| Bundle confInfo = entry.getValue(); |
| String status = confInfo.getString(ImsConferenceState.STATUS); |
| String user = confInfo.getString(ImsConferenceState.USER); |
| String displayName = confInfo.getString(ImsConferenceState.DISPLAY_TEXT); |
| String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT); |
| |
| if (DBG) { |
| log("notifyConferenceStateUpdated :: key=" + key + |
| ", status=" + status + |
| ", user=" + user + |
| ", displayName= " + displayName + |
| ", endpoint=" + endpoint); |
| } |
| |
| if ((mCallGroup != null) && (!mCallGroup.isOwner(ImsCall.this))) { |
| continue; |
| } |
| |
| // Attempt to find the participant in the call group if it exists. |
| ImsCall referrer = null; |
| if (mCallGroup != null) { |
| referrer = (ImsCall) mCallGroup.getReferrer(endpoint); |
| } |
| |
| // Participant is not being represented by an ImsCall, so handle as generic participant. |
| // Notify the {@code ImsPhoneCallTracker} of the participant state change so that it |
| // can be passed up to the {@code TelephonyConferenceController}. |
| if (referrer == null) { |
| Uri handle = Uri.parse(user); |
| Uri endpointUri = Uri.parse(endpoint); |
| int connectionState = ImsConferenceState.getConnectionStateForStatus(status); |
| |
| ConferenceParticipant conferenceParticipant = new ConferenceParticipant(handle, |
| displayName, endpointUri, connectionState); |
| if (mListener != null) { |
| try { |
| mListener.onConferenceParticipantStateChanged(this, conferenceParticipant); |
| } catch (Throwable t) { |
| loge("notifyConferenceStateUpdated :: ", t); |
| } |
| } |
| continue; |
| } |
| |
| if (referrer.mListener == null) { |
| continue; |
| } |
| |
| try { |
| if (status.equals(ImsConferenceState.STATUS_ALERTING)) { |
| referrer.mListener.onCallProgressing(referrer); |
| } |
| else if (status.equals(ImsConferenceState.STATUS_CONNECT_FAIL)) { |
| referrer.mListener.onCallStartFailed(referrer, new ImsReasonInfo()); |
| } |
| else if (status.equals(ImsConferenceState.STATUS_ON_HOLD)) { |
| referrer.mListener.onCallHoldReceived(referrer); |
| } |
| else if (status.equals(ImsConferenceState.STATUS_CONNECTED)) { |
| referrer.mListener.onCallStarted(referrer); |
| } |
| else if (status.equals(ImsConferenceState.STATUS_DISCONNECTED)) { |
| referrer.clear(new ImsReasonInfo()); |
| referrer.mListener.onCallTerminated(referrer, referrer.mLastReasonInfo); |
| } |
| } catch (Throwable t) { |
| loge("notifyConferenceStateUpdated :: ", t); |
| } |
| } |
| } |
| |
| /** |
| * Perform all cleanup and notification around the termination of a session. |
| * Note that there are 2 distinct modes of operation. The first is when |
| * we receive a session termination on the primary session when we are |
| * in the processing of merging. The second is when we are not merging anything |
| * and the call is terminated. |
| * |
| * @param reasonInfo The reason for the session termination |
| */ |
| private void processCallTerminated(ImsReasonInfo reasonInfo) { |
| if (DBG) { |
| String sessionString = mSession != null ? mSession.toString() : "null"; |
| String transientSessionString = mTransientConferenceSession != null ? |
| mTransientConferenceSession.toString() : "null"; |
| String reasonString = reasonInfo != null ? reasonInfo.toString() : "null"; |
| log("processCallTerminated :: session=" + sessionString + " transientSession=" + |
| transientSessionString + " reason=" + reasonString); |
| } |
| |
| ImsCall.Listener listener = null; |
| |
| synchronized(ImsCall.this) { |
| if (mUpdateRequest == UPDATE_MERGE) { |
| // Since we are in the process of a merge, this trigger means something |
| // else because it is probably due to the merge happening vs. the |
| // session is really terminated. Let's flag this and revisit if |
| // the merge() ends up failing because we will need to take action on the |
| // mSession in that case since the termination was not due to the merge |
| // succeeding. |
| if (DBG) { |
| log("processCallTerminated :: burying termination during ongoing merge."); |
| } |
| mSessionEndDuringMerge = true; |
| mSessionEndDuringMergeReasonInfo = reasonInfo; |
| return; |
| } |
| |
| if (mCallGroup != null) { |
| notifyConferenceSessionTerminated(reasonInfo); |
| } else { |
| listener = mListener; |
| clear(reasonInfo); |
| } |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallTerminated(ImsCall.this, reasonInfo); |
| } catch (Throwable t) { |
| loge("callSessionTerminated :: ", t); |
| } |
| } |
| } |
| |
| /** |
| * This function determines if the ImsCallSession is our actual ImsCallSession or if is |
| * the transient session used in the process of creating a conference. This function should only |
| * be called within callbacks that are not directly related to conference merging but might |
| * potentially still be called on the transient ImsCallSession sent to us from |
| * callSessionMergeStarted() when we don't really care. In those situations, we probably don't |
| * want to take any action so we need to know that we can return early. |
| * |
| * @param session - The {@link ImsCallSession} that the function needs to analyze |
| * @return true if this is the transient {@link ImsCallSession}, false otherwise. |
| */ |
| private boolean isTransientConferenceSession(ImsCallSession session) { |
| if (session != null && session != mSession && session == mTransientConferenceSession) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * We received a callback from ImsCallSession that a merge was complete. Clean up all |
| * internal state to represent this state change. This function will be called when |
| * the transient conference session goes active or we get an explicit merge complete |
| * callback on the transient session. |
| * |
| */ |
| private void processMergeComplete() { |
| if (DBG) { |
| String sessionString = mSession != null ? mSession.toString() : "null"; |
| String transientSessionString = mTransientConferenceSession != null ? |
| mTransientConferenceSession.toString() : "null"; |
| log("processMergeComplete :: session=" + sessionString + " transientSession=" + |
| transientSessionString); |
| } |
| |
| ImsCall.Listener listener; |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| if (mTransientConferenceSession != null) { |
| // Swap out the underlying sessions after shutting down the existing session. |
| mSession.setListener(null); |
| mSession = mTransientConferenceSession; |
| // We need to set ourselves as the owner of the call group to indicate that |
| // a conference call is in progress. |
| mCallGroup.setOwner(ImsCall.this); |
| listener = mListener; |
| } else { |
| // This is an interesting state that needs to be logged since we |
| // should only be going through this workflow for new conference calls |
| // and not merges into existing conferences (which a null transient |
| // session would imply) |
| log("processMergeComplete :: ERROR no transient session"); |
| } |
| // Clear some flags. If the merge eventually worked, we can safely |
| // ignore the call terminated message for the old session since we closed it already. |
| mSessionEndDuringMerge = false; |
| mSessionEndDuringMergeReasonInfo = null; |
| mUpdateRequest = UPDATE_NONE; |
| } |
| if (listener != null) { |
| try { |
| listener.onCallMerged(ImsCall.this); |
| } catch (Throwable t) { |
| loge("processMergeComplete :: ", t); |
| } |
| } |
| |
| return; |
| } |
| |
| /** |
| * We received a callback from ImsCallSession that a merge failed. Clean up all |
| * internal state to represent this state change. |
| * |
| * @param reasonInfo The {@link ImsReasonInfo} why the merge failed. |
| */ |
| private void processMergeFailed(ImsReasonInfo reasonInfo) { |
| if (DBG) { |
| String sessionString = mSession != null ? mSession.toString() : "null"; |
| String transientSessionString = mTransientConferenceSession != null ? |
| mTransientConferenceSession.toString() : "null"; |
| String reasonString = reasonInfo != null ? reasonInfo.toString() : "null"; |
| log("processMergeFailed :: session=" + sessionString + " transientSession=" + |
| transientSessionString + " reason=" + reasonString); |
| } |
| |
| ImsCall.Listener listener; |
| boolean notifyFailure = false; |
| ImsReasonInfo notifyFailureReasonInfo = null; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| if (mTransientConferenceSession != null) { |
| // Clean up any work that we performed on the transient session. |
| mTransientConferenceSession.setListener(null); |
| mTransientConferenceSession = null; |
| listener = mListener; |
| if (mSessionEndDuringMerge) { |
| // Set some local variables that will send out a notification about a |
| // previously buried termination callback for our primary session now that |
| // we know that this is not due to the conference call merging succesfully. |
| if (DBG) { |
| log("processMergeFailed :: following up on a terminate during the merge"); |
| } |
| notifyFailure = true; |
| notifyFailureReasonInfo = mSessionEndDuringMergeReasonInfo; |
| } |
| } else { |
| // This is an interesting state that needs to be logged since we |
| // should only be going through this workflow for new conference calls |
| // and not merges into existing conferences (which a null transient |
| // session would imply) |
| log("processMergeFailed - ERROR no transient session"); |
| } |
| mSessionEndDuringMerge = false; |
| mSessionEndDuringMergeReasonInfo = null; |
| mUpdateRequest = UPDATE_NONE; |
| } |
| if (listener != null) { |
| try { |
| // TODO: are both of these callbacks necessary? |
| listener.onCallMergeFailed(ImsCall.this, reasonInfo); |
| if (notifyFailure) { |
| processCallTerminated(notifyFailureReasonInfo); |
| } |
| } catch (Throwable t) { |
| loge("processMergeFailed :: ", t); |
| } |
| } |
| return; |
| } |
| |
| private void notifyError(int reason, int statusCode, String message) { |
| } |
| |
| private void throwImsException(Throwable t, int code) throws ImsException { |
| if (t instanceof ImsException) { |
| throw (ImsException) t; |
| } else { |
| throw new ImsException(String.valueOf(code), t, code); |
| } |
| } |
| |
| private void log(String s) { |
| Rlog.d(TAG, s); |
| } |
| |
| private void loge(String s) { |
| Rlog.e(TAG, s); |
| } |
| |
| private void loge(String s, Throwable t) { |
| Rlog.e(TAG, s, t); |
| } |
| |
| private class ImsCallSessionListenerProxy extends ImsCallSession.Listener { |
| @Override |
| public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionProgressing :: not supported for transient conference session=" + |
| session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionProgressing :: session=" + session + ", profile=" + profile); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| mCallProfile.mMediaProfile.copyFrom(profile); |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallProgressing(ImsCall.this); |
| } catch (Throwable t) { |
| loge("callSessionProgressing :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) { |
| if (DBG) { |
| log("callSessionStarted :: session=" + session + ", profile=" + profile); |
| } |
| |
| if (isTransientConferenceSession(session)) { |
| log("callSessionStarted :: transient conference session resumed session=" + |
| session); |
| // If we get a resume on the transient session, this means that the merge |
| // was completed, let's process it are skip the rest of the processing in |
| // this callback. |
| processMergeComplete(); |
| return; |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| mCallProfile = profile; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallStarted(ImsCall.this); |
| } catch (Throwable t) { |
| loge("callSessionStarted :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionStartFailed :: not supported for transient conference session=" + |
| session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionStartFailed :: session=" + session + |
| ", reasonInfo=" + reasonInfo); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| mLastReasonInfo = reasonInfo; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallStartFailed(ImsCall.this, reasonInfo); |
| } catch (Throwable t) { |
| loge("callSessionStarted :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionTerminated :: not supported for transient conference session=" + |
| session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionTerminated :: session=" + session + ", reasonInfo=" + reasonInfo); |
| } |
| |
| processCallTerminated(reasonInfo); |
| } |
| |
| @Override |
| public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionHeld :: not supported for transient conference session=" + session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionHeld :: session=" + session + ", profile=" + profile); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| mCallProfile = profile; |
| |
| if (mUpdateRequest == UPDATE_HOLD_MERGE) { |
| mergeInternal(); |
| return; |
| } |
| |
| mUpdateRequest = UPDATE_NONE; |
| listener = mListener; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallHeld(ImsCall.this); |
| } catch (Throwable t) { |
| loge("callSessionHeld :: ", t); |
| } |
| } |
| |
| if (mCallGroup != null) { |
| notifyConferenceStateUpdatedThroughGroupOwner(UPDATE_HOLD); |
| } |
| } |
| |
| @Override |
| public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionHoldFailed :: not supported for transient conference session=" + |
| session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionHoldFailed :: session=" + session + |
| ", reasonInfo=" + reasonInfo); |
| } |
| |
| boolean isHoldForMerge = false; |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| if (mUpdateRequest == UPDATE_HOLD_MERGE) { |
| isHoldForMerge = true; |
| } |
| |
| mUpdateRequest = UPDATE_NONE; |
| listener = mListener; |
| } |
| |
| if (isHoldForMerge) { |
| // Is hold for merge implemented/supported? If so we need to take a close look |
| // at this workflow to make sure that we handle the case where |
| // callSessionMergeFailed() does the right thing because we have not actually |
| // started the merge yet. |
| callSessionMergeFailed(session, reasonInfo); |
| return; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallHoldFailed(ImsCall.this, reasonInfo); |
| } catch (Throwable t) { |
| loge("callSessionHoldFailed :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionHoldReceived :: not supported for transient conference session=" + |
| session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionHoldReceived :: session=" + session + ", profile=" + profile); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| mCallProfile = profile; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallHoldReceived(ImsCall.this); |
| } catch (Throwable t) { |
| loge("callSessionHoldReceived :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionResumed :: not supported for transient conference session=" + |
| session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionResumed :: session=" + session + ", profile=" + profile); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| mCallProfile = profile; |
| mUpdateRequest = UPDATE_NONE; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallResumed(ImsCall.this); |
| } catch (Throwable t) { |
| loge("callSessionResumed :: ", t); |
| } |
| } |
| |
| if (mCallGroup != null) { |
| notifyConferenceStateUpdatedThroughGroupOwner(UPDATE_RESUME); |
| } |
| } |
| |
| @Override |
| public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionResumeFailed :: not supported for transient conference session=" + |
| session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionResumeFailed :: session=" + session + |
| ", reasonInfo=" + reasonInfo); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| mUpdateRequest = UPDATE_NONE; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallResumeFailed(ImsCall.this, reasonInfo); |
| } catch (Throwable t) { |
| loge("callSessionResumeFailed :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionResumeReceived :: not supported for transient conference session=" + |
| session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionResumeReceived :: session=" + session + |
| ", profile=" + profile); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| mCallProfile = profile; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallResumeReceived(ImsCall.this); |
| } catch (Throwable t) { |
| loge("callSessionResumeReceived :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionMergeStarted(ImsCallSession session, |
| ImsCallSession newSession, ImsCallProfile profile) { |
| if (DBG) { |
| String sessionString = session == null ? "null" : session.toString(); |
| String newSessionString = newSession == null ? "null" : newSession.toString(); |
| log("callSessionMergeStarted :: session=" + sessionString |
| + ", newSession=" + newSessionString + ", profile=" + profile); |
| } |
| |
| if (mUpdateRequest != UPDATE_MERGE) { |
| // Odd, we are not in the midst of merging anything. |
| if (DBG) { |
| log("callSessionMergeStarted :: no merge in progress."); |
| } |
| return; |
| } |
| |
| // There are 2 ways that we can go here. If the session that supplied the params |
| // is not null, then it is the new session that represents the new conference |
| // if the merge succeeds. If it is null, the merge is happening on our current |
| // ImsCallSession. |
| if (session == null) { |
| // Everything is already set up and we just need to make sure |
| // that we properly respond to all the future callbacks about |
| // this merge. |
| if (DBG) { |
| log("callSessionMergeStarted :: merging into existing ImsCallSession"); |
| } |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionMergeStarted :: setting our transient ImsCallSession"); |
| } |
| |
| // If we are here, this means that we are creating a new conference and |
| // we need to do some extra work around managing a new ImsCallSession that |
| // could represent our new ImsCallSession if the merge succeeds. |
| synchronized(ImsCall.this) { |
| // Keep track of this session for future callbacks to indicate success |
| // or failure of this merge. |
| mTransientConferenceSession = newSession; |
| mTransientConferenceSession.setListener(createCallSessionListener()); |
| } |
| |
| return; |
| } |
| |
| @Override |
| public void callSessionMergeComplete(ImsCallSession session) { |
| if (DBG) { |
| String sessionString = session == null ? "null" : session.toString(); |
| log("callSessionMergeComplete :: session=" + sessionString); |
| } |
| if (mUpdateRequest != UPDATE_MERGE) { |
| // Odd, we are not in the midst of merging anything. |
| if (DBG) { |
| log("callSessionMergeComplete :: no merge in progress."); |
| } |
| return; |
| } |
| // Let's let our parent ImsCall now that we received notification that |
| // the merge was completed so we can set up our internal state properly |
| processMergeComplete(); |
| } |
| |
| @Override |
| public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { |
| if (DBG) { |
| String sessionString = session == null? "null" : session.toString(); |
| String reasonInfoString = reasonInfo == null ? "null" : reasonInfo.toString(); |
| log("callSessionMergeFailed :: session=" + sessionString + |
| ", reasonInfo=" + reasonInfoString); |
| } |
| if (mUpdateRequest != UPDATE_MERGE) { |
| // Odd, we are not in the midst of merging anything. |
| if (DBG) { |
| log("callSessionMergeFailed :: no merge in progress."); |
| } |
| return; |
| } |
| // Let's tell our parent ImsCall that the merge has failed and we need to clean |
| // up any temporary, transient state. |
| processMergeFailed(reasonInfo); |
| } |
| |
| @Override |
| public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionUpdated :: not supported for transient conference session=" + |
| session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionUpdated :: session=" + session + ", profile=" + profile); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| mCallProfile = profile; |
| mUpdateRequest = UPDATE_NONE; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallUpdated(ImsCall.this); |
| } catch (Throwable t) { |
| loge("callSessionUpdated :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionUpdateFailed :: not supported for transient conference session=" + |
| session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionUpdateFailed :: session=" + session + |
| ", reasonInfo=" + reasonInfo); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| mUpdateRequest = UPDATE_NONE; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallUpdateFailed(ImsCall.this, reasonInfo); |
| } catch (Throwable t) { |
| loge("callSessionUpdateFailed :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionUpdateReceived :: not supported for transient conference " + |
| "session=" + session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionUpdateReceived :: session=" + session + |
| ", profile=" + profile); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| mProposedCallProfile = profile; |
| mUpdateRequest = UPDATE_UNSPECIFIED; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallUpdateReceived(ImsCall.this); |
| } catch (Throwable t) { |
| loge("callSessionUpdateReceived :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession, |
| ImsCallProfile profile) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionConferenceExtended :: not supported for transient conference " + |
| "session=" + session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionConferenceExtended :: session=" + session |
| + ", newSession=" + newSession + ", profile=" + profile); |
| } |
| |
| ImsCall newCall = createNewCall(newSession, profile); |
| |
| if (newCall == null) { |
| callSessionConferenceExtendFailed(session, new ImsReasonInfo()); |
| return; |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| mUpdateRequest = UPDATE_NONE; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallConferenceExtended(ImsCall.this, newCall); |
| } catch (Throwable t) { |
| loge("callSessionConferenceExtended :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionConferenceExtendFailed(ImsCallSession session, |
| ImsReasonInfo reasonInfo) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionConferenceExtendFailed :: not supported for transient " + |
| "conference session=" + session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionConferenceExtendFailed :: session=" + session + |
| ", reasonInfo=" + reasonInfo); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| mUpdateRequest = UPDATE_NONE; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo); |
| } catch (Throwable t) { |
| loge("callSessionConferenceExtendFailed :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionConferenceExtendReceived(ImsCallSession session, |
| ImsCallSession newSession, ImsCallProfile profile) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionConferenceExtendReceived :: not supported for transient " + |
| "conference session=" + session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionConferenceExtendReceived :: session=" + session |
| + ", newSession=" + newSession + ", profile=" + profile); |
| } |
| |
| ImsCall newCall = createNewCall(newSession, profile); |
| |
| if (newCall == null) { |
| // Should all the calls be terminated...??? |
| return; |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallConferenceExtendReceived(ImsCall.this, newCall); |
| } catch (Throwable t) { |
| loge("callSessionConferenceExtendReceived :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionInviteParticipantsRequestDelivered :: not supported for " + |
| "conference session=" + session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionInviteParticipantsRequestDelivered :: session=" + session); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallInviteParticipantsRequestDelivered(ImsCall.this); |
| } catch (Throwable t) { |
| loge("callSessionInviteParticipantsRequestDelivered :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionInviteParticipantsRequestFailed(ImsCallSession session, |
| ImsReasonInfo reasonInfo) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionInviteParticipantsRequestFailed :: not supported for " + |
| "conference session=" + session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionInviteParticipantsRequestFailed :: session=" + session |
| + ", reasonInfo=" + reasonInfo); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo); |
| } catch (Throwable t) { |
| loge("callSessionInviteParticipantsRequestFailed :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionRemoveParticipantsRequestDelivered :: not supported for " + |
| "conference session=" + session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionRemoveParticipantsRequestDelivered :: session=" + session); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this); |
| } catch (Throwable t) { |
| loge("callSessionRemoveParticipantsRequestDelivered :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session, |
| ImsReasonInfo reasonInfo) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionRemoveParticipantsRequestFailed :: not supported for " + |
| "conference session=" +session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionRemoveParticipantsRequestFailed :: session=" + session |
| + ", reasonInfo=" + reasonInfo); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo); |
| } catch (Throwable t) { |
| loge("callSessionRemoveParticipantsRequestFailed :: ", t); |
| } |
| } |
| } |
| |
| @Override |
| public void callSessionConferenceStateUpdated(ImsCallSession session, |
| ImsConferenceState state) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionConferenceStateUpdated :: not supported for transient " + |
| "conference session=" + session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionConferenceStateUpdated :: session=" + session |
| + ", state=" + state); |
| } |
| |
| conferenceStateUpdated(state); |
| } |
| |
| @Override |
| public void callSessionUssdMessageReceived(ImsCallSession session, int mode, |
| String ussdMessage) { |
| if (isTransientConferenceSession(session)) { |
| log("callSessionUssdMessageReceived :: not supported for transient " + |
| "conference session=" + session); |
| return; |
| } |
| |
| if (DBG) { |
| log("callSessionUssdMessageReceived :: session=" + session |
| + ", mode=" + mode + ", ussdMessage=" + ussdMessage); |
| } |
| |
| ImsCall.Listener listener; |
| |
| synchronized(ImsCall.this) { |
| listener = mListener; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage); |
| } catch (Throwable t) { |
| loge("callSessionUssdMessageReceived :: ", t); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Report a new conference state to the current {@link ImsCall} and inform listeners of the |
| * change. Marked as {@code VisibleForTesting} so that the |
| * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference |
| * event package into a regular ongoing IMS call. |
| * |
| * @param state The {@link ImsConferenceState}. |
| */ |
| @VisibleForTesting |
| public void conferenceStateUpdated(ImsConferenceState state) { |
| Listener listener; |
| |
| synchronized(this) { |
| notifyConferenceStateUpdated(state); |
| listener = mListener; |
| } |
| |
| if (listener != null) { |
| try { |
| listener.onCallConferenceStateUpdated(this, state); |
| } catch (Throwable t) { |
| loge("callSessionConferenceStateUpdated :: ", t); |
| } |
| } |
| } |
| } |