/* * Copyright (C) 2020 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.Manifest; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.Context; import android.content.pm.PackageManager; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.telephony.BinderCacheManager; import android.telephony.ims.aidl.IImsRcsController; import android.telephony.ims.aidl.SipDelegateConnectionAidlWrapper; import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.stub.DelegateConnectionMessageCallback; import android.telephony.ims.stub.DelegateConnectionStateCallback; import android.telephony.ims.stub.SipDelegate; import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.ITelephony; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.concurrent.Executor; /** * Manages the creation and destruction of SipDelegates for the {@link ImsService} managing IMS * for the subscription ID that this SipDelegateManager has been created for. * * This allows multiple IMS applications to forward SIP messages to/from their application for the * purposes of providing a single IMS registration to the carrier's IMS network from potentially * many IMS stacks implementing a subset of the supported MMTEL/RCS features. *

* This API is only supported if the device supports the * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION} feature. * @hide */ @SystemApi @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION) public class SipDelegateManager { /** * The SIP message has failed being sent or received for an unknown reason. *

* The caller should retry a message that failed with this response. */ public static final int MESSAGE_FAILURE_REASON_UNKNOWN = 0; /** * The remote service associated with this connection has died and the message was not * properly sent/received. *

* This is considered a permanent error and the system will automatically begin the teardown and * destruction of the SipDelegate. No further messages should be sent on this transport. */ public static final int MESSAGE_FAILURE_REASON_DELEGATE_DEAD = 1; /** * The message has not been sent/received because the delegate is in the process of closing and * has become unavailable. No further messages should be sent/received on this delegate. */ public static final int MESSAGE_FAILURE_REASON_DELEGATE_CLOSED = 2; /** * The SIP message has an invalid start line and the message can not be sent or the start line * failed validation due to the request containing a restricted SIP request method. * {@link SipDelegateConnection}s can not send SIP requests for the methods: REGISTER, PUBLISH, * or OPTIONS. */ public static final int MESSAGE_FAILURE_REASON_INVALID_START_LINE = 3; /** * One or more of the header fields in the header section of the outgoing SIP message is invalid * or contains a restricted header value and the SIP message can not be sent. * {@link SipDelegateConnection}s can not send SIP SUBSCRIBE requests for the "Event" header * value of "presence". */ public static final int MESSAGE_FAILURE_REASON_INVALID_HEADER_FIELDS = 4; /** * The body content of the SIP message is invalid and the message can not be sent. */ public static final int MESSAGE_FAILURE_REASON_INVALID_BODY_CONTENT = 5; /** * The feature tag associated with the outgoing message does not match any known feature tags * or it matches a denied tag and this message can not be sent. */ public static final int MESSAGE_FAILURE_REASON_INVALID_FEATURE_TAG = 6; /** * The feature tag associated with the outgoing message is not enabled for the associated * SipDelegateConnection and can not be sent. */ public static final int MESSAGE_FAILURE_REASON_TAG_NOT_ENABLED_FOR_DELEGATE = 7; /** * The link to the network has been lost and the outgoing message has failed to send. *

* This message should be retried when connectivity to the network is re-established. See * {@link android.net.ConnectivityManager.NetworkCallback} for how this can be determined. */ public static final int MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE = 8; /** * The outgoing SIP message has not been sent due to the SipDelegate not being registered for * IMS at this time. *

* This is considered a temporary failure, the message should not be retried until an IMS * registration change callback is received via * {@link DelegateConnectionStateCallback#onFeatureTagStatusChanged} */ public static final int MESSAGE_FAILURE_REASON_NOT_REGISTERED = 9; /** * The outgoing SIP message has not been sent because the {@link SipDelegateConfiguration} * version associated with the outgoing {@link SipMessage} is now stale and has failed * validation checks. *

* The @link SipMessage} should be recreated using the newest * {@link SipDelegateConfiguration} and sent again. */ public static final int MESSAGE_FAILURE_REASON_STALE_IMS_CONFIGURATION = 10; /** * The outgoing SIP message has not been sent because the internal state of the associated * {@link SipDelegate} is changing and has temporarily brought the transport down. *

* This is considered a temporary error and the {@link SipDelegateConnection} should resend the * message once {@link DelegateRegistrationState#DEREGISTERING_REASON_FEATURE_TAGS_CHANGING} is * no longer reported. */ public static final int MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION = 11; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "MESSAGE_FAILURE_REASON_", value = { MESSAGE_FAILURE_REASON_UNKNOWN, MESSAGE_FAILURE_REASON_DELEGATE_DEAD, MESSAGE_FAILURE_REASON_DELEGATE_CLOSED, MESSAGE_FAILURE_REASON_INVALID_START_LINE, MESSAGE_FAILURE_REASON_INVALID_HEADER_FIELDS, MESSAGE_FAILURE_REASON_INVALID_BODY_CONTENT, MESSAGE_FAILURE_REASON_INVALID_FEATURE_TAG, MESSAGE_FAILURE_REASON_TAG_NOT_ENABLED_FOR_DELEGATE, MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE, MESSAGE_FAILURE_REASON_NOT_REGISTERED, MESSAGE_FAILURE_REASON_STALE_IMS_CONFIGURATION, MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION }) public @interface MessageFailureReason {} /**@hide*/ public static final ArrayMap MESSAGE_FAILURE_REASON_STRING_MAP = new ArrayMap<>(11); static { MESSAGE_FAILURE_REASON_STRING_MAP.append(MESSAGE_FAILURE_REASON_UNKNOWN, "MESSAGE_FAILURE_REASON_UNKNOWN"); MESSAGE_FAILURE_REASON_STRING_MAP.append(MESSAGE_FAILURE_REASON_DELEGATE_DEAD, "MESSAGE_FAILURE_REASON_DELEGATE_DEAD"); MESSAGE_FAILURE_REASON_STRING_MAP.append(MESSAGE_FAILURE_REASON_DELEGATE_CLOSED, "MESSAGE_FAILURE_REASON_DELEGATE_CLOSED"); MESSAGE_FAILURE_REASON_STRING_MAP.append(MESSAGE_FAILURE_REASON_INVALID_HEADER_FIELDS, "MESSAGE_FAILURE_REASON_INVALID_HEADER_FIELDS"); MESSAGE_FAILURE_REASON_STRING_MAP.append(MESSAGE_FAILURE_REASON_INVALID_BODY_CONTENT, "MESSAGE_FAILURE_REASON_INVALID_BODY_CONTENT"); MESSAGE_FAILURE_REASON_STRING_MAP.append(MESSAGE_FAILURE_REASON_INVALID_FEATURE_TAG, "MESSAGE_FAILURE_REASON_INVALID_FEATURE_TAG"); MESSAGE_FAILURE_REASON_STRING_MAP.append( MESSAGE_FAILURE_REASON_TAG_NOT_ENABLED_FOR_DELEGATE, "MESSAGE_FAILURE_REASON_TAG_NOT_ENABLED_FOR_DELEGATE"); MESSAGE_FAILURE_REASON_STRING_MAP.append(MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE, "MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE"); MESSAGE_FAILURE_REASON_STRING_MAP.append(MESSAGE_FAILURE_REASON_NOT_REGISTERED, "MESSAGE_FAILURE_REASON_NOT_REGISTERED"); MESSAGE_FAILURE_REASON_STRING_MAP.append(MESSAGE_FAILURE_REASON_STALE_IMS_CONFIGURATION, "MESSAGE_FAILURE_REASON_STALE_IMS_CONFIGURATION"); MESSAGE_FAILURE_REASON_STRING_MAP.append( MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION, "MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION"); } /** * Access to use this feature tag has been denied for an unknown reason. */ public static final int DENIED_REASON_UNKNOWN = 0; /** * This feature tag is allowed to be used by this SipDelegateConnection, but it is in use by * another SipDelegateConnection and can not be associated with this delegate. The feature tag * will stay in this state until the feature tag is release by the other application. */ public static final int DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE = 1; /** * Access to use this feature tag has been denied because this application does not have the * permissions required to access this feature tag. */ public static final int DENIED_REASON_NOT_ALLOWED = 2; /** * Access to use this feature tag has been denied because single registration is not allowed by * the carrier at this time. The application should fall back to dual registration if * applicable. */ public static final int DENIED_REASON_SINGLE_REGISTRATION_NOT_ALLOWED = 3; /** * This feature tag is not recognized as a valid feature tag by the SipDelegate and has been * denied. */ public static final int DENIED_REASON_INVALID = 4; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "DENIED_REASON_", value = { DENIED_REASON_UNKNOWN, DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE, DENIED_REASON_NOT_ALLOWED, DENIED_REASON_SINGLE_REGISTRATION_NOT_ALLOWED, DENIED_REASON_INVALID }) public @interface DeniedReason {} /** * The SipDelegate has closed due to an unknown reason. */ public static final int SIP_DELEGATE_DESTROY_REASON_UNKNOWN = 0; /** * The SipDelegate has closed because the IMS service has died unexpectedly. */ public static final int SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD = 1; /** * The SipDelegate has closed because the IMS application has requested that the connection be * destroyed. */ public static final int SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP = 2; /** * The SipDelegate has been closed due to the user disabling RCS. */ public static final int SIP_DELEGATE_DESTROY_REASON_USER_DISABLED_RCS = 3; /** * The SipDelegate has been closed due to the subscription associated with this delegate being * torn down. */ public static final int SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN = 4; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "SIP_DELEGATE_DESTROY_REASON", value = { SIP_DELEGATE_DESTROY_REASON_UNKNOWN, SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD, SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP, SIP_DELEGATE_DESTROY_REASON_USER_DISABLED_RCS, SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN }) public @interface SipDelegateDestroyReason {} private final Context mContext; private final int mSubId; private final BinderCacheManager mBinderCache; private final BinderCacheManager mTelephonyBinderCache; /** * Only visible for testing. To instantiate an instance of this class, please use * {@link ImsManager#getSipDelegateManager(int)}. * @hide */ @VisibleForTesting public SipDelegateManager(Context context, int subId, BinderCacheManager binderCache, BinderCacheManager telephonyBinderCache) { mContext = context; mSubId = subId; mBinderCache = binderCache; mTelephonyBinderCache = telephonyBinderCache; } /** * Determines if creating SIP delegates are supported for the subscription specified. *

* If SIP delegates are not supported on this device or the carrier associated with this * subscription, creating a SIP delegate will always fail, as this feature is not supported. * @return true if this device supports creating a SIP delegate and the carrier associated with * this subscription supports single registration, false if creating SIP delegates is not * supported. * @throws ImsException If the remote ImsService is not available for any reason or the * subscription associated with this instance is no longer active. See * {@link ImsException#getCode()} for more information. * * @see CarrierConfigManager.Ims#KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL * @see PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION */ @RequiresPermission(anyOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE, Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public boolean isSupported() throws ImsException { try { IImsRcsController controller = mBinderCache.getBinder(); if (controller == null) { throw new ImsException("Telephony server is down", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } return controller.isSipDelegateSupported(mSubId); } catch (ServiceSpecificException e) { throw new ImsException(e.getMessage(), e.errorCode); } catch (RemoteException e) { throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } /** * Request that the ImsService implementation create a SipDelegate, which will configure the * ImsService to forward SIP traffic that matches the filtering criteria set in supplied * {@link DelegateRequest} to the application that the supplied callbacks are registered for. *

* This API requires that the caller is running as part of a long-running process and will * always be available to handle incoming messages. One mechanism that can be used for this is * the {@link android.service.carrier.CarrierMessagingClientService}, which the framework keeps * a persistent binding to when the app is the default SMS application. *

* Note: the ability to create SipDelegates is only available applications running as the * primary user. * @param request The parameters that are associated with the SipDelegate creation request that * will be used to create the SipDelegate connection. * @param executor The executor that will be used to call the callbacks associated with this * SipDelegate. * @param dc The callback that will be used to notify the listener of the creation/destruction * of the remote SipDelegate as well as changes to the state of the remote SipDelegate * connection. * @param mc The callback that will be used to notify the listener of new incoming SIP messages * as well as the status of messages that were sent by the associated * SipDelegateConnection. * @throws ImsException Thrown if there was a problem communicating with the ImsService * associated with this SipDelegateManager. See {@link ImsException#getCode()}. */ @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void createSipDelegate(@NonNull DelegateRequest request, @NonNull Executor executor, @NonNull DelegateConnectionStateCallback dc, @NonNull DelegateConnectionMessageCallback mc) throws ImsException { Objects.requireNonNull(request, "The DelegateRequest must not be null."); Objects.requireNonNull(executor, "The Executor must not be null."); Objects.requireNonNull(dc, "The DelegateConnectionStateCallback must not be null."); Objects.requireNonNull(mc, "The DelegateConnectionMessageCallback must not be null."); try { SipDelegateConnectionAidlWrapper wrapper = new SipDelegateConnectionAidlWrapper(executor, dc, mc); IImsRcsController controller = mBinderCache.listenOnBinder(wrapper, wrapper::binderDied); if (controller == null) { throw new ImsException("Telephony server is down", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } controller.createSipDelegate(mSubId, request, mContext.getOpPackageName(), wrapper.getStateCallbackBinder(), wrapper.getMessageCallbackBinder()); } catch (ServiceSpecificException e) { throw new ImsException(e.getMessage(), e.errorCode); } catch (RemoteException e) { throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } /** * Destroy a previously created {@link SipDelegateConnection} that was created using * {@link #createSipDelegate}. *

* This will also clean up all related callbacks in the associated ImsService. * @param delegateConnection The SipDelegateConnection to destroy. * @param reason The reason for why this SipDelegateConnection was destroyed. */ @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void destroySipDelegate(@NonNull SipDelegateConnection delegateConnection, @SipDelegateDestroyReason int reason) { Objects.requireNonNull(delegateConnection, "SipDelegateConnection can not be null."); if (delegateConnection instanceof SipDelegateConnectionAidlWrapper) { SipDelegateConnectionAidlWrapper w = (SipDelegateConnectionAidlWrapper) delegateConnection; try { IImsRcsController c = mBinderCache.removeRunnable(w); c.destroySipDelegate(mSubId, w.getSipDelegateBinder(), reason); } catch (RemoteException e) { // Connection to telephony died, but this will signal destruction of SipDelegate // eventually anyway, so return normally. try { w.getStateCallbackBinder().onDestroyed( SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP); } catch (RemoteException ignore) { // Local to process. } } } else { throw new IllegalArgumentException("Unknown SipDelegateConnection implementation passed" + " into this method"); } } /** * Trigger a full network registration as required by receiving a SIP message containing a * permanent error from the network or never receiving a response to a SIP transaction request. * * @param connection The {@link SipDelegateConnection} that was being used when this error was * received. * @param sipCode The SIP code response associated with the SIP message request that * triggered this condition. * @param sipReason The SIP reason code associated with the SIP message request that triggered * this condition. May be {@code null} if there was no reason String provided from the * network. */ @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void triggerFullNetworkRegistration(@NonNull SipDelegateConnection connection, @IntRange(from = 100, to = 699) int sipCode, @Nullable String sipReason) { Objects.requireNonNull(connection, "SipDelegateConnection can not be null."); if (connection instanceof SipDelegateConnectionAidlWrapper) { SipDelegateConnectionAidlWrapper w = (SipDelegateConnectionAidlWrapper) connection; try { IImsRcsController controller = mBinderCache.getBinder(); controller.triggerNetworkRegistration(mSubId, w.getSipDelegateBinder(), sipCode, sipReason); } catch (RemoteException e) { // Connection to telephony died, but this will signal destruction of SipDelegate // eventually anyway, so return. } } else { throw new IllegalArgumentException("Unknown SipDelegateConnection implementation passed" + " into this method"); } } /** * Register a new callback, which is used to notify the registrant of changes to * the state of the underlying IMS service that is attached to telephony to * implement IMS functionality. If the manager is created for * the {@link android.telephony.SubscriptionManager#DEFAULT_SUBSCRIPTION_ID}, * this throws an {@link ImsException}. * *

Requires Permission: * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE READ_PRECISE_PHONE_STATE} * or that the calling app has carrier privileges * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}). * * @param executor the Executor that will be used to call the {@link ImsStateCallback}. * @param callback The callback instance being registered. * @throws ImsException in the case that the callback can not be registered. * See {@link ImsException#getCode} for more information on when this is called. */ @RequiresPermission(anyOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE, Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public void registerImsStateCallback(@NonNull Executor executor, @NonNull ImsStateCallback callback) throws ImsException { Objects.requireNonNull(callback, "Must include a non-null ImsStateCallback."); Objects.requireNonNull(executor, "Must include a non-null Executor."); callback.init(executor); ITelephony telephony = mTelephonyBinderCache.listenOnBinder(callback, callback::binderDied); if (telephony == null) { throw new ImsException("Telephony server is down", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } try { telephony.registerImsStateCallback( mSubId, ImsFeature.FEATURE_RCS, callback.getCallbackBinder(), mContext.getOpPackageName()); } catch (ServiceSpecificException e) { throw new ImsException(e.getMessage(), e.errorCode); } catch (RemoteException | IllegalStateException e) { throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } /** * Unregisters a previously registered callback. * * @param callback The callback instance to be unregistered. */ public void unregisterImsStateCallback(@NonNull ImsStateCallback callback) { Objects.requireNonNull(callback, "Must include a non-null ImsStateCallback."); ITelephony telephony = mTelephonyBinderCache.removeRunnable(callback); try { if (telephony != null) { telephony.unregisterImsStateCallback(callback.getCallbackBinder()); } } catch (RemoteException ignore) { // ignore it } } /** * Register a new callback, which is used to notify the registrant of changes * to the state of the Sip Sessions managed remotely by the IMS stack. * *

Requires Permission: * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} * * @param executor the Executor that will be used to call the {@link SipDialogStateCallback}. * @param callback The callback instance being registered. * @throws ImsException in the case that the callback can not be registered. * See {@link ImsException#getCode} for more information on when this is called. * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerSipDialogStateCallback(@NonNull Executor executor, @NonNull SipDialogStateCallback callback) throws ImsException { Objects.requireNonNull(callback, "Must include a non-null SipDialogStateCallback."); Objects.requireNonNull(executor, "Must include a non-null Executor."); callback.attachExecutor(executor); try { IImsRcsController controller = mBinderCache.listenOnBinder( callback, callback::binderDied); if (controller == null) { throw new ImsException("Telephony server is down", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } controller.registerSipDialogStateCallback(mSubId, callback.getCallbackBinder()); } catch (ServiceSpecificException e) { throw new ImsException(e.getMessage(), e.errorCode); } catch (RemoteException e) { throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } catch (IllegalStateException e) { throw new IllegalStateException(e.getMessage()); } } /** * Unregisters a previously registered callback. * *

Requires Permission: * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} * * @param callback The callback instance to be unregistered. * * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterSipDialogStateCallback(@NonNull SipDialogStateCallback callback) throws ImsException { Objects.requireNonNull(callback, "Must include a non-null SipDialogStateCallback."); IImsRcsController controller = mBinderCache.removeRunnable(callback); try { if (controller == null) { throw new ImsException("Telephony server is down", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } controller.unregisterSipDialogStateCallback(mSubId, callback.getCallbackBinder()); } catch (ServiceSpecificException e) { throw new ImsException(e.getMessage(), e.errorCode); } catch (RemoteException e) { throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } catch (IllegalStateException e) { throw new IllegalStateException(e.getMessage()); } } }