/* * 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.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.Uri; import android.os.RemoteException; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ImsRegistrationAttributes; import android.telephony.ims.RegistrationManager; import android.telephony.ims.SipDetails; import android.telephony.ims.aidl.IImsRegistration; import android.telephony.ims.aidl.IImsRegistrationCallback; import android.util.Log; import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.util.RemoteCallbackListExt; import com.android.internal.telephony.util.TelephonyUtils; import com.android.internal.util.ArrayUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; 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.AtomicReference; import java.util.function.Consumer; import java.util.function.Supplier; /** * Controls IMS registration for this ImsService and notifies the framework when the IMS * registration for this ImsService has changed status. *

* Note: There is no guarantee on the thread that the calls from the framework will be called on. It * is the implementors responsibility to handle moving the calls to a working thread if required. */ public class ImsRegistrationImplBase { private static final String LOG_TAG = "ImsRegistrationImplBase"; /** * @hide */ // Defines the underlying radio technology type that we have registered for IMS over. @IntDef(value = { REGISTRATION_TECH_NONE, REGISTRATION_TECH_LTE, REGISTRATION_TECH_IWLAN, REGISTRATION_TECH_CROSS_SIM, REGISTRATION_TECH_NR, REGISTRATION_TECH_3G }) @Retention(RetentionPolicy.SOURCE) public @interface ImsRegistrationTech {} /** * No registration technology specified, used when we are not registered. */ public static final int REGISTRATION_TECH_NONE = -1; /** * This ImsService is registered to IMS via LTE. */ public static final int REGISTRATION_TECH_LTE = 0; /** * This ImsService is registered to IMS via IWLAN. */ public static final int REGISTRATION_TECH_IWLAN = 1; /** * This ImsService is registered to IMS via internet over second subscription. */ public static final int REGISTRATION_TECH_CROSS_SIM = 2; /** * This ImsService is registered to IMS via NR. */ public static final int REGISTRATION_TECH_NR = 3; /** * This ImsService is registered to IMS via 3G. */ public static final int REGISTRATION_TECH_3G = 4; /** * This is used to check the upper range of registration tech * @hide */ public static final int REGISTRATION_TECH_MAX = REGISTRATION_TECH_3G + 1; // Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current // state. // The unknown state is set as the initialization state. This is so that we do not call back // with NOT_REGISTERED in the case where the ImsService has not updated the registration state // yet. private static final int REGISTRATION_STATE_UNKNOWN = -1; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef( prefix = {"REASON_"}, value = { REASON_UNKNOWN, REASON_SIM_REMOVED, REASON_SIM_REFRESH, REASON_ALLOWED_NETWORK_TYPES_CHANGED, REASON_NON_IMS_CAPABLE_NETWORK, REASON_RADIO_POWER_OFF, REASON_HANDOVER_FAILED, REASON_VOPS_NOT_SUPPORTED, }) public @interface ImsDeregistrationReason{} /** * Unspecified reason. * @hide */ public static final int REASON_UNKNOWN = 0; /** * Since SIM is removed, the credentials for IMS service is also removed. * @hide */ public static final int REASON_SIM_REMOVED = 1; /** * Detach from the network shall be performed due to the SIM refresh. IMS service should be * deregistered before that procedure. * @hide */ public static final int REASON_SIM_REFRESH = 2; /** * The allowed network types have changed, resulting in a network type * that does not support IMS. * @hide */ public static final int REASON_ALLOWED_NETWORK_TYPES_CHANGED = 3; /** * The device camped on a network that does not support IMS. * @hide */ public static final int REASON_NON_IMS_CAPABLE_NETWORK = 4; /** * IMS service should be deregistered from the network before turning off the radio. * @hide */ public static final int REASON_RADIO_POWER_OFF = 5; /** * Since the handover is failed or not allowed, the data service for IMS shall be * disconnected. * @hide */ public static final int REASON_HANDOVER_FAILED = 6; /** * The network is changed to a network that does not support voice over IMS. * @hide */ public static final int REASON_VOPS_NOT_SUPPORTED = 7; private Executor mExecutor; /** * Create a new ImsRegistration. *

* Method stubs called from the framework will be called asynchronously. To specify the * {@link Executor} that the methods stubs will be called, use * {@link ImsRegistrationImplBase#ImsRegistrationImplBase(Executor)} instead. * @hide This API is not part of the Android public SDK API */ @SystemApi public ImsRegistrationImplBase() { super(); } /** * Create a ImsRegistration 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 ImsRegistration. * @hide This API is not part of the Android public SDK API */ @SystemApi public ImsRegistrationImplBase(@NonNull Executor executor) { super(); mExecutor = executor; } private final IImsRegistration mBinder = new IImsRegistration.Stub() { @Override public @ImsRegistrationTech int getRegistrationTechnology() throws RemoteException { return executeMethodAsyncForResult(() -> (mRegistrationAttributes == null) ? REGISTRATION_TECH_NONE : mRegistrationAttributes.getRegistrationTechnology(), "getRegistrationTechnology"); } @Override public void addRegistrationCallback(IImsRegistrationCallback c) throws RemoteException { AtomicReference exceptionRef = new AtomicReference<>(); executeMethodAsync(() -> { try { ImsRegistrationImplBase.this.addRegistrationCallback(c); } catch (RemoteException e) { exceptionRef.set(e); } }, "addRegistrationCallback"); if (exceptionRef.get() != null) { throw exceptionRef.get(); } } @Override public void addEmergencyRegistrationCallback(IImsRegistrationCallback c) throws RemoteException { AtomicReference exceptionRef = new AtomicReference<>(); executeMethodAsync(() -> { try { ImsRegistrationImplBase.this.addEmergencyRegistrationCallback(c); } catch (RemoteException e) { exceptionRef.set(e); } }, "addEmergencyRegistrationCallback"); if (exceptionRef.get() != null) { throw exceptionRef.get(); } } @Override public void removeEmergencyRegistrationCallback(IImsRegistrationCallback c) throws RemoteException { executeMethodAsync(() -> ImsRegistrationImplBase.this.removeEmergencyRegistrationCallback(c), "removeEmergencyRegistrationCallback"); } @Override public void removeRegistrationCallback(IImsRegistrationCallback c) throws RemoteException { executeMethodAsync(() -> ImsRegistrationImplBase.this.removeRegistrationCallback(c), "removeRegistrationCallback"); } @Override public void triggerFullNetworkRegistration(int sipCode, String sipReason) { executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this .triggerFullNetworkRegistration(sipCode, sipReason), "triggerFullNetworkRegistration"); } @Override public void triggerUpdateSipDelegateRegistration() { executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this .updateSipDelegateRegistration(), "triggerUpdateSipDelegateRegistration"); } @Override public void triggerSipDelegateDeregistration() { executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this .triggerSipDelegateDeregistration(), "triggerSipDelegateDeregistration"); } @Override public void triggerDeregistration(@ImsDeregistrationReason int reason) { executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this .triggerDeregistration(reason), "triggerDeregistration"); } // 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, "ImsRegistrationImplBase 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, "ImsRegistrationImplBase 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, "ImsRegistrationImplBase Binder - " + errorLogName + " exception: " + e.getMessage()); throw new RemoteException(e.getMessage()); } } }; private final RemoteCallbackListExt mCallbacks = new RemoteCallbackListExt<>(); private final RemoteCallbackListExt mEmergencyCallbacks = new RemoteCallbackListExt<>(); private final Object mLock = new Object(); // Locked on mLock private ImsRegistrationAttributes mRegistrationAttributes; private ImsRegistrationAttributes mEmergencyRegistrationAttributes; // Locked on mLock private int mRegistrationState = REGISTRATION_STATE_UNKNOWN; private int mEmergencyRegistrationState = REGISTRATION_STATE_UNKNOWN; // Locked on mLock, create unspecified disconnect cause. private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo(); private ImsReasonInfo mEmergencyLastDisconnectCause = new ImsReasonInfo(); // Locked on mLock private int mLastDisconnectSuggestedAction = RegistrationManager.SUGGESTED_ACTION_NONE; private int mEmergencyLastDisconnectSuggestedAction = RegistrationManager.SUGGESTED_ACTION_NONE; private int mLastDisconnectRadioTech = REGISTRATION_TECH_NONE; private int mEmergencyLastDisconnectRadioTech = REGISTRATION_TECH_NONE; // We hold onto the uris each time they change so that we can send it to a callback when its // first added. private Uri[] mUris = new Uri[0]; private boolean mUrisSet = false; /** * @hide */ public final IImsRegistration getBinder() { return mBinder; } private void addRegistrationCallback(IImsRegistrationCallback c) throws RemoteException { // This is purposefully not synchronized with broadcastToCallbacksLocked because the // list of callbacks to notify is copied over from the original list modified here. I also // do not want to risk introducing a deadlock by using the same mCallbacks Object to // synchronize on outgoing and incoming operations. mCallbacks.register(c); updateNewCallbackWithState(c, false); } private void removeRegistrationCallback(IImsRegistrationCallback c) { // This is purposefully not synchronized with broadcastToCallbacksLocked because the // list of callbacks to notify is copied over from the original list modified here. I also // do not want to risk introducing a deadlock by using the same mCallbacks Object to // synchronize on outgoing and incoming operations. mCallbacks.unregister(c); } private void addEmergencyRegistrationCallback(IImsRegistrationCallback c) throws RemoteException { mEmergencyCallbacks.register(c); updateNewCallbackWithState(c, true); } private void removeEmergencyRegistrationCallback(IImsRegistrationCallback c) { mEmergencyCallbacks.unregister(c); } /** * Called by the framework to request that the ImsService perform the network registration * of all SIP delegates associated with this ImsService. *

* If the SIP delegate feature tag configuration has changed, then this method will be * called in order to let the ImsService know that it can pick up these changes in the IMS * registration. * @hide This API is not part of the Android public SDK API */ @SystemApi public void updateSipDelegateRegistration() { // Stub implementation, ImsService should implement this } /** * Called by the framework to request that the ImsService perform the network deregistration of * all SIP delegates associated with this ImsService. *

* This is typically called in situations where the user has changed the configuration of the * device (for example, the default messaging application) and the framework is reconfiguring * the tags associated with each IMS application. *

* This should not affect the registration of features managed by the ImsService itself, such as * feature tags related to MMTEL registration. * @hide This API is not part of the Android public SDK API */ @SystemApi public void triggerSipDelegateDeregistration() { // Stub implementation, ImsService should implement this } /** * Called by the framework to notify the ImsService that a SIP delegate connection has received * a SIP message containing a permanent failure response (such as a 403) or an indication that a * SIP response timer has timed out in response to an outgoing SIP message. This method will be * called when this condition occurs to trigger the ImsService to tear down the full IMS * registration and re-register again. * * @param sipCode The SIP error code that represents a permanent failure that was received in * response to a request generated by the IMS application. See RFC3261 7.2 for the general * classes of responses available here, however the codes that generate this condition may * be carrier specific. * @param sipReason The reason associated with the SIP error code. {@code null} if there was no * reason associated with the error. * @hide This API is not part of the Android public SDK API */ @SystemApi public void triggerFullNetworkRegistration(@IntRange(from = 100, to = 699) int sipCode, @Nullable String sipReason) { // Stub implementation, ImsService should implement this } /** * Requests IMS stack to perform graceful IMS deregistration before radio performing * network detach in the events of SIM remove, refresh or and so on. The radio waits for * the IMS deregistration, which will be notified by telephony via * {@link android.hardware.radio.ims.IRadioIms#updateImsRegistrationInfo()}, * or a certain timeout interval to start the network detach procedure. * * @param reason the reason why the deregistration is triggered. * @hide */ public void triggerDeregistration(@ImsDeregistrationReason int reason) { // Stub Implementation, can be overridden by ImsService } /** * Notify the framework that the device is connected to the IMS network. * * @param imsRadioTech the radio access technology. * @hide This API is not part of the Android public SDK API */ @SystemApi public final void onRegistered(@ImsRegistrationTech int imsRadioTech) { onRegistered(new ImsRegistrationAttributes.Builder(imsRadioTech).build()); } /** * Notify the framework that the device is connected to the IMS network. * * @param attributes The attributes associated with the IMS registration. * @hide This API is not part of the Android public SDK API */ @SystemApi public final void onRegistered(@NonNull ImsRegistrationAttributes attributes) { boolean isEmergency = isEmergency(attributes); if (isEmergency) { updateToEmergencyState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED); } else { updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED); } broadcastToCallbacksLocked((c) -> { try { c.onRegistered(attributes); } catch (RemoteException e) { Log.w(LOG_TAG, e + "onRegistered(int, Set) - Skipping callback."); } }, isEmergency); } /** * Notify the framework that the device is trying to connect the IMS network. * * @param imsRadioTech the radio access technology. * @hide This API is not part of the Android public SDK API */ @SystemApi public final void onRegistering(@ImsRegistrationTech int imsRadioTech) { onRegistering(new ImsRegistrationAttributes.Builder(imsRadioTech).build()); } /** * Notify the framework that the device is trying to connect the IMS network. * * @param attributes The attributes associated with the IMS registration. * @hide This API is not part of the Android public SDK API */ @SystemApi public final void onRegistering(@NonNull ImsRegistrationAttributes attributes) { boolean isEmergency = isEmergency(attributes); if (isEmergency) { updateToEmergencyState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING); } else { updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING); } broadcastToCallbacksLocked((c) -> { try { c.onRegistering(attributes); } catch (RemoteException e) { Log.w(LOG_TAG, e + "onRegistering(int, Set) - Skipping callback."); } }, isEmergency); } /** * Notify the framework that the device is disconnected from the IMS network. *

* Note: Prior to calling {@link #onDeregistered(ImsReasonInfo)}, you should ensure that any * changes to {@link android.telephony.ims.feature.ImsFeature} capability availability is sent * to the framework. For example, * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO} * and * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE} * may be set to unavailable to ensure the framework knows these services are no longer * available due to de-registration. If you do not report capability changes impacted by * de-registration, the framework will not know which features are no longer available as a * result. * * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. * @hide This API is not part of the Android public SDK API */ @SystemApi public final void onDeregistered(ImsReasonInfo info) { // Default impl to keep backwards compatibility with old implementations onDeregistered(info, RegistrationManager.SUGGESTED_ACTION_NONE, REGISTRATION_TECH_NONE); } /** * Notify the framework that the device is disconnected from the IMS network. *

* Note: Prior to calling {@link #onDeregistered(ImsReasonInfo,int)}, you should ensure that any * changes to {@link android.telephony.ims.feature.ImsFeature} capability availability is sent * to the framework. For example, * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO} * and * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE} * may be set to unavailable to ensure the framework knows these services are no longer * available due to de-registration. If you do not report capability changes impacted by * de-registration, the framework will not know which features are no longer available as a * result. * * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. * @param suggestedAction the expected behavior of radio protocol stack. * @param imsRadioTech the network type on which IMS registration has failed. * @hide This API is not part of the Android public SDK API */ @SystemApi public final void onDeregistered(@Nullable ImsReasonInfo info, @RegistrationManager.SuggestedAction int suggestedAction, @ImsRegistrationTech int imsRadioTech) { // Impl to keep backwards compatibility with old implementations ImsRegistrationAttributes attributes = mRegistrationAttributes != null ? new ImsRegistrationAttributes(imsRadioTech, mRegistrationAttributes.getTransportType(), mRegistrationAttributes.getAttributeFlags(), mRegistrationAttributes.getFeatureTags()) : new ImsRegistrationAttributes.Builder(imsRadioTech).build(); onDeregistered(info, suggestedAction, attributes); } /** * Notify the framework that the device is disconnected from the IMS network. *

* Note: Prior to calling {@link #onDeregistered(ImsReasonInfo,int)}, you should ensure that any * changes to {@link android.telephony.ims.feature.ImsFeature} capability availability is sent * to the framework. For example, * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO} * and * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE} * may be set to unavailable to ensure the framework knows these services are no longer * available due to de-registration. If you do not report capability changes impacted by * de-registration, the framework will not know which features are no longer available as a * result. * * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. * @param suggestedAction the expected behavior of radio protocol stack. * @param attributes The attributes associated with the IMS registration * @hide This API is not part of the Android public SDK API */ @SystemApi @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE) public final void onDeregistered(@Nullable ImsReasonInfo info, @RegistrationManager.SuggestedAction int suggestedAction, @NonNull ImsRegistrationAttributes attributes) { boolean isEmergency = isEmergency(attributes); int imsRadioTech = attributes.getRegistrationTechnology(); if (isEmergency) { updateToDisconnectedEmergencyState(info, suggestedAction, imsRadioTech); } else { updateToDisconnectedState(info, suggestedAction, imsRadioTech); } // ImsReasonInfo should never be null. final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo(); broadcastToCallbacksLocked((c) -> { try { c.onDeregistered(reasonInfo, suggestedAction, imsRadioTech); } catch (RemoteException e) { Log.w(LOG_TAG, e + "onDeregistered() - Skipping callback."); } }, isEmergency); } /** * Notify the framework that the device is disconnected from the IMS network. *

* Note: Before calling {@link #onDeregistered(ImsReasonInfo, SipDetails)}, ImsService should * ensure that any changes to {@link android.telephony.ims.feature.ImsFeature} capability * availability is sent to the framework. * For example, * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO} * and * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE} * may be set to unavailable to ensure the framework knows these services are no longer * available due to de-registration. If ImsService do not report capability changes impacted * by de-registration, the framework will not know which features are no longer available as a * result. * * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. * @param details the {@link SipDetails} related to disconnected Ims registration * @hide This API is not part of the Android public SDK API */ @SystemApi public final void onDeregistered(@Nullable ImsReasonInfo info, @NonNull SipDetails details) { onDeregistered(info, RegistrationManager.SUGGESTED_ACTION_NONE, REGISTRATION_TECH_NONE, details); } /** * Notify the framework that the device is disconnected from the IMS network. *

* Note: Before calling {@link #onDeregistered(ImsReasonInfo, SipDetails)}, ImsService should * ensure that any changes to {@link android.telephony.ims.feature.ImsFeature} capability * availability is sent to the framework. * For example, * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO} * and * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE} * may be set to unavailable to ensure the framework knows these services are no longer * available due to de-registration. If ImsService do not report capability changes impacted * by de-registration, the framework will not know which features are no longer available as a * result. * * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. * @param suggestedAction the expected behavior of radio protocol stack. * @param imsRadioTech the network type on which IMS registration has failed. * @param details the {@link SipDetails} related to disconnected Ims registration * @hide This API is not part of the Android public SDK API */ @SystemApi public final void onDeregistered(@Nullable ImsReasonInfo info, @RegistrationManager.SuggestedAction int suggestedAction, @ImsRegistrationTech int imsRadioTech, @NonNull SipDetails details) { updateToDisconnectedState(info, suggestedAction, imsRadioTech); // ImsReasonInfo should never be null. final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo(); broadcastToCallbacksLocked((c) -> { try { c.onDeregisteredWithDetails(reasonInfo, suggestedAction, imsRadioTech, details); } catch (RemoteException e) { Log.w(LOG_TAG, e + "onDeregistered() - Skipping callback."); } }, false); } /** * Notify the framework that the handover from the current radio technology to the technology * defined in {@code imsRadioTech} has failed. * @param imsRadioTech The technology that has failed to be changed. Valid values are * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and * {@link #REGISTRATION_TECH_CROSS_SIM}. * @param info The {@link ImsReasonInfo} for the failure to change technology. * @hide This API is not part of the Android public SDK API */ @SystemApi public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech, ImsReasonInfo info) { ImsRegistrationAttributes attributes = mRegistrationAttributes != null ? new ImsRegistrationAttributes(imsRadioTech, mRegistrationAttributes.getTransportType(), mRegistrationAttributes.getAttributeFlags(), mRegistrationAttributes.getFeatureTags()) : new ImsRegistrationAttributes.Builder(imsRadioTech).build(); onTechnologyChangeFailed(info, attributes); } /** * Notify the framework that the handover from the current radio technology to the technology * defined in {@code imsRadioTech} has failed. * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and * {@link #REGISTRATION_TECH_CROSS_SIM}. * @param info The {@link ImsReasonInfo} for the failure to change technology. * @param attributes The attributes associated with the IMS registration * @hide This API is not part of the Android public SDK API */ @SystemApi @FlaggedApi(Flags.FLAG_EMERGENCY_REGISTRATION_STATE) public final void onTechnologyChangeFailed(@Nullable ImsReasonInfo info, @NonNull ImsRegistrationAttributes attributes) { boolean isEmergency = isEmergency(attributes); int imsRadioTech = attributes.getRegistrationTechnology(); final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo(); broadcastToCallbacksLocked(c -> { try { c.onTechnologyChangeFailed(imsRadioTech, reasonInfo); } catch (RemoteException e) { Log.w(LOG_TAG, e + "onTechnologyChangeFailed() - Skipping callback."); } }, isEmergency); } /** * Invoked when the {@link Uri}s associated to this device's subscriber have changed. * These {@link Uri}s' are filtered out during conference calls. * * The {@link Uri}s are not guaranteed to be different between subsequent calls. * @param uris changed uris * @hide This API is not part of the Android public SDK API */ @SystemApi public final void onSubscriberAssociatedUriChanged(Uri[] uris) { synchronized (mLock) { mUris = ArrayUtils.cloneOrNull(uris); mUrisSet = true; } broadcastToCallbacksLocked((c) -> onSubscriberAssociatedUriChanged(c, uris), false); } private boolean isEmergency(ImsRegistrationAttributes attributes) { if (attributes == null) { return false; } else { return (attributes.getAttributeFlags() & ImsRegistrationAttributes.ATTR_REGISTRATION_TYPE_EMERGENCY) != 0; } } /** * Broadcast the specified operation in ta synchronized manner so that multiple threads do not * try to call broadcast at the same time, which will generate an error. * @param c The Consumer lambda method containing the callback to call. */ private void broadcastToCallbacksLocked(Consumer c, boolean isEmergency) { // One broadcast can happen at a time, so synchronize threads so only one // beginBroadcast/endBroadcast happens at a time. if (isEmergency) { synchronized (mEmergencyCallbacks) { mEmergencyCallbacks.broadcastAction(c); } } else { synchronized (mCallbacks) { mCallbacks.broadcastAction(c); } } } private void onSubscriberAssociatedUriChanged(IImsRegistrationCallback callback, Uri[] uris) { try { callback.onSubscriberAssociatedUriChanged(uris); } catch (RemoteException e) { Log.w(LOG_TAG, e + "onSubscriberAssociatedUriChanged() - Skipping callback."); } } private void updateToState(ImsRegistrationAttributes attributes, int newState) { synchronized (mLock) { mRegistrationAttributes = attributes; mRegistrationState = newState; mLastDisconnectCause = null; mLastDisconnectSuggestedAction = RegistrationManager.SUGGESTED_ACTION_NONE; mLastDisconnectRadioTech = REGISTRATION_TECH_NONE; } } private void updateToEmergencyState(ImsRegistrationAttributes attributes, int newState) { synchronized (mLock) { mEmergencyRegistrationAttributes = attributes; mEmergencyRegistrationState = newState; mEmergencyLastDisconnectCause = null; mEmergencyLastDisconnectSuggestedAction = RegistrationManager.SUGGESTED_ACTION_NONE; mEmergencyLastDisconnectRadioTech = REGISTRATION_TECH_NONE; } } private void updateToDisconnectedState(ImsReasonInfo info, @RegistrationManager.SuggestedAction int suggestedAction, @ImsRegistrationTech int imsRadioTech) { synchronized (mLock) { //We don't want to send this info over if we are disconnected mUrisSet = false; mUris = null; updateToState(new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_NONE).build(), RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED); if (info != null) { mLastDisconnectCause = info; mLastDisconnectSuggestedAction = suggestedAction; mLastDisconnectRadioTech = imsRadioTech; } else { Log.w(LOG_TAG, "updateToDisconnectedState: no ImsReasonInfo provided."); mLastDisconnectCause = new ImsReasonInfo(); } } } private void updateToDisconnectedEmergencyState(ImsReasonInfo info, @RegistrationManager.SuggestedAction int suggestedAction, @ImsRegistrationTech int imsRadioTech) { synchronized (mLock) { //We don't want to send this info over if we are disconnected mUrisSet = false; mUris = null; updateToEmergencyState(new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_NONE) .build(), RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED); if (info != null) { mEmergencyLastDisconnectCause = info; mEmergencyLastDisconnectSuggestedAction = suggestedAction; mEmergencyLastDisconnectRadioTech = imsRadioTech; } else { Log.w(LOG_TAG, "updateToDisconnectedState: no ImsReasonInfo provided."); mEmergencyLastDisconnectCause = new ImsReasonInfo(); } } } /** * @param c the newly registered callback that will be updated with the current registration * state. */ private void updateNewCallbackWithState(IImsRegistrationCallback c, boolean isEmergencyCallback) throws RemoteException { int state; ImsRegistrationAttributes attributes; ImsReasonInfo disconnectInfo; int suggestedAction; int imsDisconnectRadioTech; boolean urisSet; Uri[] uris; synchronized (mLock) { state = isEmergencyCallback ? mEmergencyRegistrationState : mRegistrationState; attributes = isEmergencyCallback ? mEmergencyRegistrationAttributes : mRegistrationAttributes; disconnectInfo = isEmergencyCallback ? mEmergencyLastDisconnectCause : mLastDisconnectCause; suggestedAction = isEmergencyCallback ? mEmergencyLastDisconnectSuggestedAction : mLastDisconnectSuggestedAction; imsDisconnectRadioTech = isEmergencyCallback ? mEmergencyLastDisconnectRadioTech : mLastDisconnectRadioTech; urisSet = mUrisSet; uris = mUris; } switch (state) { case RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED: { c.onDeregistered(disconnectInfo, suggestedAction, imsDisconnectRadioTech); break; } case RegistrationManager.REGISTRATION_STATE_REGISTERING: { c.onRegistering(attributes); break; } case RegistrationManager.REGISTRATION_STATE_REGISTERED: { c.onRegistered(attributes); break; } case REGISTRATION_STATE_UNKNOWN: { // Do not callback if the state has not been updated yet by the ImsService. break; } } if (urisSet) { onSubscriberAssociatedUriChanged(c, uris); } } /** * Set default Executor from ImsService. * @param executor The default executor for the framework to use when executing the methods * overridden by the implementation of Registration. * @hide */ public final void setDefaultExecutor(@NonNull Executor executor) { if (mExecutor == null) { mExecutor = executor; } } /** * Clear the cached data when the subscription is no longer valid * such as when a sim is removed. * @hide */ public final void clearRegistrationCache() { synchronized (mLock) { mUris = null; mUrisSet = false; } } }