/* * 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.feature; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Binder; import android.os.Bundle; import android.os.Message; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.telecom.TelecomManager; import android.telephony.AccessNetworkConstants; import android.telephony.ims.ImsCallProfile; import android.telephony.ims.ImsCallSession; import android.telephony.ims.ImsCallSessionListener; import android.telephony.ims.ImsException; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ImsService; import android.telephony.ims.MediaQualityStatus; import android.telephony.ims.MediaThreshold; import android.telephony.ims.RtpHeaderExtensionType; import android.telephony.ims.SrvccCall; import android.telephony.ims.aidl.IImsCallSessionListener; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsMmTelFeature; import android.telephony.ims.aidl.IImsMmTelListener; import android.telephony.ims.aidl.IImsSmsListener; import android.telephony.ims.aidl.IImsTrafficSessionCallback; import android.telephony.ims.aidl.ISrvccStartedCallback; import android.telephony.ims.stub.ImsCallSessionImplBase; import android.telephony.ims.stub.ImsEcbmImplBase; import android.telephony.ims.stub.ImsMultiEndpointImplBase; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.ImsSmsImplBase; import android.telephony.ims.stub.ImsUtImplBase; import android.util.ArraySet; import android.util.Log; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsUt; import com.android.internal.telephony.util.TelephonyUtils; import com.android.server.telecom.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.HashMap; 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.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Supplier; /** * Base implementation for Voice and SMS (IR-92) and Video (IR-94) IMS support. * * Any class wishing to use MmTelFeature should extend this class and implement all methods that the * service supports. */ public class MmTelFeature extends ImsFeature { private static final String LOG_TAG = "MmTelFeature"; private Executor mExecutor; private ImsSmsImplBase mSmsImpl; private HashMap mTrafficCallbacks = new HashMap<>(); /** * Creates a new MmTelFeature using the Executor set in {@link ImsService#getExecutor} * @hide */ @SystemApi public MmTelFeature() { } /** * Create a new MmTelFeature using the Executor specified for methods being called by the * framework. * @param executor The executor for the framework to use when executing the methods overridden * by the implementation of MmTelFeature. * @hide */ @SystemApi public MmTelFeature(@NonNull Executor executor) { super(); mExecutor = executor; } private final IImsMmTelFeature mImsMMTelBinder = new IImsMmTelFeature.Stub() { @Override public void setListener(IImsMmTelListener l) { executeMethodAsyncNoException(() -> MmTelFeature.this.setListener(l), "setListener"); } @Override public int getFeatureState() throws RemoteException { return executeMethodAsyncForResult(() -> MmTelFeature.this.getFeatureState(), "getFeatureState"); } @Override public ImsCallProfile createCallProfile(int callSessionType, int callType) throws RemoteException { return executeMethodAsyncForResult(() -> MmTelFeature.this.createCallProfile( callSessionType, callType), "createCallProfile"); } @Override public void changeOfferedRtpHeaderExtensionTypes(List types) throws RemoteException { executeMethodAsync(() -> MmTelFeature.this.changeOfferedRtpHeaderExtensionTypes( new ArraySet<>(types)), "changeOfferedRtpHeaderExtensionTypes"); } @Override public IImsCallSession createCallSession(ImsCallProfile profile) throws RemoteException { AtomicReference exceptionRef = new AtomicReference<>(); IImsCallSession result = executeMethodAsyncForResult(() -> { try { return createCallSessionInterface(profile); } catch (RemoteException e) { exceptionRef.set(e); return null; } }, "createCallSession"); if (exceptionRef.get() != null) { throw exceptionRef.get(); } return result; } @Override public int shouldProcessCall(String[] numbers) { Integer result = executeMethodAsyncForResultNoException(() -> MmTelFeature.this.shouldProcessCall(numbers), "shouldProcessCall"); if (result != null) { return result.intValue(); } else { return PROCESS_CALL_CSFB; } } @Override public IImsUt getUtInterface() throws RemoteException { AtomicReference exceptionRef = new AtomicReference<>(); IImsUt result = executeMethodAsyncForResult(() -> { try { return MmTelFeature.this.getUtInterface(); } catch (RemoteException e) { exceptionRef.set(e); return null; } }, "getUtInterface"); if (exceptionRef.get() != null) { throw exceptionRef.get(); } return result; } @Override public IImsEcbm getEcbmInterface() throws RemoteException { AtomicReference exceptionRef = new AtomicReference<>(); IImsEcbm result = executeMethodAsyncForResult(() -> { try { return MmTelFeature.this.getEcbmInterface(); } catch (RemoteException e) { exceptionRef.set(e); return null; } }, "getEcbmInterface"); if (exceptionRef.get() != null) { throw exceptionRef.get(); } return result; } @Override public void setUiTtyMode(int uiTtyMode, Message onCompleteMessage) throws RemoteException { executeMethodAsync(() -> MmTelFeature.this.setUiTtyMode(uiTtyMode, onCompleteMessage), "setUiTtyMode"); } @Override public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { AtomicReference exceptionRef = new AtomicReference<>(); IImsMultiEndpoint result = executeMethodAsyncForResult(() -> { try { return MmTelFeature.this.getMultiEndpointInterface(); } catch (RemoteException e) { exceptionRef.set(e); return null; } }, "getMultiEndpointInterface"); if (exceptionRef.get() != null) { throw exceptionRef.get(); } return result; } @Override public int queryCapabilityStatus() { Integer result = executeMethodAsyncForResultNoException(() -> MmTelFeature.this .queryCapabilityStatus().mCapabilities, "queryCapabilityStatus"); if (result != null) { return result.intValue(); } else { return 0; } } @Override public void addCapabilityCallback(IImsCapabilityCallback c) { executeMethodAsyncNoException(() -> MmTelFeature.this .addCapabilityCallback(c), "addCapabilityCallback"); } @Override public void removeCapabilityCallback(IImsCapabilityCallback c) { executeMethodAsyncNoException(() -> MmTelFeature.this .removeCapabilityCallback(c), "removeCapabilityCallback"); } @Override public void changeCapabilitiesConfiguration(CapabilityChangeRequest request, IImsCapabilityCallback c) { executeMethodAsyncNoException(() -> MmTelFeature.this .requestChangeEnabledCapabilities(request, c), "changeCapabilitiesConfiguration"); } @Override public void queryCapabilityConfiguration(int capability, int radioTech, IImsCapabilityCallback c) { executeMethodAsyncNoException(() -> queryCapabilityConfigurationInternal( capability, radioTech, c), "queryCapabilityConfiguration"); } @Override public void setMediaQualityThreshold(@MediaQualityStatus.MediaSessionType int sessionType, MediaThreshold mediaThreshold) { if (mediaThreshold != null) { executeMethodAsyncNoException(() -> setMediaThreshold(sessionType, mediaThreshold), "setMediaQualityThreshold"); } else { executeMethodAsyncNoException(() -> clearMediaThreshold(sessionType), "clearMediaQualityThreshold"); } } @Override public MediaQualityStatus queryMediaQualityStatus( @MediaQualityStatus.MediaSessionType int sessionType) throws RemoteException { return executeMethodAsyncForResult(() -> MmTelFeature.this.queryMediaQualityStatus( sessionType), "queryMediaQualityStatus"); } @Override public void setSmsListener(IImsSmsListener l) { executeMethodAsyncNoException(() -> MmTelFeature.this.setSmsListener(l), "setSmsListener", getImsSmsImpl().getExecutor()); } @Override public void sendSms(int token, int messageRef, String format, String smsc, boolean retry, byte[] pdu) { executeMethodAsyncNoException(() -> MmTelFeature.this .sendSms(token, messageRef, format, smsc, retry, pdu), "sendSms", getImsSmsImpl().getExecutor()); } @Override public void onMemoryAvailable(int token) { executeMethodAsyncNoException(() -> MmTelFeature.this .onMemoryAvailable(token), "onMemoryAvailable", getImsSmsImpl().getExecutor()); } @Override public void acknowledgeSms(int token, int messageRef, int result) { executeMethodAsyncNoException(() -> MmTelFeature.this .acknowledgeSms(token, messageRef, result), "acknowledgeSms", getImsSmsImpl().getExecutor()); } @Override public void acknowledgeSmsWithPdu(int token, int messageRef, int result, byte[] pdu) { executeMethodAsyncNoException(() -> MmTelFeature.this .acknowledgeSms(token, messageRef, result, pdu), "acknowledgeSms", getImsSmsImpl().getExecutor()); } @Override public void acknowledgeSmsReport(int token, int messageRef, int result) { executeMethodAsyncNoException(() -> MmTelFeature.this .acknowledgeSmsReport(token, messageRef, result), "acknowledgeSmsReport", getImsSmsImpl().getExecutor()); } @Override public String getSmsFormat() { return executeMethodAsyncForResultNoException(() -> MmTelFeature.this .getSmsFormat(), "getSmsFormat", getImsSmsImpl().getExecutor()); } @Override public void onSmsReady() { executeMethodAsyncNoException(() -> MmTelFeature.this.onSmsReady(), "onSmsReady", getImsSmsImpl().getExecutor()); } @Override public void notifySrvccStarted(final ISrvccStartedCallback cb) { executeMethodAsyncNoException( () -> MmTelFeature.this.notifySrvccStarted( (profiles) -> { try { cb.onSrvccCallNotified(profiles); } catch (Exception e) { Log.e(LOG_TAG, "onSrvccCallNotified e=" + e); } }), "notifySrvccStarted"); } @Override public void notifySrvccCompleted() { executeMethodAsyncNoException( () -> MmTelFeature.this.notifySrvccCompleted(), "notifySrvccCompleted"); } @Override public void notifySrvccFailed() { executeMethodAsyncNoException( () -> MmTelFeature.this.notifySrvccFailed(), "notifySrvccFailed"); } @Override public void notifySrvccCanceled() { executeMethodAsyncNoException( () -> MmTelFeature.this.notifySrvccCanceled(), "notifySrvccCanceled"); } @Override public void setTerminalBasedCallWaitingStatus(boolean enabled) throws RemoteException { synchronized (mLock) { try { MmTelFeature.this.setTerminalBasedCallWaitingStatus(enabled); } catch (ServiceSpecificException se) { throw new ServiceSpecificException(se.errorCode, se.getMessage()); } catch (Exception e) { throw new RemoteException(e.getMessage()); } } } // 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) throws RemoteException { try { CompletableFuture.runAsync( () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join(); } catch (CancellationException | CompletionException e) { Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: " + e.getMessage()); throw new RemoteException(e.getMessage()); } } private void executeMethodAsyncNoException(Runnable r, String errorLogName) { try { CompletableFuture.runAsync( () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join(); } catch (CancellationException | CompletionException e) { Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: " + e.getMessage()); } } private void executeMethodAsyncNoException(Runnable r, String errorLogName, Executor executor) { try { CompletableFuture.runAsync( () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join(); } catch (CancellationException | CompletionException e) { Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: " + e.getMessage()); } } private T executeMethodAsyncForResult(Supplier r, String errorLogName) throws RemoteException { CompletableFuture future = CompletableFuture.supplyAsync( () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor); try { return future.get(); } catch (ExecutionException | InterruptedException e) { Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: " + e.getMessage()); throw new RemoteException(e.getMessage()); } } private T executeMethodAsyncForResultNoException(Supplier r, String errorLogName) { CompletableFuture future = CompletableFuture.supplyAsync( () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor); try { return future.get(); } catch (ExecutionException | InterruptedException e) { Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: " + e.getMessage()); return null; } } private T executeMethodAsyncForResultNoException(Supplier r, String errorLogName, Executor executor) { CompletableFuture future = CompletableFuture.supplyAsync( () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor); try { return future.get(); } catch (ExecutionException | InterruptedException e) { Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: " + e.getMessage()); return null; } } }; /** * Contains the capabilities defined and supported by a MmTelFeature in the form of a Bitmask. * The capabilities that are used in MmTelFeature are defined as * {@link MmTelCapabilities#CAPABILITY_TYPE_VOICE}, * {@link MmTelCapabilities#CAPABILITY_TYPE_VIDEO}, * {@link MmTelCapabilities#CAPABILITY_TYPE_UT}, * {@link MmTelCapabilities#CAPABILITY_TYPE_SMS}, and * {@link MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER}. * * The capabilities of this MmTelFeature will be set by the framework. */ public static class MmTelCapabilities extends Capabilities { /** * Create a new empty {@link MmTelCapabilities} instance. * @see #addCapabilities(int) * @see #removeCapabilities(int) * @hide */ @SystemApi public MmTelCapabilities() { super(); } /**@deprecated Use {@link MmTelCapabilities} to construct a new instance instead. * @hide */ @Deprecated @SystemApi public MmTelCapabilities(Capabilities c) { mCapabilities = c.mCapabilities; } /** * Create a new {link @MmTelCapabilities} instance with the provided capabilities. * @param capabilities The capabilities that are supported for MmTel in the form of a * bitfield. * @hide */ @SystemApi public MmTelCapabilities(@MmTelCapability int capabilities) { super(capabilities); } /** @hide */ @IntDef(flag = true, value = { CAPABILITY_TYPE_VOICE, CAPABILITY_TYPE_VIDEO, CAPABILITY_TYPE_UT, CAPABILITY_TYPE_SMS, CAPABILITY_TYPE_CALL_COMPOSER, CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY }) @Retention(RetentionPolicy.SOURCE) public @interface MmTelCapability {} /** * Undefined capability type for initialization * This is used to check the upper range of MmTel capability * @hide */ public static final int CAPABILITY_TYPE_NONE = 0; /** * This MmTelFeature supports Voice calling (IR.92) */ public static final int CAPABILITY_TYPE_VOICE = 1 << 0; /** * This MmTelFeature supports Video (IR.94) */ public static final int CAPABILITY_TYPE_VIDEO = 1 << 1; /** * This MmTelFeature supports XCAP over Ut for supplementary services. (IR.92) */ public static final int CAPABILITY_TYPE_UT = 1 << 2; /** * This MmTelFeature supports SMS (IR.92) */ public static final int CAPABILITY_TYPE_SMS = 1 << 3; /** * This MmTelFeature supports Call Composer (section 2.4 of RC.20). This is the superset * Call Composer, meaning that all subset types of Call Composers must be enabled when this * capability is enabled */ public static final int CAPABILITY_TYPE_CALL_COMPOSER = 1 << 4; /** * This MmTelFeature supports Business-only Call Composer. This is a subset of * {@code CAPABILITY_TYPE_CALL_COMPOSER} that only supports business related * information for calling (e.g. information to signal if the call is a business call) in * the SIP header. When enabling {@code CAPABILITY_TYPE_CALL_COMPOSER}, the * {@code CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY} capability must also be enabled. */ @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER) public static final int CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY = 1 << 5; /** * This is used to check the upper range of MmTel capability * @hide */ public static final int CAPABILITY_TYPE_MAX = CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY + 1; /** * @hide */ @Override @SystemApi public final void addCapabilities(@MmTelCapability int capabilities) { super.addCapabilities(capabilities); } /** * @hide */ @Override @SystemApi public final void removeCapabilities(@MmTelCapability int capability) { super.removeCapabilities(capability); } /** * @param capabilities a bitmask of one or more capabilities. * * @return true if all queried capabilities are true, otherwise false. */ @Override public final boolean isCapable(@MmTelCapability int capabilities) { return super.isCapable(capabilities); } /** * @hide */ @NonNull @Override public String toString() { StringBuilder builder = new StringBuilder("MmTel Capabilities - ["); builder.append("Voice: "); builder.append(isCapable(CAPABILITY_TYPE_VOICE)); builder.append(" Video: "); builder.append(isCapable(CAPABILITY_TYPE_VIDEO)); builder.append(" UT: "); builder.append(isCapable(CAPABILITY_TYPE_UT)); builder.append(" SMS: "); builder.append(isCapable(CAPABILITY_TYPE_SMS)); builder.append(" CALL_COMPOSER: "); builder.append(isCapable(CAPABILITY_TYPE_CALL_COMPOSER)); builder.append(" BUSINESS_COMPOSER_ONLY: "); builder.append(isCapable(CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY)); builder.append("]"); return builder.toString(); } } /** * Listener that the framework implements for communication from the MmTelFeature. * @hide */ public static class Listener extends IImsMmTelListener.Stub { /** * Called when the IMS provider receives an incoming call. * @param c The {@link ImsCallSession} associated with the new call. * @param callId The call ID of the session of the new incoming call. * @param extras A bundle containing extra parameters related to the call. See * {@link #EXTRA_IS_UNKNOWN_CALL} and {@link #EXTRA_IS_USSD} above. * @return the listener to listen to the session events. An {@link ImsCallSession} can only * hold one listener at a time. see {@link ImsCallSessionListener}. * If this method returns {@code null}, then the call could not be placed. * @hide */ @Override @Nullable public IImsCallSessionListener onIncomingCall(IImsCallSession c, String callId, Bundle extras) { return null; } /** * Called when the IMS provider implicitly rejects an incoming call during setup. * @param callProfile An {@link ImsCallProfile} with the call details. * @param reason The {@link ImsReasonInfo} reason for call rejection. * @hide */ @Override public void onRejectedCall(ImsCallProfile callProfile, ImsReasonInfo reason) { } /** * Updates the Listener when the voice message count for IMS has changed. * @param count an integer representing the new message count. * @hide */ @Override public void onVoiceMessageCountUpdate(int count) { } /** * Called to set the audio handler for this connection. * @param imsAudioHandler an {@link ImsAudioHandler} used to handle the audio * for this IMS call. * @hide */ @Override public void onAudioModeIsVoipChanged(int imsAudioHandler) { } /** * Called when the IMS triggers EPS fallback procedure. * * @param reason specifies the reason that causes EPS fallback. * @hide */ @Override public void onTriggerEpsFallback(@EpsFallbackReason int reason) { } /** * Called when the IMS notifies the upcoming traffic type to the radio. * * @param token A nonce to identify the request * @param trafficType The {@link ImsTrafficType} type for IMS traffic. * @param accessNetworkType The {@link AccessNetworkConstants#RadioAccessNetworkType} * type of the radio access network. * @param trafficDirection Indicates whether traffic is originated by mobile originated or * mobile terminated use case eg. MO/MT call/SMS etc. * @param callback The callback to receive the result. * @hide */ @Override public void onStartImsTrafficSession(int token, @ImsTrafficType int trafficType, @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType, @ImsTrafficDirection int trafficDirection, IImsTrafficSessionCallback callback) { } /** * Called when the IMS notifies the traffic type has been stopped. * * @param token A nonce registered with {@link #onStartImsTrafficSession}. * @param accessNetworkType The {@link AccessNetworkConstants#RadioAccessNetworkType} * type of the radio access network. * @hide */ @Override public void onModifyImsTrafficSession(int token, @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType) { } /** * Called when the IMS notifies the traffic type has been stopped. * * @param token A nonce registered with {@link #onStartImsTrafficSession}. * @hide */ @Override public void onStopImsTrafficSession(int token) { } /** * Called when the IMS provider notifies {@link MediaQualityStatus}. * * @param status media quality status currently measured. * @hide */ @Override public void onMediaQualityStatusChanged(MediaQualityStatus status) { } } /** * A wrapper class of {@link ImsTrafficSessionCallback}. * @hide */ public static class ImsTrafficSessionCallbackWrapper { public static final int INVALID_TOKEN = -1; private static final int MAX_TOKEN = 0x10000; private static final AtomicInteger sTokenGenerator = new AtomicInteger(); /** Callback to receive the response */ private IImsTrafficSessionCallbackStub mCallback = null; /** Identifier to distinguish each IMS traffic request */ private int mToken = INVALID_TOKEN; private ImsTrafficSessionCallback mImsTrafficSessionCallback; private ImsTrafficSessionCallbackWrapper(ImsTrafficSessionCallback callback) { mImsTrafficSessionCallback = callback; } /** * Updates the callback. * * The mToken should be kept since it is used to identify the traffic notified to the modem * until calling {@link MmtelFEature#stopImsTrafficSession}. */ final void update(@NonNull @CallbackExecutor Executor executor) { if (executor == null) { throw new IllegalArgumentException( "ImsTrafficSessionCallback Executor must be non-null"); } if (mCallback == null) { // initial start of Ims traffic. mCallback = new IImsTrafficSessionCallbackStub( mImsTrafficSessionCallback, executor); mToken = generateToken(); } else { // handover between cellular and Wi-Fi mCallback.update(executor); } } /** * Using a static class and weak reference here to avoid memory leak caused by the * {@link IImsTrafficSessionCallback.Stub} callback retaining references to the outside * {@link ImsTrafficSessionCallback}. */ private static class IImsTrafficSessionCallbackStub extends IImsTrafficSessionCallback.Stub { private WeakReference mImsTrafficSessionCallbackWeakRef; private Executor mExecutor; IImsTrafficSessionCallbackStub(ImsTrafficSessionCallback imsTrafficCallback, Executor executor) { mImsTrafficSessionCallbackWeakRef = new WeakReference(imsTrafficCallback); mExecutor = executor; } void update(Executor executor) { mExecutor = executor; } @Override public void onReady() { ImsTrafficSessionCallback callback = mImsTrafficSessionCallbackWeakRef.get(); if (callback == null) return; Binder.withCleanCallingIdentity( () -> mExecutor.execute(() -> callback.onReady())); } @Override public void onError(ConnectionFailureInfo info) { ImsTrafficSessionCallback callback = mImsTrafficSessionCallbackWeakRef.get(); if (callback == null) return; Binder.withCleanCallingIdentity( () -> mExecutor.execute(() -> callback.onError(info))); } } /** * Returns the callback binder. */ final IImsTrafficSessionCallbackStub getCallbackBinder() { return mCallback; } /** * Returns the token. */ final int getToken() { return mToken; } /** * Resets the members. * It's called by {@link MmTelFeature#stopImsTrafficSession}. */ final void reset() { mCallback = null; mToken = INVALID_TOKEN; } private static int generateToken() { int token = sTokenGenerator.incrementAndGet(); if (token == MAX_TOKEN) sTokenGenerator.set(0); return token; } } /** * To be returned by {@link #shouldProcessCall(String[])} when the ImsService should process the * outgoing call as IMS. * @hide */ @SystemApi public static final int PROCESS_CALL_IMS = 0; /** * To be returned by {@link #shouldProcessCall(String[])} when the telephony framework should * not process the outgoing call as IMS and should instead use circuit switch. * @hide */ @SystemApi public static final int PROCESS_CALL_CSFB = 1; /** @hide */ @IntDef(flag = true, value = { PROCESS_CALL_IMS, PROCESS_CALL_CSFB }) @Retention(RetentionPolicy.SOURCE) public @interface ProcessCallResult {} /** * If the flag is present and true, it indicates that the incoming call is for USSD. *

* This is an optional boolean flag. * @hide */ @SystemApi public static final String EXTRA_IS_USSD = "android.telephony.ims.feature.extra.IS_USSD"; /** * If this flag is present and true, this call is marked as an unknown dialing call instead * of an incoming call. An example of such a call is a call that is originated by sending * commands (like AT commands) directly to the modem without Android involvement or dialing * calls appearing over IMS when the modem does a silent redial from circuit-switched to IMS in * certain situations. *

* This is an optional boolean flag. * @hide */ @SystemApi public static final String EXTRA_IS_UNKNOWN_CALL = "android.telephony.ims.feature.extra.IS_UNKNOWN_CALL"; /** @hide */ @IntDef( prefix = "AUDIO_HANDLER_", value = { AUDIO_HANDLER_ANDROID, AUDIO_HANDLER_BASEBAND }) @Retention(RetentionPolicy.SOURCE) public @interface ImsAudioHandler {} /** * Audio Handler - Android * @hide */ @SystemApi public static final int AUDIO_HANDLER_ANDROID = 0; /** * Audio Handler - Baseband * @hide */ @SystemApi public static final int AUDIO_HANDLER_BASEBAND = 1; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef( prefix = "EPS_FALLBACK_REASON_", value = { EPS_FALLBACK_REASON_INVALID, EPS_FALLBACK_REASON_NO_NETWORK_TRIGGER, EPS_FALLBACK_REASON_NO_NETWORK_RESPONSE, }) public @interface EpsFallbackReason {} /** * Default value. Internal use only. * This value should not be used to trigger EPS fallback. * @hide */ public static final int EPS_FALLBACK_REASON_INVALID = -1; /** * If the network only supports the EPS fallback in 5G NR SA for voice calling and the EPS * Fallback procedure by the network during the call setup is not triggered, UE initiated * fallback will be triggered with this reason. The modem shall locally release the 5G NR * SA RRC connection and acquire the LTE network and perform a tracking area update * procedure. After the EPS fallback procedure is completed, the call setup for voice will * be established if there is no problem. * * @hide */ public static final int EPS_FALLBACK_REASON_NO_NETWORK_TRIGGER = 1; /** * If the UE doesn't receive any response for SIP INVITE within a certain timeout in 5G NR * SA for MO voice calling, the device determines that voice call is not available in 5G and * terminates all active SIP dialogs and SIP requests and enters IMS non-registered state. * In that case, UE initiated fallback will be triggered with this reason. The modem shall * reset modem's data buffer of IMS PDU to prevent the ghost call. After the EPS fallback * procedure is completed, VoLTE call could be tried if there is no problem. * * @hide */ public static final int EPS_FALLBACK_REASON_NO_NETWORK_RESPONSE = 2; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef( prefix = "IMS_TRAFFIC_TYPE_", value = { IMS_TRAFFIC_TYPE_NONE, IMS_TRAFFIC_TYPE_EMERGENCY, IMS_TRAFFIC_TYPE_EMERGENCY_SMS, IMS_TRAFFIC_TYPE_VOICE, IMS_TRAFFIC_TYPE_VIDEO, IMS_TRAFFIC_TYPE_SMS, IMS_TRAFFIC_TYPE_REGISTRATION, IMS_TRAFFIC_TYPE_UT_XCAP }) public @interface ImsTrafficType {} /** * Default value for initialization. Internal use only. * @hide */ public static final int IMS_TRAFFIC_TYPE_NONE = -1; /** * Emergency call * @hide */ public static final int IMS_TRAFFIC_TYPE_EMERGENCY = 0; /** * Emergency SMS * @hide */ public static final int IMS_TRAFFIC_TYPE_EMERGENCY_SMS = 1; /** * Voice call * @hide */ public static final int IMS_TRAFFIC_TYPE_VOICE = 2; /** * Video call * @hide */ public static final int IMS_TRAFFIC_TYPE_VIDEO = 3; /** * SMS over IMS * @hide */ public static final int IMS_TRAFFIC_TYPE_SMS = 4; /** * IMS registration and subscription for reg event package (signaling) * @hide */ public static final int IMS_TRAFFIC_TYPE_REGISTRATION = 5; /** * Ut/XCAP (XML Configuration Access Protocol) * @hide */ public static final int IMS_TRAFFIC_TYPE_UT_XCAP = 6; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef( prefix = { "IMS_TRAFFIC_DIRECTION_" }, value = {IMS_TRAFFIC_DIRECTION_INCOMING, IMS_TRAFFIC_DIRECTION_OUTGOING}) public @interface ImsTrafficDirection {} /** * Indicates that the traffic is an incoming traffic. * @hide */ public static final int IMS_TRAFFIC_DIRECTION_INCOMING = 0; /** * Indicates that the traffic is an outgoing traffic. * @hide */ public static final int IMS_TRAFFIC_DIRECTION_OUTGOING = 1; private IImsMmTelListener mListener; /** * @param listener A {@link Listener} used when the MmTelFeature receives an incoming call and * notifies the framework. */ private void setListener(IImsMmTelListener listener) { synchronized (mLock) { mListener = listener; if (mListener != null) { onFeatureReady(); } } } /** * @return the listener associated with this MmTelFeature. May be null if it has not been set * by the framework yet. */ private IImsMmTelListener getListener() { synchronized (mLock) { return mListener; } } /** * The current capability status that this MmTelFeature has defined is available. This * configuration will be used by the platform to figure out which capabilities are CURRENTLY * available to be used. * * Should be a subset of the capabilities that are enabled by the framework in * {@link #changeEnabledCapabilities}. * @return A copy of the current MmTelFeature capability status. * @hide */ @Override @SystemApi public @NonNull final MmTelCapabilities queryCapabilityStatus() { return new MmTelCapabilities(super.queryCapabilityStatus()); } /** * Notify the framework that the status of the Capabilities has changed. Even though the * MmTelFeature capability may be enabled by the framework, the status may be disabled due to * the feature being unavailable from the network. * @param c The current capability status of the MmTelFeature. If a capability is disabled, then * the status of that capability is disabled. This can happen if the network does not currently * support the capability that is enabled. A capability that is disabled by the framework (via * {@link #changeEnabledCapabilities}) should also show the status as disabled. * @hide */ @SystemApi public final void notifyCapabilitiesStatusChanged(@NonNull MmTelCapabilities c) { if (c == null) { throw new IllegalArgumentException("MmTelCapabilities must be non-null!"); } super.notifyCapabilitiesStatusChanged(c); } /** * Notify the framework that the measured media quality has crossed a threshold set by {@link * MmTelFeature#setMediaThreshold} * * @param status current media quality status measured. * @hide */ @SystemApi public final void notifyMediaQualityStatusChanged( @NonNull MediaQualityStatus status) { if (status == null) { throw new IllegalArgumentException( "MediaQualityStatus must be non-null!"); } Log.i(LOG_TAG, "notifyMediaQualityStatusChanged " + status); IImsMmTelListener listener = getListener(); if (listener == null) { throw new IllegalStateException("Session is not available."); } try { listener.onMediaQualityStatusChanged(status); } catch (RemoteException e) { throw new RuntimeException(e); } } /** * Notify the framework of an incoming call. * @param c The {@link ImsCallSessionImplBase} of the new incoming call. * @param extras A bundle containing extra parameters related to the call. See * {@link #EXTRA_IS_UNKNOWN_CALL} and {@link #EXTRA_IS_USSD} above. * @hide * * @deprecated use {@link #notifyIncomingCall(ImsCallSessionImplBase, String, Bundle)} instead */ @Deprecated @SystemApi public final void notifyIncomingCall(@NonNull ImsCallSessionImplBase c, @NonNull Bundle extras) { if (c == null || extras == null) { throw new IllegalArgumentException("ImsCallSessionImplBase and Bundle can not be " + "null."); } IImsMmTelListener listener = getListener(); if (listener == null) { throw new IllegalStateException("Session is not available."); } try { c.setDefaultExecutor(MmTelFeature.this.mExecutor); listener.onIncomingCall(c.getServiceImpl(), null, extras); } catch (RemoteException e) { throw new RuntimeException(e); } } /** * Notify the framework of an incoming call. * @param c The {@link ImsCallSessionImplBase} of the new incoming call. * @param callId The call ID of the session of the new incoming call. * @param extras A bundle containing extra parameters related to the call. See * {@link #EXTRA_IS_UNKNOWN_CALL} and {@link #EXTRA_IS_USSD} above. * @return The listener used by the framework to listen to call session events created * from the ImsService. * If this method returns {@code null}, then the call could not be placed. * @hide */ @SystemApi @Nullable public final ImsCallSessionListener notifyIncomingCall( @NonNull ImsCallSessionImplBase c, @NonNull String callId, @NonNull Bundle extras) { if (c == null || callId == null || extras == null) { throw new IllegalArgumentException("ImsCallSessionImplBase, callId, and Bundle can " + "not be null."); } IImsMmTelListener listener = getListener(); if (listener == null) { throw new IllegalStateException("Session is not available."); } try { c.setDefaultExecutor(MmTelFeature.this.mExecutor); IImsCallSessionListener isl = listener.onIncomingCall(c.getServiceImpl(), callId, extras); if (isl != null) { ImsCallSessionListener iCSL = new ImsCallSessionListener(isl); iCSL.setDefaultExecutor(MmTelFeature.this.mExecutor); return iCSL; } else { return null; } } catch (RemoteException e) { throw new RuntimeException(e); } } /** * Notify the framework that a call has been implicitly rejected by this MmTelFeature * during call setup. * @param callProfile The {@link ImsCallProfile} IMS call profile with details. * This can be null if no call information is available for the rejected call. * @param reason The {@link ImsReasonInfo} call rejection reason. * @hide */ @SystemApi public final void notifyRejectedCall(@NonNull ImsCallProfile callProfile, @NonNull ImsReasonInfo reason) { if (callProfile == null || reason == null) { throw new IllegalArgumentException("ImsCallProfile and ImsReasonInfo must not be " + "null."); } IImsMmTelListener listener = getListener(); if (listener == null) { throw new IllegalStateException("Session is not available."); } try { listener.onRejectedCall(callProfile, reason); } catch (RemoteException e) { throw new RuntimeException(e); } } /** * * @hide */ public final void notifyIncomingCallSession(IImsCallSession c, Bundle extras) { IImsMmTelListener listener = getListener(); if (listener == null) { throw new IllegalStateException("Session is not available."); } try { listener.onIncomingCall(c, null, extras); } catch (RemoteException e) { throw new RuntimeException(e); } } /** * Notify the framework of a change in the Voice Message count. * @link count the new Voice Message count. * @hide */ @SystemApi public final void notifyVoiceMessageCountUpdate(int count) { IImsMmTelListener listener = getListener(); if (listener == null) { throw new IllegalStateException("Session is not available."); } try { listener.onVoiceMessageCountUpdate(count); } catch (RemoteException e) { throw new RuntimeException(e); } } /** * Sets the audio handler for this connection. The vendor IMS stack will invoke this API * to inform Telephony/Telecom layers about which audio handlers i.e. either Android or Modem * shall be used for handling the IMS call audio. * * @param imsAudioHandler {@link MmTelFeature#ImsAudioHandler} used to handle the audio * for this IMS call. * @hide */ @SystemApi public final void setCallAudioHandler(@ImsAudioHandler int imsAudioHandler) { IImsMmTelListener listener = getListener(); if (listener == null) { throw new IllegalStateException("Session is not available."); } try { listener.onAudioModeIsVoipChanged(imsAudioHandler); } catch (RemoteException e) { throw new RuntimeException(e); } } /** * Triggers the EPS fallback procedure. * * @param reason specifies the reason that causes EPS fallback. * @hide */ public final void triggerEpsFallback(@EpsFallbackReason int reason) { IImsMmTelListener listener = getListener(); if (listener == null) { throw new IllegalStateException("Session is not available."); } try { listener.onTriggerEpsFallback(reason); } catch (RemoteException e) { throw new RuntimeException(e); } } /** * Starts a new IMS traffic session with the framework. * * This API notifies the NAS and RRC layers of the modem that IMS traffic of type * {@link ImsTrafficType} is starting for the IMS session represented by a * {@link ImsTrafficSessionCallback}. The {@link ImsTrafficSessionCallback} * will notify the caller when IMS traffic is ready to start via the * {@link ImsTrafficSessionCallback#onReady()} callback. If there was an error starting * IMS traffic for the specified traffic type, {@link ImsTrafficSessionCallback#onError()} will * be called, which will also notify the caller of the reason of the failure. * * If there is a handover that changes the {@link AccessNetworkConstants#RadioAccessNetworkType} * of this IMS traffic session, then {@link #modifyImsTrafficSession} should be called. This is * used, for example, when a WiFi <-> cellular handover occurs. * * Once the IMS traffic session is finished, {@link #stopImsTrafficSession} must be called. * * Note: This API will be used to prioritize RF resources in case of DSDS. The service priority * is EMERGENCY > EMERGENCY SMS > VOICE > VIDEO > SMS > REGISTRATION > Ut/XCAP. RF * shall be prioritized to the subscription which handles the higher priority service. * When both subscriptions are handling the same type of service, then RF shall be * prioritized to the voice preferred sub. * * @param trafficType The {@link ImsTrafficType} type for IMS traffic. * @param accessNetworkType The {@link AccessNetworkConstants#RadioAccessNetworkType} type of * the radio access network. * @param trafficDirection Indicates whether traffic is originated by mobile originated or * mobile terminated use case eg. MO/MT call/SMS etc. * @param executor The Executor that will be used to call the {@link ImsTrafficSessionCallback}. * @param callback The session representing the IMS Session associated with a specific * trafficType. This callback instance should only be used for the specified traffic type * until {@link #stopImsTrafficSession} is called. * * @see modifyImsTrafficSession * @see stopImsTrafficSession * * @hide */ public final void startImsTrafficSession(@ImsTrafficType int trafficType, @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType, @ImsTrafficDirection int trafficDirection, @NonNull Executor executor, @NonNull ImsTrafficSessionCallback callback) { IImsMmTelListener listener = getListener(); if (listener == null) { throw new IllegalStateException("Session is not available."); } // TODO: retrieve from the callback list ImsTrafficSessionCallbackWrapper callbackWrapper = mTrafficCallbacks.get(callback); if (callbackWrapper == null) { callbackWrapper = new ImsTrafficSessionCallbackWrapper(callback); mTrafficCallbacks.put(callback, callbackWrapper); } try { callbackWrapper.update(executor); listener.onStartImsTrafficSession(callbackWrapper.getToken(), trafficType, accessNetworkType, trafficDirection, callbackWrapper.getCallbackBinder()); } catch (RemoteException e) { throw new RuntimeException(e); } } /** * Modifies an existing IMS traffic session represented by the associated * {@link ImsTrafficSessionCallback}. * * The {@link ImsTrafficSessionCallback} will notify the caller when IMS traffic is ready to * start after modification using the {@link ImsTrafficSessionCallback#onReady()} callback. * If there was an error modifying IMS traffic for the new radio access network type type, * {@link ImsTrafficSessionCallback#onError()} will be called, which will also notify the * caller of the reason of the failure. * * @param accessNetworkType The {@link AccessNetworkConstants#RadioAccessNetworkType} type of * the radio access network. * @param callback The callback registered with {@link #startImsTrafficSession}. * * @see startImsTrafficSession * @see stopImsTrafficSession * * @hide */ public final void modifyImsTrafficSession( @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType, @NonNull ImsTrafficSessionCallback callback) { IImsMmTelListener listener = getListener(); if (listener == null) { throw new IllegalStateException("Session is not available."); } ImsTrafficSessionCallbackWrapper callbackWrapper = mTrafficCallbacks.get(callback); if (callbackWrapper == null) { // should not reach here. throw new IllegalStateException("Unknown ImsTrafficSessionCallback instance."); } try { listener.onModifyImsTrafficSession(callbackWrapper.getToken(), accessNetworkType); } catch (RemoteException e) { throw new RuntimeException(e); } } /** * Notifies the framework that the IMS traffic session represented by the associated * {@link ImsTrafficSessionCallback} has ended. * * @param callback The callback registered with {@link #startImsTrafficSession}. * * @see startImsTrafficSession * @see modifyImsTrafficSession * * @hide */ public final void stopImsTrafficSession(@NonNull ImsTrafficSessionCallback callback) { IImsMmTelListener listener = getListener(); if (listener == null) { throw new IllegalStateException("Session is not available."); } ImsTrafficSessionCallbackWrapper callbackWrapper = mTrafficCallbacks.get(callback); if (callbackWrapper == null) { // should not reach here. throw new IllegalStateException("Unknown ImsTrafficSessionCallback instance."); } try { listener.onStopImsTrafficSession(callbackWrapper.getToken()); callbackWrapper.reset(); mTrafficCallbacks.remove(callback); } catch (RemoteException e) { throw new RuntimeException(e); } } /** * Provides the MmTelFeature with the ability to return the framework Capability Configuration * for a provided Capability. If the framework calls {@link #changeEnabledCapabilities} and * includes a capability A to enable or disable, this method should return the correct enabled * status for capability A. * @param capability The capability that we are querying the configuration for. * @return true if the capability is enabled, false otherwise. * @hide */ @Override @SystemApi public boolean queryCapabilityConfiguration(@MmTelCapabilities.MmTelCapability int capability, @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) { // Base implementation - Override to provide functionality return false; } /** * The MmTelFeature should override this method to handle the enabling/disabling of * MmTel Features, defined in {@link MmTelCapabilities.MmTelCapability}. The framework assumes * the {@link CapabilityChangeRequest} was processed successfully. If a subset of capabilities * could not be set to their new values, * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError} must be called * individually for each capability whose processing resulted in an error. * * Enabling/Disabling a capability here indicates that the capability should be registered or * deregistered (depending on the capability change) and become available or unavailable to * the framework. * @hide */ @Override @SystemApi public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request, @NonNull CapabilityCallbackProxy c) { // Base implementation, no-op } /** * Called by the framework to pass {@link MediaThreshold}. The MmTelFeature should override this * method to get Media quality threshold. This will pass the consolidated threshold values from * Telephony framework. IMS provider needs to monitor media quality of active call and notify * media quality {@link #notifyMediaQualityStatusChanged(MediaQualityStatus)} when the measured * media quality crosses at least one of {@link MediaThreshold} set by this. * * @param mediaSessionType media session type for this Threshold info. * @param mediaThreshold media threshold information * @hide */ @SystemApi public void setMediaThreshold( @MediaQualityStatus.MediaSessionType int mediaSessionType, @NonNull MediaThreshold mediaThreshold) { // Base Implementation - Should be overridden. Log.d(LOG_TAG, "setMediaThreshold is not supported." + mediaThreshold); } /** * The MmTelFeature should override this method to clear Media quality thresholds that were * registered and stop media quality status updates. * * @param mediaSessionType media session type * @hide */ @SystemApi public void clearMediaThreshold(@MediaQualityStatus.MediaSessionType int mediaSessionType) { // Base Implementation - Should be overridden. Log.d(LOG_TAG, "clearMediaThreshold is not supported." + mediaSessionType); } /** * IMS provider should override this method to return currently measured media quality status. * *

* If media quality status is not yet measured after call is active, it needs to notify media * quality status {@link #notifyMediaQualityStatusChanged(MediaQualityStatus)} when the first * measurement is done. * * @param mediaSessionType media session type * @return Current media quality status. It could be null if media quality status is not * measured yet or {@link MediaThreshold} was not set corresponding to the media session * type. * * @hide */ @SystemApi @Nullable public MediaQualityStatus queryMediaQualityStatus( @MediaQualityStatus.MediaSessionType int mediaSessionType) { // Base Implementation - Should be overridden. Log.d(LOG_TAG, "queryMediaQualityStatus is not supported." + mediaSessionType); return null; } /** * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state. * * @param callSessionType a service type that is specified in {@link ImsCallProfile} * {@link ImsCallProfile#SERVICE_TYPE_NONE} * {@link ImsCallProfile#SERVICE_TYPE_NORMAL} * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY} * @param callType a call type that is specified in {@link ImsCallProfile} * {@link ImsCallProfile#CALL_TYPE_VOICE} * {@link ImsCallProfile#CALL_TYPE_VT} * {@link ImsCallProfile#CALL_TYPE_VT_TX} * {@link ImsCallProfile#CALL_TYPE_VT_RX} * {@link ImsCallProfile#CALL_TYPE_VT_NODIR} * {@link ImsCallProfile#CALL_TYPE_VS} * {@link ImsCallProfile#CALL_TYPE_VS_TX} * {@link ImsCallProfile#CALL_TYPE_VS_RX} * @return a {@link ImsCallProfile} object * @hide */ @SystemApi public @Nullable ImsCallProfile createCallProfile(int callSessionType, int callType) { // Base Implementation - Should be overridden return null; } /** * Called by the framework to report a change to the RTP header extension types which should be * offered during SDP negotiation (see RFC8285 for more information). *

* The {@link ImsService} should report the RTP header extensions which were accepted during * SDP negotiation using {@link ImsCallProfile#setAcceptedRtpHeaderExtensionTypes(Set)}. * * @param extensionTypes The RTP header extensions the framework wishes to offer during * outgoing and incoming call setup. An empty list indicates that there * are no framework defined RTP header extension types to offer. * @hide */ @SystemApi public void changeOfferedRtpHeaderExtensionTypes( @NonNull Set extensionTypes) { // Base implementation - should be overridden if RTP header extension handling is supported. } /** * @hide */ public IImsCallSession createCallSessionInterface(ImsCallProfile profile) throws RemoteException { ImsCallSessionImplBase s = MmTelFeature.this.createCallSession(profile); if (s != null) { s.setDefaultExecutor(mExecutor); return s.getServiceImpl(); } else { return null; } } /** * Creates an {@link ImsCallSession} with the specified call profile. * Use other methods, if applicable, instead of interacting with * {@link ImsCallSession} directly. * * @param profile a call profile to make the call * @hide */ @SystemApi public @Nullable ImsCallSessionImplBase createCallSession(@NonNull ImsCallProfile profile) { // Base Implementation - Should be overridden return null; } /** * Called by the framework to determine if the outgoing call, designated by the outgoing * {@link String}s, should be processed as an IMS call or CSFB call. If this method's * functionality is not overridden, the platform will process every call as IMS as long as the * MmTelFeature reports that the {@link MmTelCapabilities#CAPABILITY_TYPE_VOICE} capability is * available. * @param numbers An array of {@link String}s that will be used for placing the call. There can * be multiple {@link String}s listed in the case when we want to place an outgoing * call as a conference. * @return a {@link ProcessCallResult} to the framework, which will be used to determine if the * call will be placed over IMS or via CSFB. * @hide */ @SystemApi public @ProcessCallResult int shouldProcessCall(@NonNull String[] numbers) { return PROCESS_CALL_IMS; } /** * * @hide */ protected IImsUt getUtInterface() throws RemoteException { ImsUtImplBase utImpl = getUt(); if (utImpl != null) { utImpl.setDefaultExecutor(mExecutor); return utImpl.getInterface(); } else { return null; } } /** * @hide */ protected IImsEcbm getEcbmInterface() throws RemoteException { ImsEcbmImplBase ecbmImpl = getEcbm(); if (ecbmImpl != null) { ecbmImpl.setDefaultExecutor(mExecutor); return ecbmImpl.getImsEcbm(); } else { return null; } } /** * @hide */ public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { ImsMultiEndpointImplBase multiendpointImpl = getMultiEndpoint(); if (multiendpointImpl != null) { multiendpointImpl.setDefaultExecutor(mExecutor); return multiendpointImpl.getIImsMultiEndpoint(); } else { return null; } } /** * @hide */ public @NonNull ImsSmsImplBase getImsSmsImpl() { synchronized (mLock) { if (mSmsImpl == null) { mSmsImpl = getSmsImplementation(); mSmsImpl.setDefaultExecutor(mExecutor); } return mSmsImpl; } } /** * @return The {@link ImsUtImplBase} Ut interface implementation for the supplementary service * configuration. * @hide */ @SystemApi public @NonNull ImsUtImplBase getUt() { // Base Implementation - Should be overridden return new ImsUtImplBase(); } /** * @return The {@link ImsEcbmImplBase} Emergency call-back mode interface for emergency VoLTE * calls that support it. * @hide */ @SystemApi public @NonNull ImsEcbmImplBase getEcbm() { // Base Implementation - Should be overridden return new ImsEcbmImplBase(); } /** * @return The {@link ImsMultiEndpointImplBase} implementation for implementing Dialog event * package processing for multi-endpoint. * @hide */ @SystemApi public @NonNull ImsMultiEndpointImplBase getMultiEndpoint() { // Base Implementation - Should be overridden return new ImsMultiEndpointImplBase(); } /** * Sets the current UI TTY mode for the MmTelFeature. * @param mode An integer containing the new UI TTY Mode, can consist of * {@link TelecomManager#TTY_MODE_OFF}, * {@link TelecomManager#TTY_MODE_FULL}, * {@link TelecomManager#TTY_MODE_HCO}, * {@link TelecomManager#TTY_MODE_VCO} * @param onCompleteMessage If non-null, this MmTelFeature should call this {@link Message} when * the operation is complete by using the associated {@link android.os.Messenger} in * {@link Message#replyTo}. For example: * {@code * // Set UI TTY Mode and other operations... * try { * // Notify framework that the mode was changed. * Messenger uiMessenger = onCompleteMessage.replyTo; * uiMessenger.send(onCompleteMessage); * } catch (RemoteException e) { * // Remote side is dead * } * } * @hide */ @SystemApi public void setUiTtyMode(int mode, @Nullable Message onCompleteMessage) { // Base Implementation - Should be overridden } /** * Notifies the MmTelFeature of the enablement status of terminal based call waiting * * If the terminal based call waiting is provisioned, * IMS controls the enablement of terminal based call waiting which is defined * in 3GPP TS 24.615. * * @param enabled user setting controlling whether or not call waiting is enabled. * * @hide */ @SystemApi public void setTerminalBasedCallWaitingStatus(boolean enabled) { // Base Implementation - Should be overridden by IMS service throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION, "Not implemented on device."); } /** * Notifies the MmTelFeature that the network has initiated an SRVCC (Single radio voice * call continuity) for all IMS calls. When the network initiates an SRVCC, calls from * the LTE domain are handed over to the legacy circuit switched domain. The modem requires * knowledge of ongoing calls in the IMS domain in order to complete the SRVCC operation. *

* @param consumer The callback used to notify the framework of the list of IMS calls and their * state at the time of the SRVCC. * * @hide */ @SystemApi public void notifySrvccStarted(@NonNull Consumer> consumer) { // Base Implementation - Should be overridden by IMS service } /** * Notifies the MmTelFeature that the SRVCC is completed and the calls have been moved * over to the circuit-switched domain. * {@link android.telephony.CarrierConfigManager.ImsVoice#KEY_SRVCC_TYPE_INT_ARRAY} * specifies the calls can be moved. Other calls will be disconnected. *

* The MmTelFeature may now release all resources related to the IMS calls. * * @hide */ @SystemApi public void notifySrvccCompleted() { // Base Implementation - Should be overridden by IMS service } /** * Notifies the MmTelFeature that the SRVCC has failed. * * The handover can fail by encountering a failure at the radio level * or temporary MSC server internal errors in handover procedure. * Refer to 3GPP TS 23.216 section 8 Handover Failure. *

* IMS service will recover and continue calls over IMS. * Per TS 24.237 12.2.4.2, UE shall send SIP UPDATE request containing the reason-text * set to "failure to transition to CS domain". * * @hide */ @SystemApi public void notifySrvccFailed() { // Base Implementation - Should be overridden by IMS service } /** * Notifies the MmTelFeature that the SRVCC has been canceled. * * Since the state of network can be changed, the network can decide to terminate * the handover procedure before its completion and to return to its state before the handover * procedure was triggered. * Refer to 3GPP TS 23.216 section 8.1.3 Handover Cancellation. * *

* IMS service will recover and continue calls over IMS. * Per TS 24.237 12.2.4.2, UE shall send SIP UPDATE request containing the reason-text * set to "handover canceled". * * @hide */ @SystemApi public void notifySrvccCanceled() { // Base Implementation - Should be overridden by IMS service } private void setSmsListener(IImsSmsListener listener) { getImsSmsImpl().registerSmsListener(listener); } private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, byte[] pdu) { getImsSmsImpl().sendSms(token, messageRef, format, smsc, isRetry, pdu); } private void onMemoryAvailable(int token) { getImsSmsImpl().onMemoryAvailable(token); } private void acknowledgeSms(int token, int messageRef, @ImsSmsImplBase.DeliverStatusResult int result) { getImsSmsImpl().acknowledgeSms(token, messageRef, result); } private void acknowledgeSms(int token, int messageRef, @ImsSmsImplBase.DeliverStatusResult int result, byte[] pdu) { getImsSmsImpl().acknowledgeSms(token, messageRef, result, pdu); } private void acknowledgeSmsReport(int token, int messageRef, @ImsSmsImplBase.StatusReportResult int result) { getImsSmsImpl().acknowledgeSmsReport(token, messageRef, result); } private void onSmsReady() { getImsSmsImpl().onReady(); } /** * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default * non-functional implementation is returned. * * @return an instance of {@link ImsSmsImplBase} which should be implemented by the IMS * Provider. * @hide */ @SystemApi public @NonNull ImsSmsImplBase getSmsImplementation() { return new ImsSmsImplBase(); } private String getSmsFormat() { return getImsSmsImpl().getSmsFormat(); } /** * {@inheritDoc} * @hide */ @Override @SystemApi public void onFeatureRemoved() { // Base Implementation - Should be overridden } /** * {@inheritDoc} * @hide */ @Override @SystemApi public void onFeatureReady() { // Base Implementation - Should be overridden } /** * @hide */ @Override public final IImsMmTelFeature getBinder() { return mImsMMTelBinder; } /** * Set default Executor from ImsService. * @param executor The default executor for the framework to use when executing the methods * overridden by the implementation of MmTelFeature. * @hide */ public final void setDefaultExecutor(@NonNull Executor executor) { if (mExecutor == null) { mExecutor = executor; } } }