/* * Copyright (C) 2017 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.stub; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Bundle; import android.os.Message; import android.os.RemoteException; import android.telephony.ims.ImsCallProfile; import android.telephony.ims.ImsCallSession; import android.telephony.ims.ImsCallSessionListener; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ImsStreamMediaProfile; import android.telephony.ims.ImsVideoCallProvider; import android.telephony.ims.RtpHeaderExtension; import android.telephony.ims.RtpHeaderExtensionType; import android.telephony.ims.aidl.IImsCallSessionListener; 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.util.TelephonyUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.function.Supplier; /** * Base implementation of IImsCallSession, which implements stub versions of the methods available. * * Override the methods that your implementation of ImsCallSession supports. * * @hide */ @SystemApi // DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you // will break other implementations of ImsCallSession maintained by other ImsServices. public class ImsCallSessionImplBase implements AutoCloseable { private static final String LOG_TAG = "ImsCallSessionImplBase"; /** * Notify USSD Mode. */ public static final int USSD_MODE_NOTIFY = 0; /** * Request USSD Mode */ public static final int USSD_MODE_REQUEST = 1; /** @hide */ @IntDef( prefix = "MEDIA_STREAM_TYPE_", value = { MEDIA_STREAM_TYPE_AUDIO, MEDIA_STREAM_TYPE_VIDEO }) @Retention(RetentionPolicy.SOURCE) public @interface MediaStreamType {} /** * Media Stream Type - Audio * @hide */ public static final int MEDIA_STREAM_TYPE_AUDIO = 1; /** * Media Stream Type - Video * @hide */ public static final int MEDIA_STREAM_TYPE_VIDEO = 2; /** @hide */ @IntDef( prefix = "MEDIA_STREAM_DIRECTION_", value = { MEDIA_STREAM_DIRECTION_UPLINK, MEDIA_STREAM_DIRECTION_DOWNLINK }) @Retention(RetentionPolicy.SOURCE) public @interface MediaStreamDirection {} /** * Media Stream Direction - Uplink * @hide */ public static final int MEDIA_STREAM_DIRECTION_UPLINK = 1; /** * Media Stream Direction - Downlink * @hide */ public static final int MEDIA_STREAM_DIRECTION_DOWNLINK = 2; /** * Defines IMS call session state. */ 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"; } } /** * @hide */ private State() { } } private Executor mExecutor = Runnable::run; // Non-final for injection by tests private IImsCallSession mServiceImpl = new IImsCallSession.Stub() { @Override public void close() { executeMethodAsync(() -> ImsCallSessionImplBase.this.close(), "close"); } @Override public String getCallId() { return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.getCallId(), "getCallId"); } @Override public ImsCallProfile getCallProfile() { return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.getCallProfile(), "getCallProfile"); } @Override public ImsCallProfile getLocalCallProfile() { return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this .getLocalCallProfile(), "getLocalCallProfile"); } @Override public ImsCallProfile getRemoteCallProfile() { return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this .getRemoteCallProfile(), "getRemoteCallProfile"); } @Override public String getProperty(String name) { return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.getProperty(name), "getProperty"); } @Override public int getState() { return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.getState(), "getState"); } @Override public boolean isInCall() { return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.isInCall(), "isInCall"); } @Override public void setListener(IImsCallSessionListener listener) { ImsCallSessionListener iCSL = new ImsCallSessionListener(listener); iCSL.setDefaultExecutor(mExecutor); executeMethodAsync(() -> ImsCallSessionImplBase.this.setListener(iCSL), "setListener"); } @Override public void setMute(boolean muted) { executeMethodAsync(() -> ImsCallSessionImplBase.this.setMute(muted), "setMute"); } @Override public void start(String callee, ImsCallProfile profile) { executeMethodAsync(() -> ImsCallSessionImplBase.this.start(callee, profile), "start"); } @Override public void startConference(String[] participants, ImsCallProfile profile) throws RemoteException { executeMethodAsync(() -> ImsCallSessionImplBase.this.startConference(participants, profile), "startConference"); } @Override public void accept(int callType, ImsStreamMediaProfile profile) { executeMethodAsync(() -> ImsCallSessionImplBase.this.accept(callType, profile), "accept"); } @Override public void deflect(String deflectNumber) { executeMethodAsync(() -> ImsCallSessionImplBase.this.deflect(deflectNumber), "deflect"); } @Override public void reject(int reason) { executeMethodAsync(() -> ImsCallSessionImplBase.this.reject(reason), "reject"); } @Override public void transfer(@NonNull String number, boolean isConfirmationRequired) { executeMethodAsync(() -> ImsCallSessionImplBase.this.transfer(number, isConfirmationRequired), "transfer"); } @Override public void consultativeTransfer(@NonNull IImsCallSession transferToSession) { executeMethodAsync(() -> { ImsCallSessionImplBase otherSession = new ImsCallSessionImplBase(); otherSession.setServiceImpl(transferToSession); ImsCallSessionImplBase.this.transfer(otherSession); }, "consultativeTransfer"); } @Override public void terminate(int reason) { executeMethodAsync(() -> ImsCallSessionImplBase.this.terminate(reason), "terminate"); } @Override public void hold(ImsStreamMediaProfile profile) { executeMethodAsync(() -> ImsCallSessionImplBase.this.hold(profile), "hold"); } @Override public void resume(ImsStreamMediaProfile profile) { executeMethodAsync(() -> ImsCallSessionImplBase.this.resume(profile), "resume"); } @Override public void merge() { executeMethodAsync(() -> ImsCallSessionImplBase.this.merge(), "merge"); } @Override public void update(int callType, ImsStreamMediaProfile profile) { executeMethodAsync(() -> ImsCallSessionImplBase.this.update(callType, profile), "update"); } @Override public void extendToConference(String[] participants) { executeMethodAsync(() -> ImsCallSessionImplBase.this.extendToConference(participants), "extendToConference"); } @Override public void inviteParticipants(String[] participants) { executeMethodAsync(() -> ImsCallSessionImplBase.this.inviteParticipants(participants), "inviteParticipants"); } @Override public void removeParticipants(String[] participants) { executeMethodAsync(() -> ImsCallSessionImplBase.this.removeParticipants(participants), "removeParticipants"); } @Override public void sendDtmf(char c, Message result) { executeMethodAsync(() -> ImsCallSessionImplBase.this.sendDtmf(c, result), "sendDtmf"); } @Override public void startDtmf(char c) { executeMethodAsync(() -> ImsCallSessionImplBase.this.startDtmf(c), "startDtmf"); } @Override public void stopDtmf() { executeMethodAsync(() -> ImsCallSessionImplBase.this.stopDtmf(), "stopDtmf"); } @Override public void sendUssd(String ussdMessage) { executeMethodAsync(() -> ImsCallSessionImplBase.this.sendUssd(ussdMessage), "sendUssd"); } @Override public IImsVideoCallProvider getVideoCallProvider() { return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this .getVideoCallProvider(), "getVideoCallProvider"); } @Override public boolean isMultiparty() { return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.isMultiparty(), "isMultiparty"); } @Override public void sendRttModifyRequest(ImsCallProfile toProfile) { executeMethodAsync(() -> ImsCallSessionImplBase.this.sendRttModifyRequest(toProfile), "sendRttModifyRequest"); } @Override public void sendRttModifyResponse(boolean status) { executeMethodAsync(() -> ImsCallSessionImplBase.this.sendRttModifyResponse(status), "sendRttModifyResponse"); } @Override public void sendRttMessage(String rttMessage) { executeMethodAsync(() -> ImsCallSessionImplBase.this.sendRttMessage(rttMessage), "sendRttMessage"); } @Override public void sendRtpHeaderExtensions(@NonNull List extensions) { executeMethodAsync(() -> ImsCallSessionImplBase.this.sendRtpHeaderExtensions( new ArraySet(extensions)), "sendRtpHeaderExtensions"); } @Override public void callSessionNotifyAnbr(int mediaType, int direction, int bitsPerSecond) { executeMethodAsync(() -> ImsCallSessionImplBase.this.callSessionNotifyAnbr( mediaType, direction, bitsPerSecond), "callSessionNotifyAnbr"); } // Call the methods with a clean calling identity on the executor and wait indefinitely for // the future to return. private void executeMethodAsync(Runnable r, String errorLogName) { try { CompletableFuture.runAsync( () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join(); } catch (CancellationException | CompletionException e) { Log.w(LOG_TAG, "ImsCallSessionImplBase Binder - " + errorLogName + " exception: " + e.getMessage()); } } private T executeMethodAsyncForResult(Supplier r, String errorLogName) { CompletableFuture future = CompletableFuture.supplyAsync( () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor); try { return future.get(); } catch (ExecutionException | InterruptedException e) { Log.w(LOG_TAG, "ImsCallSessionImplBase Binder - " + errorLogName + " exception: " + e.getMessage()); return null; } } }; /** * @hide */ public final void setListener(IImsCallSessionListener listener) throws RemoteException { setListener(new ImsCallSessionListener(listener)); } /** * Sets the listener to listen to the session events. An {@link ImsCallSession} * can only hold one listener at a time. Subsequent calls to this method * override the previous listener. * * @param listener {@link ImsCallSessionListener} used to notify the framework of updates * to the ImsCallSession * @deprecated use {@link android.telephony.ims.feature.MmTelFeature#notifyIncomingCall( * ImsCallSessionImplBase, String, Bundle)} to get the listener instead */ @Deprecated public void setListener(ImsCallSessionListener listener) { } /** * Closes the object. This {@link ImsCallSessionImplBase} is not usable after being closed. */ @Override public void close() { } /** * @return A String containing the unique call ID of this {@link ImsCallSessionImplBase}. */ public String getCallId() { return null; } /** * @return The {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is associated * with. */ public ImsCallProfile getCallProfile() { return null; } /** * @return The local {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is * associated with. */ public ImsCallProfile getLocalCallProfile() { return null; } /** * @return The remote {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is * associated with. */ public ImsCallProfile getRemoteCallProfile() { return null; } /** * @param name The String extra key. * @return The string extra value associated with the specified property. */ public String getProperty(String name) { return null; } /** * @return The {@link ImsCallSessionImplBase} state, defined in * {@link ImsCallSessionImplBase.State}. */ public int getState() { return ImsCallSessionImplBase.State.INVALID; } /** * @return true if the {@link ImsCallSessionImplBase} is in a call, false otherwise. */ public boolean isInCall() { return false; } /** * Mutes or unmutes the mic for the active call. * * @param muted true if the call should be muted, false otherwise. */ public void setMute(boolean muted) { } /** * Initiates an IMS call with the specified number and call profile. * The session listener set in {@link #setListener(ImsCallSessionListener)} is called back upon * defined session events. * Only valid to call when the session state is in * {@link ImsCallSession.State#IDLE}. * * @param callee dialed string to make the call to * @param profile call profile to make the call with the specified service type, * call type and media information * @see {@link ImsCallSession.Listener#callSessionStarted}, * {@link ImsCallSession.Listener#callSessionStartFailed} */ public void start(String callee, ImsCallProfile profile) { } /** * Initiates an IMS call with the specified participants and call profile. * The session listener set in {@link #setListener(ImsCallSessionListener)} 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 * @param profile call profile to make the call with the specified service type, * call type and media information * @see {@link ImsCallSession.Listener#callSessionStarted}, * {@link ImsCallSession.Listener#callSessionStartFailed} */ public void startConference(String[] participants, ImsCallProfile profile) { } /** * 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 {@link ImsCallSession.Listener#callSessionStarted} */ public void accept(int callType, ImsStreamMediaProfile profile) { } /** * Deflects an incoming call. * * @param deflectNumber number to deflect the call */ public void deflect(String deflectNumber) { } /** * Rejects an incoming call or session update. * * @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}. * The {@link android.telecom.InCallService} (dialer app) can use the * {@link android.telecom.Call#reject(int)} API to reject a call while specifying * a user-indicated reason for rejecting the call. * Normal call declines ({@link android.telecom.Call#REJECT_REASON_DECLINED}) will * map to {@link ImsReasonInfo#CODE_USER_DECLINE}. * Unwanted calls ({@link android.telecom.Call#REJECT_REASON_UNWANTED}) will map * to {@link ImsReasonInfo#CODE_SIP_USER_MARKED_UNWANTED}. * {@link ImsCallSession.Listener#callSessionStartFailed} */ public void reject(int reason) { } /** * Transfer an established call to given number * * @param number number to transfer the call * @param isConfirmationRequired if {@code True}, indicates a confirmed transfer, * if {@code False} it indicates an unconfirmed transfer. * @hide */ public void transfer(@NonNull String number, boolean isConfirmationRequired) { } /** * Transfer an established call to another call session * * @param otherSession The other ImsCallSession to transfer the ongoing session to. * @hide */ public void transfer(@NonNull ImsCallSessionImplBase otherSession) { } /** * Terminates a call. * * @param reason reason code to terminate a call, defined in {@link ImsReasonInfo}. * * @see {@link ImsCallSession.Listener#callSessionTerminated} */ public void terminate(int reason) { } /** * Puts a call on hold. When it succeeds, {@link ImsCallSession.Listener#callSessionHeld} is * called. * * @param profile stream media profile {@link ImsStreamMediaProfile} to hold the call * @see {@link ImsCallSession.Listener#callSessionHeld}, * {@link ImsCallSession.Listener#callSessionHoldFailed} */ public void hold(ImsStreamMediaProfile profile) { } /** * Continues a call that's on hold. When it succeeds, * {@link ImsCallSession.Listener#callSessionResumed} is called. * * @param profile stream media profile with {@link ImsStreamMediaProfile} to resume the call * @see {@link ImsCallSession.Listener#callSessionResumed}, * {@link ImsCallSession.Listener#callSessionResumeFailed} */ public void resume(ImsStreamMediaProfile profile) { } /** * Merges the active and held call. When the merge starts, * {@link ImsCallSession.Listener#callSessionMergeStarted} is called. * {@link ImsCallSession.Listener#callSessionMergeComplete} is called if the merge is * successful, and {@link ImsCallSession.Listener#callSessionMergeFailed} is called if the merge * fails. * * @see {@link ImsCallSession.Listener#callSessionMergeStarted}, * {@link ImsCallSession.Listener#callSessionMergeComplete}, * {@link ImsCallSession.Listener#callSessionMergeFailed} */ public void merge() { } /** * 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 {@link ImsCallSession.Listener#callSessionUpdated}, * {@link ImsCallSession.Listener#callSessionUpdateFailed} */ public void update(int callType, ImsStreamMediaProfile profile) { } /** * Extends this call to the conference call with the specified recipients. * * @param participants participant list to be invited to the conference call after extending the * call * @see {@link ImsCallSession.Listener#callSessionConferenceExtended}, * {@link ImsCallSession.Listener#callSessionConferenceExtendFailed} */ public void extendToConference(String[] participants) { } /** * Requests the conference server to invite an additional participants to the conference. * * @param participants participant list to be invited to the conference call * @see {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestDelivered}, * {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestFailed} */ public void inviteParticipants(String[] participants) { } /** * Requests the conference server to remove the specified participants from the conference. * * @param participants participant list to be removed from the conference call * @see {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestDelivered}, * {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestFailed} */ public void removeParticipants(String[] participants) { } /** * 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. * @param result If non-null, the {@link Message} to send when the operation is complete. This * is done by using the associated {@link android.os.Messenger} in * {@link Message#replyTo}. For example: * {@code * // Send DTMF and other operations... * try { * // Notify framework that the DTMF was sent. * Messenger dtmfMessenger = result.replyTo; * if (dtmfMessenger != null) { * dtmfMessenger.send(result); * } * } catch (RemoteException e) { * // Remote side is dead * } * } */ public void sendDtmf(char c, Message result) { } /** * Start 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) { } /** * Stop a DTMF code. */ public void stopDtmf() { } /** * Sends an USSD message. * * @param ussdMessage USSD message to send */ public void sendUssd(String ussdMessage) { } /** * See {@link #getImsVideoCallProvider()}, used directly in older ImsService implementations. * @hide */ public IImsVideoCallProvider getVideoCallProvider() { ImsVideoCallProvider provider = getImsVideoCallProvider(); return provider != null ? provider.getInterface() : null; } /** * @return The {@link ImsVideoCallProvider} implementation contained within the IMS service * process. */ public ImsVideoCallProvider getImsVideoCallProvider() { return null; } /** * Determines if the current session is multiparty. * @return {@code True} if the session is multiparty. */ public boolean isMultiparty() { return false; } /** * Device issues RTT modify request * @param toProfile The profile with requested changes made */ public void sendRttModifyRequest(ImsCallProfile toProfile) { } /** * Device responds to Remote RTT modify request * @param status true if the the request was accepted or false of the request is defined. */ public void sendRttModifyResponse(boolean status) { } /** * Device sends RTT message * @param rttMessage RTT message to be sent */ public void sendRttMessage(String rttMessage) { } /** * Device 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 RTP header extensions to be included in the next RTP header. */ public void sendRtpHeaderExtensions(@NonNull Set rtpHeaderExtensions) { } /** * 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). * @hide */ public void callSessionNotifyAnbr(@MediaStreamType int mediaType, @MediaStreamDirection int direction, @IntRange(from = 0) int bitsPerSecond) { // Base Implementation - Should be overridden by IMS service Log.i(LOG_TAG, "ImsCallSessionImplBase callSessionNotifyAnbr - mediaType: " + mediaType); } /** @hide */ public IImsCallSession getServiceImpl() { return mServiceImpl; } /** @hide */ public void setServiceImpl(IImsCallSession serviceImpl) { mServiceImpl = serviceImpl; } /** * Set default Executor from MmTelFeature. * @param executor The default executor for the framework to use when executing the methods * overridden by the implementation of ImsCallSession. * @hide */ public final void setDefaultExecutor(@NonNull Executor executor) { mExecutor = executor; } }