/* * Copyright (C) 2018 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 android.telephony.ims; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; import android.os.Message; import android.os.RemoteException; import android.telephony.CallQuality; import android.telephony.ims.aidl.IImsCallSessionListener; import android.telephony.ims.stub.ImsCallSessionImplBase; import android.util.ArraySet; import android.util.Log; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsVideoCallProvider; import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.util.TelephonyUtils; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; /** * Provides the call initiation/termination, and media exchange between two IMS endpoints. * It directly communicates with IMS service which implements the IMS protocol behavior. * * @hide */ public class ImsCallSession { private static final String TAG = "ImsCallSession"; /** * Defines IMS call session state. Please use * {@link android.telephony.ims.stub.ImsCallSessionImplBase.State} definition. * This is kept around for capability reasons. */ public static class State { public static final int IDLE = 0; public static final int INITIATED = 1; public static final int NEGOTIATING = 2; public static final int ESTABLISHING = 3; public static final int ESTABLISHED = 4; public static final int RENEGOTIATING = 5; public static final int REESTABLISHING = 6; public static final int TERMINATING = 7; public static final int TERMINATED = 8; public static final int INVALID = (-1); /** * Converts the state to string. */ public static String toString(int state) { switch (state) { case IDLE: return "IDLE"; case INITIATED: return "INITIATED"; case NEGOTIATING: return "NEGOTIATING"; case ESTABLISHING: return "ESTABLISHING"; case ESTABLISHED: return "ESTABLISHED"; case RENEGOTIATING: return "RENEGOTIATING"; case REESTABLISHING: return "REESTABLISHING"; case TERMINATING: return "TERMINATING"; case TERMINATED: return "TERMINATED"; default: return "UNKNOWN"; } } private State() { } } /** * Listener for events relating to an IMS session, such as when a session is being * recieved ("on ringing") or a call is outgoing ("on calling"). *

Many of these events are also received by {@link ImsCall.Listener}.

*/ public static class Listener { /** * Called when the session is initiating. * * see: {@link ImsCallSessionListener#callSessionInitiating(ImsCallProfile)} */ public void callSessionInitiating(ImsCallSession session, ImsCallProfile profile) { // no-op } /** * Called when the session failed before initiating was called. * * see: {@link ImsCallSessionListener#callSessionInitiatingFailed(ImsReasonInfo)} */ public void callSessionInitiatingFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { // no-op } /** * Called when the session is progressing. * * see: {@link ImsCallSessionListener#callSessionProgressing(ImsStreamMediaProfile)} */ public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) { // no-op } /** * Called when the session is established. * * @param session the session object that carries out the IMS session */ public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) { // no-op } /** * Called when the session establishment is failed. * * @param session the session object that carries out the IMS session * @param reasonInfo detailed reason of the session establishment failure */ public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { } /** * Called when the session is terminated. * * @param session the session object that carries out the IMS session * @param reasonInfo detailed reason of the session termination */ public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) { } /** * Called when the session is in hold. * * @param session the session object that carries out the IMS session */ public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) { } /** * Called when the session hold is failed. * * @param session the session object that carries out the IMS session * @param reasonInfo detailed reason of the session hold failure */ public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { } /** * Called when the session hold is received from the remote user. * * @param session the session object that carries out the IMS session */ public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) { } /** * Called when the session resume is done. * * @param session the session object that carries out the IMS session */ public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) { } /** * Called when the session resume is failed. * * @param session the session object that carries out the IMS session * @param reasonInfo detailed reason of the session resume failure */ public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { } /** * Called when the session resume is received from the remote user. * * @param session the session object that carries out the IMS session */ public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) { } /** * Called when the session merge has been started. At this point, the {@code newSession} * represents the session which has been initiated to the IMS conference server for the * new merged conference. * * @param session the session object that carries out the IMS session * @param newSession the session object that is merged with an active & hold session */ public void callSessionMergeStarted(ImsCallSession session, ImsCallSession newSession, ImsCallProfile profile) { } /** * Called when the session merge is successful and the merged session is active. * * @param session the session object that carries out the IMS session */ public void callSessionMergeComplete(ImsCallSession session) { } /** * Called when the session merge has failed. * * @param session the session object that carries out the IMS session * @param reasonInfo detailed reason of the call merge failure */ public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { } /** * Called when the session is updated (except for hold/unhold). * * @param session the session object that carries out the IMS session */ public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) { } /** * Called when the session update is failed. * * @param session the session object that carries out the IMS session * @param reasonInfo detailed reason of the session update failure */ public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { } /** * Called when the session update is received from the remote user. * * @param session the session object that carries out the IMS session */ public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) { // no-op } /** * Called when the session is extended to the conference session. * * @param session the session object that carries out the IMS session * @param newSession the session object that is extended to the conference * from the active session */ public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession, ImsCallProfile profile) { } /** * Called when the conference extension is failed. * * @param session the session object that carries out the IMS session * @param reasonInfo detailed reason of the conference extension failure */ public void callSessionConferenceExtendFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { } /** * Called when the conference extension is received from the remote user. * * @param session the session object that carries out the IMS session */ public void callSessionConferenceExtendReceived(ImsCallSession session, ImsCallSession newSession, ImsCallProfile profile) { // no-op } /** * Called when the invitation request of the participants is delivered to the conference * server. * * @param session the session object that carries out the IMS session */ public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) { // no-op } /** * Called when the invitation request of the participants is failed. * * @param session the session object that carries out the IMS session * @param reasonInfo detailed reason of the conference invitation failure */ public void callSessionInviteParticipantsRequestFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { // no-op } /** * Called when the removal request of the participants is delivered to the conference * server. * * @param session the session object that carries out the IMS session */ public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) { // no-op } /** * Called when the removal request of the participants is failed. * * @param session the session object that carries out the IMS session * @param reasonInfo detailed reason of the conference removal failure */ public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session, ImsReasonInfo reasonInfo) { // no-op } /** * Called when the conference state is updated. * * @param session the session object that carries out the IMS session */ public void callSessionConferenceStateUpdated(ImsCallSession session, ImsConferenceState state) { // 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 callSessionUssdMessageReceived(ImsCallSession session, int mode, String ussdMessage) { // no-op } /** * Called when an {@link ImsCallSession} may handover from one network type to another. * For example, the session may handover from WIFI to LTE if conditions are right. *

* If handover is attempted, * {@link #callSessionHandover(ImsCallSession, int, int, ImsReasonInfo)} or * {@link #callSessionHandoverFailed(ImsCallSession, int, int, ImsReasonInfo)} will be * called to indicate the success or failure of the handover. * * @param session IMS session object * @param srcNetworkType original network type * @param targetNetworkType new network type */ public void callSessionMayHandover(ImsCallSession session, int srcNetworkType, int targetNetworkType) { // no-op } /** * Called when session network type changes * * @param session IMS session object * @param srcNetworkType original network type * @param targetNetworkType new network type * @param reasonInfo */ public void callSessionHandover(ImsCallSession session, int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo) { // no-op } /** * Called when session access technology change fails * * @param session IMS session object * @param srcNetworkType original access technology * @param targetNetworkType new access technology * @param reasonInfo handover failure reason */ public void callSessionHandoverFailed(ImsCallSession session, int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo) { // no-op } /** * Called when TTY mode of remote party changed * * @param session IMS session object * @param mode TTY mode of remote party */ public void callSessionTtyModeReceived(ImsCallSession session, int mode) { // no-op } /** * Notifies of a change to the multiparty state for this {@code ImsCallSession}. * * @param session The call session. * @param isMultiParty {@code true} if the session became multiparty, {@code false} * otherwise. */ public void callSessionMultipartyStateChanged(ImsCallSession session, boolean isMultiParty) { // no-op } /** * Called when the session supplementary service is received * * @param session the session object that carries out the IMS session */ public void callSessionSuppServiceReceived(ImsCallSession session, ImsSuppServiceNotification suppServiceInfo) { } /** * Received RTT modify request from Remote Party */ public void callSessionRttModifyRequestReceived(ImsCallSession session, ImsCallProfile callProfile) { // no-op } /** * Received response for RTT modify request */ public void callSessionRttModifyResponseReceived(int status) { // no -op } /** * Device received RTT message from Remote UE */ public void callSessionRttMessageReceived(String rttMessage) { // no-op } /** * While in call, there has been a change in RTT audio indicator. */ public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) { // no-op } /** * Received success response for call transfer request. */ public void callSessionTransferred(@NonNull ImsCallSession session) { // no-op } /** * Received failure response for call transfer request. */ public void callSessionTransferFailed(@NonNull ImsCallSession session, @Nullable ImsReasonInfo reasonInfo) { // no-op } /** * Informs the framework of a DTMF digit which was received from the network. *

* According to RFC 2833 sec 3.10, * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to * 12 ~ 15. * @param digit the DTMF digit */ public void callSessionDtmfReceived(char digit) { // no-op } /** * Called when the IMS service reports a change to the call quality. */ public void callQualityChanged(CallQuality callQuality) { // no-op } /** * Called when the IMS service reports incoming RTP header extension data. */ public void callSessionRtpHeaderExtensionsReceived( @NonNull Set extensions) { // no-op } /** * Called when radio to send ANBRQ message to the access network to query the desired * bitrate. */ public void callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond) { // no-op } } private final IImsCallSession miSession; private boolean mClosed = false; private String mCallId = null; private Listener mListener; private Executor mListenerExecutor = Runnable::run; private IImsCallSessionListenerProxy mIImsCallSessionListenerProxy = null; public ImsCallSession(IImsCallSession iSession) { miSession = iSession; mIImsCallSessionListenerProxy = new IImsCallSessionListenerProxy(); if (iSession != null) { try { iSession.setListener(mIImsCallSessionListenerProxy); } catch (RemoteException e) { if (Flags.ignoreAlreadyTerminatedIncomingCallBeforeRegisteringListener()) { // Registering listener failed, so other operations are not allowed. Log.e(TAG, "ImsCallSession : " + e); mClosed = true; } } } else { mClosed = true; } } public ImsCallSession(IImsCallSession iSession, Listener listener, Executor executor) { this(iSession); setListener(listener, executor); } /** * returns the IImsCallSessionListenerProxy for the ImsCallSession */ public final IImsCallSessionListenerProxy getIImsCallSessionListenerProxy() { return mIImsCallSessionListenerProxy; } /** * Closes this object. This object is not usable after being closed. */ public void close() { synchronized (this) { if (mClosed) { return; } try { miSession.close(); mClosed = true; } catch (RemoteException e) { } } } /** * Gets the call ID of the session. * * @return the call ID * If null is returned for getCallId, then that means that the call ID has not been set yet. */ public String getCallId() { if (mClosed) { return null; } if (mCallId != null) { return mCallId; } else { try { return mCallId = miSession.getCallId(); } catch (RemoteException e) { return null; } } } /** * Sets the call ID of the session. * * @param callId Call ID of the session, which is transferred from * {@link android.telephony.ims.feature.MmTelFeature#notifyIncomingCall( * ImsCallSessionImplBase, String, Bundle)} */ public void setCallId(String callId) { if (callId != null) { mCallId = callId; } } /** * Gets the call profile that this session is associated with * * @return the call profile that this session is associated with */ public ImsCallProfile getCallProfile() { if (mClosed) { return null; } try { return miSession.getCallProfile(); } catch (RemoteException e) { return null; } } /** * Gets the local call profile that this session is associated with * * @return the local call profile that this session is associated with */ public ImsCallProfile getLocalCallProfile() { if (mClosed) { return null; } try { return miSession.getLocalCallProfile(); } catch (RemoteException e) { return null; } } /** * Gets the remote call profile that this session is associated with * * @return the remote call profile that this session is associated with */ public ImsCallProfile getRemoteCallProfile() { if (mClosed) { return null; } try { return miSession.getRemoteCallProfile(); } catch (RemoteException e) { return null; } } /** * Gets the video call provider for the session. * * @return The video call provider. */ public IImsVideoCallProvider getVideoCallProvider() { if (mClosed) { return null; } try { return miSession.getVideoCallProvider(); } catch (RemoteException e) { return null; } } /** * Gets the value associated with the specified property of this session. * * @return the string value associated with the specified property */ public String getProperty(String name) { if (mClosed) { return null; } try { return miSession.getProperty(name); } catch (RemoteException e) { return null; } } /** * Gets the session state. * The value returned must be one of the states in {@link State}. * * @return the session state */ public int getState() { if (mClosed) { return State.INVALID; } try { return miSession.getState(); } catch (RemoteException e) { return State.INVALID; } } /** * Determines if the {@link ImsCallSession} is currently alive (e.g. not in a terminated or * closed state). * * @return {@code True} if the session is alive. */ public boolean isAlive() { if (mClosed) { return false; } int state = getState(); switch (state) { case State.IDLE: case State.INITIATED: case State.NEGOTIATING: case State.ESTABLISHING: case State.ESTABLISHED: case State.RENEGOTIATING: case State.REESTABLISHING: return true; default: return false; } } /** * Gets the native IMS call session. */ public IImsCallSession getSession() { return miSession; } /** * Checks if the session is in call. * * @return true if the session is in call */ public boolean isInCall() { if (mClosed) { return false; } try { return miSession.isInCall(); } catch (RemoteException e) { return false; } } /** * Sets the listener to listen to the session events. A {@link ImsCallSession} * can only hold one listener at a time. Subsequent calls to this method * override the previous listener. * * @param listener to listen to the session events of this object * @param executor an Executor that will execute callbacks */ public void setListener(Listener listener, Executor executor) { mListener = listener; if (executor != null) { mListenerExecutor = executor; } } /** * Mutes or unmutes the mic for the active call. * * @param muted true if the call is muted, false otherwise */ public void setMute(boolean muted) { if (mClosed) { return; } try { miSession.setMute(muted); } catch (RemoteException e) { } } /** * Initiates an IMS call with the specified target and call profile. * The session listener is called back upon defined session events. * The method is only valid to call when the session state is in * {@link ImsCallSession.State#IDLE}. * * @param callee dial string to make the call to. The platform passes the dialed number * entered by the user as-is. The {@link ImsService} should ensure that the * number is formatted in SIP messages appropriately (e.g. using * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}). * @param profile call profile to make the call with the specified service type, * call type and media information * @see Listener#callSessionStarted, Listener#callSessionStartFailed */ public void start(String callee, ImsCallProfile profile) { if (mClosed) { return; } try { miSession.start(callee, profile); } catch (RemoteException e) { } } /** * Initiates an IMS conference call with the specified target and call profile. * The session listener is called back upon defined session events. * The method is only valid to call when the session state is in * {@link ImsCallSession.State#IDLE}. * * @param participants participant list to initiate an IMS conference call. The platform passes * the dialed numbers entered by the user as-is. The {@link ImsService} should * ensure that the number is formatted in SIP messages appropriately (e.g. using * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}). * @param profile call profile to make the call with the specified service type, * call type and media information * @see Listener#callSessionStarted, Listener#callSessionStartFailed */ public void start(String[] participants, ImsCallProfile profile) { if (mClosed) { return; } try { miSession.startConference(participants, profile); } catch (RemoteException e) { } } /** * Accepts an incoming call or session update. * * @param callType call type specified in {@link ImsCallProfile} to be answered * @param profile stream media profile {@link ImsStreamMediaProfile} to be answered * @see Listener#callSessionStarted */ public void accept(int callType, ImsStreamMediaProfile profile) { if (mClosed) { return; } try { miSession.accept(callType, profile); } catch (RemoteException e) { } } /** * Deflects an incoming call. * * @param number number to be deflected to */ public void deflect(String number) { if (mClosed) { return; } try { miSession.deflect(number); } catch (RemoteException e) { } } /** * Rejects an incoming call or session update. * * @param reason reason code to reject an incoming call * @see Listener#callSessionStartFailed */ public void reject(int reason) { if (mClosed) { return; } try { miSession.reject(reason); } catch (RemoteException e) { } } /** * Transfers an ongoing call. * * @param number number to be transferred to. * @param isConfirmationRequired indicates whether confirmation of the transfer is required. */ public void transfer(@NonNull String number, boolean isConfirmationRequired) { if (mClosed) { return; } try { miSession.transfer(number, isConfirmationRequired); } catch (RemoteException e) { } } /** * Transfers a call to another ongoing call. * * @param transferToSession the other ImsCallSession to which this session will be transferred. */ public void transfer(@NonNull ImsCallSession transferToSession) { if (mClosed) { return; } try { if (transferToSession != null) { miSession.consultativeTransfer(transferToSession.getSession()); } } catch (RemoteException e) { } } /** * Terminates a call. * * @see Listener#callSessionTerminated */ public void terminate(int reason) { if (mClosed) { return; } try { miSession.terminate(reason); } catch (RemoteException e) { } } /** * Puts a call on hold. When it succeeds, {@link Listener#callSessionHeld} is called. * * @param profile stream media profile {@link ImsStreamMediaProfile} to hold the call * @see Listener#callSessionHeld, Listener#callSessionHoldFailed */ public void hold(ImsStreamMediaProfile profile) { if (mClosed) { return; } try { miSession.hold(profile); } catch (RemoteException e) { } } /** * Continues a call that's on hold. When it succeeds, * {@link Listener#callSessionResumed} is called. * * @param profile stream media profile {@link ImsStreamMediaProfile} to resume the call * @see Listener#callSessionResumed, Listener#callSessionResumeFailed */ public void resume(ImsStreamMediaProfile profile) { if (mClosed) { return; } try { miSession.resume(profile); } catch (RemoteException e) { } } /** * Merges the active & hold call. When it succeeds, * {@link Listener#callSessionMergeStarted} is called. * * @see Listener#callSessionMergeStarted , Listener#callSessionMergeFailed */ public void merge() { if (mClosed) { return; } try { miSession.merge(); } catch (RemoteException e) { } } /** * Updates the current call's properties (ex. call mode change: video upgrade / downgrade). * * @param callType call type specified in {@link ImsCallProfile} to be updated * @param profile stream media profile {@link ImsStreamMediaProfile} to be updated * @see Listener#callSessionUpdated, Listener#callSessionUpdateFailed */ public void update(int callType, ImsStreamMediaProfile profile) { if (mClosed) { return; } try { miSession.update(callType, profile); } catch (RemoteException e) { } } /** * Extends this call to the conference call with the specified recipients. * * @param participants list to be invited to the conference call after extending the call * @see Listener#callSessionConferenceExtended * @see Listener#callSessionConferenceExtendFailed */ public void extendToConference(String[] participants) { if (mClosed) { return; } try { miSession.extendToConference(participants); } catch (RemoteException e) { } } /** * Requests the conference server to invite an additional participants to the conference. * * @param participants list to be invited to the conference call * @see Listener#callSessionInviteParticipantsRequestDelivered * @see Listener#callSessionInviteParticipantsRequestFailed */ public void inviteParticipants(String[] participants) { if (mClosed) { return; } try { miSession.inviteParticipants(participants); } catch (RemoteException e) { } } /** * Requests the conference server to remove the specified participants from the conference. * * @param participants participant list to be removed from the conference call * @see Listener#callSessionRemoveParticipantsRequestDelivered * @see Listener#callSessionRemoveParticipantsRequestFailed */ public void removeParticipants(String[] participants) { if (mClosed) { return; } try { miSession.removeParticipants(participants); } catch (RemoteException e) { } } /** * Sends a DTMF code. According to RFC 2833, * 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 the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. */ public void sendDtmf(char c, Message result) { if (mClosed) { return; } try { miSession.sendDtmf(c, result); } catch (RemoteException e) { } } /** * Starts a DTMF code. According to RFC 2833, * 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 the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. */ public void startDtmf(char c) { if (mClosed) { return; } try { miSession.startDtmf(c); } catch (RemoteException e) { } } /** * Stops a DTMF code. */ public void stopDtmf() { if (mClosed) { return; } try { miSession.stopDtmf(); } catch (RemoteException e) { } } /** * Sends an USSD message. * * @param ussdMessage USSD message to send */ public void sendUssd(String ussdMessage) { if (mClosed) { return; } try { miSession.sendUssd(ussdMessage); } catch (RemoteException e) { } } /** * Determines if the session is multiparty. * * @return {@code True} if the session is multiparty. */ public boolean isMultiparty() { if (mClosed) { return false; } try { return miSession.isMultiparty(); } catch (RemoteException e) { return false; } } /** * Sends Rtt Message * * @param rttMessage rtt text to be sent */ public void sendRttMessage(String rttMessage) { if (mClosed) { return; } try { miSession.sendRttMessage(rttMessage); } catch (RemoteException e) { } } /** * Sends RTT Upgrade or downgrade request * * @param to Profile with the RTT flag set to the desired value */ public void sendRttModifyRequest(ImsCallProfile to) { if (mClosed) { return; } try { miSession.sendRttModifyRequest(to); } catch (RemoteException e) { } } /** * Sends RTT Upgrade response * * @param response : response for upgrade */ public void sendRttModifyResponse(boolean response) { if (mClosed) { return; } try { miSession.sendRttModifyResponse(response); } catch (RemoteException e) { } } /** * Requests that {@code rtpHeaderExtensions} are sent as a header extension with the next * RTP packet sent by the IMS stack. *

* The {@link RtpHeaderExtensionType}s negotiated during SDP (Session Description Protocol) * signalling determine the {@link RtpHeaderExtension}s which can be sent using this method. * See RFC8285 for more information. *

* By specification, the RTP header extension is an unacknowledged transmission and there is no * guarantee that the header extension will be delivered by the network to the other end of the * call. * @param rtpHeaderExtensions The header extensions to be included in the next RTP header. */ public void sendRtpHeaderExtensions(@NonNull Set rtpHeaderExtensions) { if (mClosed) { return; } try { miSession.sendRtpHeaderExtensions( new ArrayList(rtpHeaderExtensions)); } catch (RemoteException e) { } } /** * Deliver the bitrate for the indicated media type, direction and bitrate to the upper layer. * * @param mediaType MediaType is used to identify media stream such as audio or video. * @param direction Direction of this packet stream (e.g. uplink or downlink). * @param bitsPerSecond This value is the bitrate received from the NW through the Recommended * bitrate MAC Control Element message and ImsStack converts this value from MAC bitrate * to audio/video codec bitrate (defined in TS26.114). */ public void callSessionNotifyAnbr(int mediaType, int direction, int bitsPerSecond) { if (mClosed) { return; } try { miSession.callSessionNotifyAnbr(mediaType, direction, bitsPerSecond); } catch (RemoteException e) { Log.e(TAG, "callSessionNotifyAnbr" + e); } } /** * A listener type for receiving notification on IMS call session events. * When an event is generated for an {@link IImsCallSession}, * the application is notified by having one of the methods called on * the {@link IImsCallSessionListener}. */ private class IImsCallSessionListenerProxy extends IImsCallSessionListener.Stub { /** * Notifies the result of the basic session operation (setup / terminate). */ @Override public void callSessionInitiating(ImsCallProfile profile) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionInitiating(ImsCallSession.this, profile); } }, mListenerExecutor); } @Override public void callSessionProgressing(ImsStreamMediaProfile profile) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionProgressing(ImsCallSession.this, profile); } }, mListenerExecutor); } @Override public void callSessionInitiated(ImsCallProfile profile) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionStarted(ImsCallSession.this, profile); } }, mListenerExecutor); } @Override public void callSessionInitiatingFailed(ImsReasonInfo reasonInfo) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo); } }, mListenerExecutor); } @Override public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo); } }, mListenerExecutor); } @Override public void callSessionTerminated(ImsReasonInfo reasonInfo) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionTerminated(ImsCallSession.this, reasonInfo); } }, mListenerExecutor); } /** * Notifies the result of the call hold/resume operation. */ @Override public void callSessionHeld(ImsCallProfile profile) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionHeld(ImsCallSession.this, profile); } }, mListenerExecutor); } @Override public void callSessionHoldFailed(ImsReasonInfo reasonInfo) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionHoldFailed(ImsCallSession.this, reasonInfo); } }, mListenerExecutor); } @Override public void callSessionHoldReceived(ImsCallProfile profile) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionHoldReceived(ImsCallSession.this, profile); } }, mListenerExecutor); } @Override public void callSessionResumed(ImsCallProfile profile) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionResumed(ImsCallSession.this, profile); } }, mListenerExecutor); } @Override public void callSessionResumeFailed(ImsReasonInfo reasonInfo) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionResumeFailed(ImsCallSession.this, reasonInfo); } }, mListenerExecutor); } @Override public void callSessionResumeReceived(ImsCallProfile profile) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionResumeReceived(ImsCallSession.this, profile); } }, mListenerExecutor); } /** * Notifies the start of a call merge operation. * * @param newSession The merged call session. * @param profile The call profile. */ @Override public void callSessionMergeStarted(IImsCallSession newSession, ImsCallProfile profile) { // This callback can be used for future use to add additional // functionality that may be needed between conference start and complete Log.d(TAG, "callSessionMergeStarted"); } /** * Notifies the successful completion of a call merge operation. * * @param newSession The call session. */ @Override public void callSessionMergeComplete(IImsCallSession newSession) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { if (newSession != null) { // New session created after conference mListener.callSessionMergeComplete(new ImsCallSession(newSession)); } else { // Session already exists. Hence no need to pass mListener.callSessionMergeComplete(null); } } }, mListenerExecutor); } /** * Notifies of a failure to perform a call merge operation. * * @param reasonInfo The merge failure reason. */ @Override public void callSessionMergeFailed(ImsReasonInfo reasonInfo) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo); } }, mListenerExecutor); } /** * Notifies the result of call upgrade / downgrade or any other call updates. */ @Override public void callSessionUpdated(ImsCallProfile profile) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionUpdated(ImsCallSession.this, profile); } }, mListenerExecutor); } @Override public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo); } }, mListenerExecutor); } @Override public void callSessionUpdateReceived(ImsCallProfile profile) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionUpdateReceived(ImsCallSession.this, profile); } }, mListenerExecutor); } /** * Notifies the result of conference extension. */ @Override public void callSessionConferenceExtended(IImsCallSession newSession, ImsCallProfile profile) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionConferenceExtended(ImsCallSession.this, new ImsCallSession(newSession), profile); } }, mListenerExecutor); } @Override public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionConferenceExtendFailed( ImsCallSession.this, reasonInfo); } }, mListenerExecutor); } @Override public void callSessionConferenceExtendReceived(IImsCallSession newSession, ImsCallProfile profile) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionConferenceExtendReceived(ImsCallSession.this, new ImsCallSession(newSession), profile); } }, mListenerExecutor); } /** * Notifies the result of the participant invitation / removal to/from * the conference session. */ @Override public void callSessionInviteParticipantsRequestDelivered() { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionInviteParticipantsRequestDelivered( ImsCallSession.this); } }, mListenerExecutor); } @Override public void callSessionInviteParticipantsRequestFailed(ImsReasonInfo reasonInfo) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this, reasonInfo); } }, mListenerExecutor); } @Override public void callSessionRemoveParticipantsRequestDelivered() { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionRemoveParticipantsRequestDelivered(ImsCallSession.this); } }, mListenerExecutor); } @Override public void callSessionRemoveParticipantsRequestFailed(ImsReasonInfo reasonInfo) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this, reasonInfo); } }, mListenerExecutor); } /** * Notifies the changes of the conference info. in the conference session. */ @Override public void callSessionConferenceStateUpdated(ImsConferenceState state) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state); } }, mListenerExecutor); } /** * Notifies the incoming USSD message. */ @Override public void callSessionUssdMessageReceived(int mode, String ussdMessage) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, ussdMessage); } }, mListenerExecutor); } /** * Notifies of a case where a {@link ImsCallSession} may * potentially handover from one radio technology to another. * @param srcNetworkType The source network type; one of the network type constants defined * in {@link android.telephony.TelephonyManager}. For example * {@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE}. * @param targetNetworkType The target radio access technology; one of the network type * constants defined in {@link android.telephony.TelephonyManager}. * For example * {@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE}. */ @Override public void callSessionMayHandover(int srcNetworkType, int targetNetworkType) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType, targetNetworkType); } }, mListenerExecutor); } /** * Notifies of handover information for this call */ @Override public void callSessionHandover(int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionHandover(ImsCallSession.this, srcNetworkType, targetNetworkType, reasonInfo); } }, mListenerExecutor); } /** * Notifies of handover failure info for this call */ @Override public void callSessionHandoverFailed(int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType, targetNetworkType, reasonInfo); } }, mListenerExecutor); } /** * Notifies the TTY mode received from remote party. */ @Override public void callSessionTtyModeReceived(int mode) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionTtyModeReceived(ImsCallSession.this, mode); } }, mListenerExecutor); } /** * Notifies of a change to the multiparty state for this {@code ImsCallSession}. * * @param isMultiParty {@code true} if the session became multiparty, {@code false} * otherwise. */ public void callSessionMultipartyStateChanged(boolean isMultiParty) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionMultipartyStateChanged(ImsCallSession.this, isMultiParty); } }, mListenerExecutor); } @Override public void callSessionSuppServiceReceived(ImsSuppServiceNotification suppServiceInfo ) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionSuppServiceReceived(ImsCallSession.this, suppServiceInfo); } }, mListenerExecutor); } /** * Received RTT modify request from remote party */ @Override public void callSessionRttModifyRequestReceived(ImsCallProfile callProfile) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionRttModifyRequestReceived(ImsCallSession.this, callProfile); } }, mListenerExecutor); } /** * Received response for RTT modify request */ @Override public void callSessionRttModifyResponseReceived(int status) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionRttModifyResponseReceived(status); } }, mListenerExecutor); } /** * RTT Message received */ @Override public void callSessionRttMessageReceived(String rttMessage) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionRttMessageReceived(rttMessage); } }, mListenerExecutor); } /** * While in call, there has been a change in RTT audio indicator. */ @Override public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionRttAudioIndicatorChanged(profile); } }, mListenerExecutor); } @Override public void callSessionTransferred() { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionTransferred(ImsCallSession.this); } }, mListenerExecutor); } @Override public void callSessionTransferFailed(@Nullable ImsReasonInfo reasonInfo) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo); } }, mListenerExecutor); } /** * DTMF digit received. * @param dtmf The DTMF digit. */ @Override public void callSessionDtmfReceived(char dtmf) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionDtmfReceived(dtmf); } }, mListenerExecutor); } /** * Call quality updated */ @Override public void callQualityChanged(CallQuality callQuality) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callQualityChanged(callQuality); } }, mListenerExecutor); } /** * RTP header extension data received. * @param extensions The header extension data. */ @Override public void callSessionRtpHeaderExtensionsReceived( @NonNull List extensions) { TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionRtpHeaderExtensionsReceived( new ArraySet(extensions)); } }, mListenerExecutor); } /** * ANBR Query received. * * @param mediaType MediaType is used to identify media stream such as audio or video. * @param direction Direction of this packet stream (e.g. uplink or downlink). * @param bitsPerSecond This value is the bitrate requested by the other party UE through * RTP CMR, RTCPAPP or TMMBR, and ImsStack converts this value to the MAC bitrate * (defined in TS36.321, range: 0 ~ 8000 kbit/s). */ @Override public void callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond) { Log.d(TAG, "callSessionSendAnbrQuery in ImsCallSession"); TelephonyUtils.runWithCleanCallingIdentity(()-> { if (mListener != null) { mListener.callSessionSendAnbrQuery(mediaType, direction, bitsPerSecond); } }, mListenerExecutor); } } /** * Provides a string representation of the {@link ImsCallSession}. Primarily intended for * use in log statements. * * @return String representation of session. */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("[ImsCallSession objId:"); sb.append(System.identityHashCode(this)); sb.append(" callId:"); sb.append(mCallId != null ? mCallId : "[UNINITIALIZED]"); sb.append("]"); return sb.toString(); } }