1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package android.telephony.ims.stub;
18 
19 import android.annotation.IntDef;
20 import android.annotation.IntRange;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SystemApi;
24 import android.net.Uri;
25 import android.os.RemoteException;
26 import android.telephony.ims.ImsReasonInfo;
27 import android.telephony.ims.ImsRegistrationAttributes;
28 import android.telephony.ims.RegistrationManager;
29 import android.telephony.ims.SipDetails;
30 import android.telephony.ims.aidl.IImsRegistration;
31 import android.telephony.ims.aidl.IImsRegistrationCallback;
32 import android.util.Log;
33 
34 import com.android.internal.telephony.util.RemoteCallbackListExt;
35 import com.android.internal.telephony.util.TelephonyUtils;
36 import com.android.internal.util.ArrayUtils;
37 
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.util.concurrent.CancellationException;
41 import java.util.concurrent.CompletableFuture;
42 import java.util.concurrent.CompletionException;
43 import java.util.concurrent.ExecutionException;
44 import java.util.concurrent.Executor;
45 import java.util.concurrent.atomic.AtomicReference;
46 import java.util.function.Consumer;
47 import java.util.function.Supplier;
48 
49 /**
50  * Controls IMS registration for this ImsService and notifies the framework when the IMS
51  * registration for this ImsService has changed status.
52  * <p>
53  * Note: There is no guarantee on the thread that the calls from the framework will be called on. It
54  * is the implementors responsibility to handle moving the calls to a working thread if required.
55  */
56 public class ImsRegistrationImplBase {
57 
58     private static final String LOG_TAG = "ImsRegistrationImplBase";
59 
60     /**
61      * @hide
62      */
63     // Defines the underlying radio technology type that we have registered for IMS over.
64     @IntDef(value = {
65                     REGISTRATION_TECH_NONE,
66                     REGISTRATION_TECH_LTE,
67                     REGISTRATION_TECH_IWLAN,
68                     REGISTRATION_TECH_CROSS_SIM,
69                     REGISTRATION_TECH_NR,
70                     REGISTRATION_TECH_3G
71             })
72     @Retention(RetentionPolicy.SOURCE)
73     public @interface ImsRegistrationTech {}
74     /**
75      * No registration technology specified, used when we are not registered.
76      */
77     public static final int REGISTRATION_TECH_NONE = -1;
78     /**
79      * This ImsService is registered to IMS via LTE.
80      */
81     public static final int REGISTRATION_TECH_LTE = 0;
82     /**
83      * This ImsService is registered to IMS via IWLAN.
84      */
85     public static final int REGISTRATION_TECH_IWLAN = 1;
86 
87     /**
88      * This ImsService is registered to IMS via internet over second subscription.
89      */
90     public static final int REGISTRATION_TECH_CROSS_SIM = 2;
91 
92     /**
93      * This ImsService is registered to IMS via NR.
94      */
95     public static final int REGISTRATION_TECH_NR = 3;
96 
97     /**
98      * This ImsService is registered to IMS via 3G.
99      */
100     public static final int REGISTRATION_TECH_3G = 4;
101 
102     /**
103      * This is used to check the upper range of registration tech
104      * @hide
105      */
106     public static final int REGISTRATION_TECH_MAX = REGISTRATION_TECH_3G + 1;
107 
108     // Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current
109     // state.
110     // The unknown state is set as the initialization state. This is so that we do not call back
111     // with NOT_REGISTERED in the case where the ImsService has not updated the registration state
112     // yet.
113     private static final int REGISTRATION_STATE_UNKNOWN = -1;
114 
115     /** @hide */
116     @Retention(RetentionPolicy.SOURCE)
117     @IntDef(
118         prefix = {"REASON_"},
119         value = {
120             REASON_UNKNOWN,
121             REASON_SIM_REMOVED,
122             REASON_SIM_REFRESH,
123             REASON_ALLOWED_NETWORK_TYPES_CHANGED,
124             REASON_NON_IMS_CAPABLE_NETWORK,
125             REASON_RADIO_POWER_OFF,
126             REASON_HANDOVER_FAILED,
127             REASON_VOPS_NOT_SUPPORTED,
128         })
129     public @interface ImsDeregistrationReason{}
130 
131     /**
132      * Unspecified reason.
133      * @hide
134      */
135     public static final int REASON_UNKNOWN = 0;
136 
137     /**
138      * Since SIM is removed, the credentials for IMS service is also removed.
139      * @hide
140      */
141     public static final int REASON_SIM_REMOVED = 1;
142 
143     /**
144      * Detach from the network shall be performed due to the SIM refresh. IMS service should be
145      * deregistered before that procedure.
146      * @hide
147      */
148     public static final int REASON_SIM_REFRESH = 2;
149 
150     /**
151      * The allowed network types have changed, resulting in a network type
152      * that does not support IMS.
153      * @hide
154      */
155     public static final int REASON_ALLOWED_NETWORK_TYPES_CHANGED = 3;
156 
157    /**
158      * The device camped on a network that does not support IMS.
159      * @hide
160      */
161     public static final int REASON_NON_IMS_CAPABLE_NETWORK = 4;
162 
163     /**
164      * IMS service should be deregistered from the network before turning off the radio.
165      * @hide
166      */
167     public static final int REASON_RADIO_POWER_OFF = 5;
168 
169     /**
170      * Since the handover is failed or not allowed, the data service for IMS shall be
171      * disconnected.
172      * @hide
173      */
174     public static final int REASON_HANDOVER_FAILED = 6;
175 
176     /**
177      * The network is changed to a network that does not support voice over IMS.
178      * @hide
179      */
180     public static final int REASON_VOPS_NOT_SUPPORTED = 7;
181 
182     private Executor mExecutor;
183 
184     /**
185      * Create a new ImsRegistration.
186      * <p>
187      * Method stubs called from the framework will be called asynchronously. To specify the
188      * {@link Executor} that the methods stubs will be called, use
189      * {@link ImsRegistrationImplBase#ImsRegistrationImplBase(Executor)} instead.
190      * @hide This API is not part of the Android public SDK API
191      */
192     @SystemApi
ImsRegistrationImplBase()193     public ImsRegistrationImplBase() {
194         super();
195     }
196 
197     /**
198      * Create a ImsRegistration using the Executor specified for methods being called by the
199      * framework.
200      * @param executor The executor for the framework to use when executing the methods overridden
201      * by the implementation of ImsRegistration.
202      * @hide This API is not part of the Android public SDK API
203      */
204     @SystemApi
ImsRegistrationImplBase(@onNull Executor executor)205     public ImsRegistrationImplBase(@NonNull Executor executor) {
206         super();
207         mExecutor = executor;
208     }
209 
210     private final IImsRegistration mBinder = new IImsRegistration.Stub() {
211 
212         @Override
213         public @ImsRegistrationTech int getRegistrationTechnology() throws RemoteException {
214             return executeMethodAsyncForResult(() -> (mRegistrationAttributes == null)
215                     ? REGISTRATION_TECH_NONE : mRegistrationAttributes.getRegistrationTechnology(),
216                     "getRegistrationTechnology");
217         }
218 
219         @Override
220         public void addRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
221             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
222             executeMethodAsync(() -> {
223                 try {
224                     ImsRegistrationImplBase.this.addRegistrationCallback(c);
225                 } catch (RemoteException e) {
226                     exceptionRef.set(e);
227                 }
228             }, "addRegistrationCallback");
229 
230             if (exceptionRef.get() != null) {
231                 throw exceptionRef.get();
232             }
233         }
234 
235         @Override
236         public void removeRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
237             executeMethodAsync(() -> ImsRegistrationImplBase.this.removeRegistrationCallback(c),
238                     "removeRegistrationCallback");
239         }
240 
241         @Override
242         public void triggerFullNetworkRegistration(int sipCode, String sipReason) {
243             executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this
244                     .triggerFullNetworkRegistration(sipCode, sipReason),
245                     "triggerFullNetworkRegistration");
246         }
247 
248         @Override
249         public void triggerUpdateSipDelegateRegistration() {
250             executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this
251                     .updateSipDelegateRegistration(), "triggerUpdateSipDelegateRegistration");
252         }
253 
254         @Override
255         public void triggerSipDelegateDeregistration() {
256             executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this
257                     .triggerSipDelegateDeregistration(), "triggerSipDelegateDeregistration");
258         }
259 
260         @Override
261         public void triggerDeregistration(@ImsDeregistrationReason int reason) {
262             executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this
263                     .triggerDeregistration(reason), "triggerDeregistration");
264         }
265 
266         // Call the methods with a clean calling identity on the executor and wait indefinitely for
267         // the future to return.
268         private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
269             try {
270                 CompletableFuture.runAsync(
271                         () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
272             } catch (CancellationException | CompletionException e) {
273                 Log.w(LOG_TAG, "ImsRegistrationImplBase Binder - " + errorLogName + " exception: "
274                         + e.getMessage());
275                 throw new RemoteException(e.getMessage());
276             }
277         }
278 
279         private void executeMethodAsyncNoException(Runnable r, String errorLogName) {
280             try {
281                 CompletableFuture.runAsync(
282                         () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
283             } catch (CancellationException | CompletionException e) {
284                 Log.w(LOG_TAG, "ImsRegistrationImplBase Binder - " + errorLogName + " exception: "
285                         + e.getMessage());
286             }
287         }
288 
289         private <T> T executeMethodAsyncForResult(Supplier<T> r,
290                 String errorLogName) throws RemoteException {
291             CompletableFuture<T> future = CompletableFuture.supplyAsync(
292                     () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
293             try {
294                 return future.get();
295             } catch (ExecutionException | InterruptedException e) {
296                 Log.w(LOG_TAG, "ImsRegistrationImplBase Binder - " + errorLogName + " exception: "
297                         + e.getMessage());
298                 throw new RemoteException(e.getMessage());
299             }
300         }
301     };
302 
303     private final RemoteCallbackListExt<IImsRegistrationCallback> mCallbacks =
304             new RemoteCallbackListExt<>();
305     private final Object mLock = new Object();
306     // Locked on mLock
307     private ImsRegistrationAttributes mRegistrationAttributes;
308     // Locked on mLock
309     private int mRegistrationState = REGISTRATION_STATE_UNKNOWN;
310     // Locked on mLock, create unspecified disconnect cause.
311     private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo();
312     // Locked on mLock
313     private int mLastDisconnectSuggestedAction = RegistrationManager.SUGGESTED_ACTION_NONE;
314     private int mLastDisconnectRadioTech = REGISTRATION_TECH_NONE;
315 
316     // We hold onto the uris each time they change so that we can send it to a callback when its
317     // first added.
318     private Uri[] mUris = new Uri[0];
319     private boolean mUrisSet = false;
320 
321     /**
322      * @hide
323      */
getBinder()324     public final IImsRegistration getBinder() {
325         return mBinder;
326     }
327 
addRegistrationCallback(IImsRegistrationCallback c)328     private void addRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
329         // This is purposefully not synchronized with broadcastToCallbacksLocked because the
330         // list of callbacks to notify is copied over from the original list modified here. I also
331         // do not want to risk introducing a deadlock by using the same mCallbacks Object to
332         // synchronize on outgoing and incoming operations.
333         mCallbacks.register(c);
334         updateNewCallbackWithState(c);
335     }
336 
removeRegistrationCallback(IImsRegistrationCallback c)337     private void removeRegistrationCallback(IImsRegistrationCallback c) {
338         // This is purposefully not synchronized with broadcastToCallbacksLocked because the
339         // list of callbacks to notify is copied over from the original list modified here. I also
340         // do not want to risk introducing a deadlock by using the same mCallbacks Object to
341         // synchronize on outgoing and incoming operations.
342         mCallbacks.unregister(c);
343     }
344 
345     /**
346      * Called by the framework to request that the ImsService perform the network registration
347      * of all SIP delegates associated with this ImsService.
348      * <p>
349      * If the SIP delegate feature tag configuration has changed, then this method will be
350      * called in order to let the ImsService know that it can pick up these changes in the IMS
351      * registration.
352      * @hide This API is not part of the Android public SDK API
353      */
354     @SystemApi
updateSipDelegateRegistration()355     public void updateSipDelegateRegistration() {
356         // Stub implementation, ImsService should implement this
357     }
358 
359 
360     /**
361      * Called by the framework to request that the ImsService perform the network deregistration of
362      * all SIP delegates associated with this ImsService.
363      * <p>
364      * This is typically called in situations where the user has changed the configuration of the
365      * device (for example, the default messaging application) and the framework is reconfiguring
366      * the tags associated with each IMS application.
367      * <p>
368      * This should not affect the registration of features managed by the ImsService itself, such as
369      * feature tags related to MMTEL registration.
370      * @hide This API is not part of the Android public SDK API
371      */
372     @SystemApi
triggerSipDelegateDeregistration()373     public void triggerSipDelegateDeregistration() {
374         // Stub implementation, ImsService should implement this
375     }
376 
377     /**
378      * Called by the framework to notify the ImsService that a SIP delegate connection has received
379      * a SIP message containing a permanent failure response (such as a 403) or an indication that a
380      * SIP response timer has timed out in response to an outgoing SIP message. This method will be
381      * called when this condition occurs to trigger the ImsService to tear down the full IMS
382      * registration and re-register again.
383      *
384      * @param sipCode The SIP error code that represents a permanent failure that was received in
385      *    response to a request generated by the IMS application. See RFC3261 7.2 for the general
386      *    classes of responses available here, however the codes that generate this condition may
387      *    be carrier specific.
388      * @param sipReason The reason associated with the SIP error code. {@code null} if there was no
389      *    reason associated with the error.
390      * @hide This API is not part of the Android public SDK API
391      */
392     @SystemApi
triggerFullNetworkRegistration(@ntRangefrom = 100, to = 699) int sipCode, @Nullable String sipReason)393     public void triggerFullNetworkRegistration(@IntRange(from = 100, to = 699) int sipCode,
394             @Nullable String sipReason) {
395         // Stub implementation, ImsService should implement this
396     }
397 
398     /**
399      * Requests IMS stack to perform graceful IMS deregistration before radio performing
400      * network detach in the events of SIM remove, refresh or and so on. The radio waits for
401      * the IMS deregistration, which will be notified by telephony via
402      * {@link android.hardware.radio.ims.IRadioIms#updateImsRegistrationInfo()},
403      * or a certain timeout interval to start the network detach procedure.
404      *
405      * @param reason the reason why the deregistration is triggered.
406      * @hide
407      */
triggerDeregistration(@msDeregistrationReason int reason)408     public void triggerDeregistration(@ImsDeregistrationReason int reason) {
409         // Stub Implementation, can be overridden by ImsService
410     }
411 
412     /**
413      * Notify the framework that the device is connected to the IMS network.
414      *
415      * @param imsRadioTech the radio access technology.
416      * @hide This API is not part of the Android public SDK API
417      */
418     @SystemApi
onRegistered(@msRegistrationTech int imsRadioTech)419     public final void onRegistered(@ImsRegistrationTech int imsRadioTech) {
420         onRegistered(new ImsRegistrationAttributes.Builder(imsRadioTech).build());
421     }
422 
423     /**
424      * Notify the framework that the device is connected to the IMS network.
425      *
426      * @param attributes The attributes associated with the IMS registration.
427      * @hide This API is not part of the Android public SDK API
428      */
429     @SystemApi
onRegistered(@onNull ImsRegistrationAttributes attributes)430     public final void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
431         updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED);
432         broadcastToCallbacksLocked((c) -> {
433             try {
434                 c.onRegistered(attributes);
435             } catch (RemoteException e) {
436                 Log.w(LOG_TAG, e + "onRegistered(int, Set) - Skipping callback.");
437             }
438         });
439     }
440 
441     /**
442      * Notify the framework that the device is trying to connect the IMS network.
443      *
444      * @param imsRadioTech the radio access technology.
445      * @hide This API is not part of the Android public SDK API
446      */
447     @SystemApi
onRegistering(@msRegistrationTech int imsRadioTech)448     public final void onRegistering(@ImsRegistrationTech int imsRadioTech) {
449         onRegistering(new ImsRegistrationAttributes.Builder(imsRadioTech).build());
450     }
451 
452     /**
453      * Notify the framework that the device is trying to connect the IMS network.
454      *
455      * @param attributes The attributes associated with the IMS registration.
456      * @hide This API is not part of the Android public SDK API
457      */
458     @SystemApi
onRegistering(@onNull ImsRegistrationAttributes attributes)459     public final void onRegistering(@NonNull ImsRegistrationAttributes attributes) {
460         updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING);
461         broadcastToCallbacksLocked((c) -> {
462             try {
463                 c.onRegistering(attributes);
464             } catch (RemoteException e) {
465                 Log.w(LOG_TAG, e + "onRegistering(int, Set) - Skipping callback.");
466             }
467         });
468     }
469 
470     /**
471      * Notify the framework that the device is disconnected from the IMS network.
472      * <p>
473      * Note: Prior to calling {@link #onDeregistered(ImsReasonInfo)}, you should ensure that any
474      * changes to {@link android.telephony.ims.feature.ImsFeature} capability availability is sent
475      * to the framework.  For example,
476      * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}
477      * and
478      * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}
479      * may be set to unavailable to ensure the framework knows these services are no longer
480      * available due to de-registration.  If you do not report capability changes impacted by
481      * de-registration, the framework will not know which features are no longer available as a
482      * result.
483      *
484      * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
485      * @hide This API is not part of the Android public SDK API
486      */
487     @SystemApi
onDeregistered(ImsReasonInfo info)488     public final void onDeregistered(ImsReasonInfo info) {
489         // Default impl to keep backwards compatibility with old implementations
490         onDeregistered(info, RegistrationManager.SUGGESTED_ACTION_NONE, REGISTRATION_TECH_NONE);
491     }
492 
493     /**
494      * Notify the framework that the device is disconnected from the IMS network.
495      * <p>
496      * Note: Prior to calling {@link #onDeregistered(ImsReasonInfo,int)}, you should ensure that any
497      * changes to {@link android.telephony.ims.feature.ImsFeature} capability availability is sent
498      * to the framework.  For example,
499      * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}
500      * and
501      * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}
502      * may be set to unavailable to ensure the framework knows these services are no longer
503      * available due to de-registration.  If you do not report capability changes impacted by
504      * de-registration, the framework will not know which features are no longer available as a
505      * result.
506      *
507      * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
508      * @param suggestedAction the expected behavior of radio protocol stack.
509      * @param imsRadioTech the network type on which IMS registration has failed.
510      * @hide This API is not part of the Android public SDK API
511      */
512     @SystemApi
onDeregistered(@ullable ImsReasonInfo info, @RegistrationManager.SuggestedAction int suggestedAction, @ImsRegistrationTech int imsRadioTech)513     public final void onDeregistered(@Nullable ImsReasonInfo info,
514             @RegistrationManager.SuggestedAction int suggestedAction,
515             @ImsRegistrationTech int imsRadioTech) {
516         updateToDisconnectedState(info, suggestedAction, imsRadioTech);
517         // ImsReasonInfo should never be null.
518         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
519         broadcastToCallbacksLocked((c) -> {
520             try {
521                 c.onDeregistered(reasonInfo, suggestedAction, imsRadioTech);
522             } catch (RemoteException e) {
523                 Log.w(LOG_TAG, e + "onDeregistered() - Skipping callback.");
524             }
525         });
526     }
527 
528     /**
529      * Notify the framework that the device is disconnected from the IMS network.
530      * <p>
531      * Note: Before calling {@link #onDeregistered(ImsReasonInfo, SipDetails)}, ImsService should
532      * ensure that any changes to {@link android.telephony.ims.feature.ImsFeature} capability
533      * availability is sent to the framework.
534      * For example,
535      * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}
536      * and
537      * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}
538      * may be set to unavailable to ensure the framework knows these services are no longer
539      * available due to de-registration.  If ImsService do not report capability changes impacted
540      * by de-registration, the framework will not know which features are no longer available as a
541      * result.
542      *
543      * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
544      * @param details the {@link SipDetails} related to disconnected Ims registration
545      * @hide This API is not part of the Android public SDK API
546      */
547     @SystemApi
onDeregistered(@ullable ImsReasonInfo info, @NonNull SipDetails details)548     public final void onDeregistered(@Nullable ImsReasonInfo info,
549             @NonNull SipDetails details) {
550         onDeregistered(info, RegistrationManager.SUGGESTED_ACTION_NONE, REGISTRATION_TECH_NONE,
551                 details);
552     }
553 
554     /**
555      * Notify the framework that the device is disconnected from the IMS network.
556      * <p>
557      * Note: Before calling {@link #onDeregistered(ImsReasonInfo, SipDetails)}, ImsService should
558      * ensure that any changes to {@link android.telephony.ims.feature.ImsFeature} capability
559      * availability is sent to the framework.
560      * For example,
561      * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}
562      * and
563      * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}
564      * may be set to unavailable to ensure the framework knows these services are no longer
565      * available due to de-registration.  If ImsService do not report capability changes impacted
566      * by de-registration, the framework will not know which features are no longer available as a
567      * result.
568      *
569      * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
570      * @param suggestedAction the expected behavior of radio protocol stack.
571      * @param details the {@link SipDetails} related to disconnected Ims registration
572      * @hide This API is not part of the Android public SDK API
573      */
574     @SystemApi
onDeregistered(@ullable ImsReasonInfo info, @RegistrationManager.SuggestedAction int suggestedAction, @ImsRegistrationTech int imsRadioTech, @NonNull SipDetails details)575     public final void onDeregistered(@Nullable ImsReasonInfo info,
576             @RegistrationManager.SuggestedAction int suggestedAction,
577             @ImsRegistrationTech int imsRadioTech, @NonNull SipDetails details) {
578         updateToDisconnectedState(info, suggestedAction, imsRadioTech);
579         // ImsReasonInfo should never be null.
580         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
581         broadcastToCallbacksLocked((c) -> {
582             try {
583                 c.onDeregisteredWithDetails(reasonInfo, suggestedAction, imsRadioTech, details);
584             } catch (RemoteException e) {
585                 Log.w(LOG_TAG, e + "onDeregistered() - Skipping callback.");
586             }
587         });
588     }
589 
590     /**
591      * Notify the framework that the handover from the current radio technology to the technology
592      * defined in {@code imsRadioTech} has failed.
593      * @param imsRadioTech The technology that has failed to be changed. Valid values are
594      * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and
595      * {@link #REGISTRATION_TECH_CROSS_SIM}.
596      * @param info The {@link ImsReasonInfo} for the failure to change technology.
597      * @hide This API is not part of the Android public SDK API
598      */
599     @SystemApi
onTechnologyChangeFailed(@msRegistrationTech int imsRadioTech, ImsReasonInfo info)600     public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
601             ImsReasonInfo info) {
602         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
603         broadcastToCallbacksLocked((c) -> {
604             try {
605                 c.onTechnologyChangeFailed(imsRadioTech, reasonInfo);
606             } catch (RemoteException e) {
607                 Log.w(LOG_TAG, e + "onTechnologyChangeFailed() - Skipping callback.");
608             }
609         });
610     }
611 
612     /**
613      * Invoked when the {@link Uri}s associated to this device's subscriber have changed.
614      * These {@link Uri}s' are filtered out during conference calls.
615      *
616      * The {@link Uri}s are not guaranteed to be different between subsequent calls.
617      * @param uris changed uris
618      * @hide This API is not part of the Android public SDK API
619      */
620     @SystemApi
onSubscriberAssociatedUriChanged(Uri[] uris)621     public final void onSubscriberAssociatedUriChanged(Uri[] uris) {
622         synchronized (mLock) {
623             mUris = ArrayUtils.cloneOrNull(uris);
624             mUrisSet = true;
625         }
626         broadcastToCallbacksLocked((c) -> onSubscriberAssociatedUriChanged(c, uris));
627     }
628 
629     /**
630      * Broadcast the specified operation in a synchronized manner so that multiple threads do not
631      * try to call broadcast at the same time, which will generate an error.
632      * @param c The Consumer lambda method containing the callback to call.
633      */
broadcastToCallbacksLocked(Consumer<IImsRegistrationCallback> c)634     private void broadcastToCallbacksLocked(Consumer<IImsRegistrationCallback> c) {
635         // One broadcast can happen at a time, so synchronize threads so only one
636         // beginBroadcast/endBroadcast happens at a time.
637         synchronized (mCallbacks) {
638             mCallbacks.broadcastAction(c);
639         }
640     }
641 
onSubscriberAssociatedUriChanged(IImsRegistrationCallback callback, Uri[] uris)642     private void onSubscriberAssociatedUriChanged(IImsRegistrationCallback callback, Uri[] uris) {
643         try {
644             callback.onSubscriberAssociatedUriChanged(uris);
645         } catch (RemoteException e) {
646             Log.w(LOG_TAG, e + "onSubscriberAssociatedUriChanged() - Skipping callback.");
647         }
648     }
649 
updateToState(ImsRegistrationAttributes attributes, int newState)650     private void updateToState(ImsRegistrationAttributes attributes, int newState) {
651         synchronized (mLock) {
652             mRegistrationAttributes = attributes;
653             mRegistrationState = newState;
654             mLastDisconnectCause = null;
655             mLastDisconnectSuggestedAction = RegistrationManager.SUGGESTED_ACTION_NONE;
656             mLastDisconnectRadioTech = REGISTRATION_TECH_NONE;
657         }
658     }
659 
updateToDisconnectedState(ImsReasonInfo info, @RegistrationManager.SuggestedAction int suggestedAction, @ImsRegistrationTech int imsRadioTech)660     private void updateToDisconnectedState(ImsReasonInfo info,
661             @RegistrationManager.SuggestedAction int suggestedAction,
662             @ImsRegistrationTech int imsRadioTech) {
663         synchronized (mLock) {
664             //We don't want to send this info over if we are disconnected
665             mUrisSet = false;
666             mUris = null;
667 
668             updateToState(new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_NONE).build(),
669                     RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
670             if (info != null) {
671                 mLastDisconnectCause = info;
672                 mLastDisconnectSuggestedAction = suggestedAction;
673                 mLastDisconnectRadioTech = imsRadioTech;
674             } else {
675                 Log.w(LOG_TAG, "updateToDisconnectedState: no ImsReasonInfo provided.");
676                 mLastDisconnectCause = new ImsReasonInfo();
677             }
678         }
679     }
680 
681     /**
682      * @param c the newly registered callback that will be updated with the current registration
683      *         state.
684      */
updateNewCallbackWithState(IImsRegistrationCallback c)685     private void updateNewCallbackWithState(IImsRegistrationCallback c)
686             throws RemoteException {
687         int state;
688         ImsRegistrationAttributes attributes;
689         ImsReasonInfo disconnectInfo;
690         int suggestedAction;
691         int imsDisconnectRadioTech;
692         boolean urisSet;
693         Uri[] uris;
694         synchronized (mLock) {
695             state = mRegistrationState;
696             attributes = mRegistrationAttributes;
697             disconnectInfo = mLastDisconnectCause;
698             suggestedAction = mLastDisconnectSuggestedAction;
699             imsDisconnectRadioTech = mLastDisconnectRadioTech;
700             urisSet = mUrisSet;
701             uris = mUris;
702         }
703         switch (state) {
704             case RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED: {
705                 c.onDeregistered(disconnectInfo, suggestedAction, imsDisconnectRadioTech);
706                 break;
707             }
708             case RegistrationManager.REGISTRATION_STATE_REGISTERING: {
709                 c.onRegistering(attributes);
710                 break;
711             }
712             case RegistrationManager.REGISTRATION_STATE_REGISTERED: {
713                 c.onRegistered(attributes);
714                 break;
715             }
716             case REGISTRATION_STATE_UNKNOWN: {
717                 // Do not callback if the state has not been updated yet by the ImsService.
718                 break;
719             }
720         }
721         if (urisSet) {
722             onSubscriberAssociatedUriChanged(c, uris);
723         }
724     }
725 
726     /**
727      * Set default Executor from ImsService.
728      * @param executor The default executor for the framework to use when executing the methods
729      * overridden by the implementation of Registration.
730      * @hide
731      */
setDefaultExecutor(@onNull Executor executor)732     public final void setDefaultExecutor(@NonNull Executor executor) {
733         if (mExecutor == null) {
734             mExecutor = executor;
735         }
736     }
737 
738     /**
739      * Clear the cached data when the subscription is no longer valid
740      * such as when a sim is removed.
741      * @hide
742      */
clearRegistrationCache()743     public final void clearRegistrationCache() {
744         synchronized (mLock) {
745             mUris = null;
746             mUrisSet = false;
747         }
748     }
749 }
750