• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.aidl.IImsRegistration;
30 import android.telephony.ims.aidl.IImsRegistrationCallback;
31 import android.util.Log;
32 
33 import com.android.internal.telephony.util.RemoteCallbackListExt;
34 import com.android.internal.telephony.util.TelephonyUtils;
35 import com.android.internal.util.ArrayUtils;
36 
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39 import java.util.concurrent.CancellationException;
40 import java.util.concurrent.CompletableFuture;
41 import java.util.concurrent.CompletionException;
42 import java.util.concurrent.ExecutionException;
43 import java.util.concurrent.Executor;
44 import java.util.concurrent.atomic.AtomicReference;
45 import java.util.function.Supplier;
46 
47 /**
48  * Controls IMS registration for this ImsService and notifies the framework when the IMS
49  * registration for this ImsService has changed status.
50  * <p>
51  * Note: There is no guarantee on the thread that the calls from the framework will be called on. It
52  * is the implementors responsibility to handle moving the calls to a working thread if required.
53  */
54 public class ImsRegistrationImplBase {
55 
56     private static final String LOG_TAG = "ImsRegistrationImplBase";
57 
58     /**
59      * @hide
60      */
61     // Defines the underlying radio technology type that we have registered for IMS over.
62     @IntDef(value = {
63                     REGISTRATION_TECH_NONE,
64                     REGISTRATION_TECH_LTE,
65                     REGISTRATION_TECH_IWLAN,
66                     REGISTRATION_TECH_CROSS_SIM,
67                     REGISTRATION_TECH_NR
68             })
69     @Retention(RetentionPolicy.SOURCE)
70     public @interface ImsRegistrationTech {}
71     /**
72      * No registration technology specified, used when we are not registered.
73      */
74     public static final int REGISTRATION_TECH_NONE = -1;
75     /**
76      * This ImsService is registered to IMS via LTE.
77      */
78     public static final int REGISTRATION_TECH_LTE = 0;
79     /**
80      * This ImsService is registered to IMS via IWLAN.
81      */
82     public static final int REGISTRATION_TECH_IWLAN = 1;
83 
84     /**
85      * This ImsService is registered to IMS via internet over second subscription.
86      */
87     public static final int REGISTRATION_TECH_CROSS_SIM = 2;
88 
89     /**
90      * This ImsService is registered to IMS via NR.
91      */
92     public static final int REGISTRATION_TECH_NR = 3;
93 
94     /**
95      * This is used to check the upper range of registration tech
96      * @hide
97      */
98     public static final int REGISTRATION_TECH_MAX = REGISTRATION_TECH_NR + 1;
99 
100     // Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current
101     // state.
102     // The unknown state is set as the initialization state. This is so that we do not call back
103     // with NOT_REGISTERED in the case where the ImsService has not updated the registration state
104     // yet.
105     private static final int REGISTRATION_STATE_UNKNOWN = -1;
106 
107     private Executor mExecutor;
108 
109     /**
110      * Create a new ImsRegistration.
111      * <p>
112      * Method stubs called from the framework will be called asynchronously. To specify the
113      * {@link Executor} that the methods stubs will be called, use
114      * {@link ImsRegistrationImplBase#ImsRegistrationImplBase(Executor)} instead.
115      * @hide This API is not part of the Android public SDK API
116      */
117     @SystemApi
ImsRegistrationImplBase()118     public ImsRegistrationImplBase() {
119         super();
120     }
121 
122     /**
123      * Create a ImsRegistration using the Executor specified for methods being called by the
124      * framework.
125      * @param executor The executor for the framework to use when executing the methods overridden
126      * by the implementation of ImsRegistration.
127      * @hide This API is not part of the Android public SDK API
128      */
129     @SystemApi
ImsRegistrationImplBase(@onNull Executor executor)130     public ImsRegistrationImplBase(@NonNull Executor executor) {
131         super();
132         mExecutor = executor;
133     }
134 
135     private final IImsRegistration mBinder = new IImsRegistration.Stub() {
136 
137         @Override
138         public @ImsRegistrationTech int getRegistrationTechnology() throws RemoteException {
139             return executeMethodAsyncForResult(() -> (mRegistrationAttributes == null)
140                     ? REGISTRATION_TECH_NONE : mRegistrationAttributes.getRegistrationTechnology(),
141                     "getRegistrationTechnology");
142         }
143 
144         @Override
145         public void addRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
146             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
147             executeMethodAsync(() -> {
148                 try {
149                     ImsRegistrationImplBase.this.addRegistrationCallback(c);
150                 } catch (RemoteException e) {
151                     exceptionRef.set(e);
152                 }
153             }, "addRegistrationCallback");
154 
155             if (exceptionRef.get() != null) {
156                 throw exceptionRef.get();
157             }
158         }
159 
160         @Override
161         public void removeRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
162             executeMethodAsync(() -> ImsRegistrationImplBase.this.removeRegistrationCallback(c),
163                     "removeRegistrationCallback");
164         }
165 
166         @Override
167         public void triggerFullNetworkRegistration(int sipCode, String sipReason) {
168             executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this
169                     .triggerFullNetworkRegistration(sipCode, sipReason),
170                     "triggerFullNetworkRegistration");
171         }
172 
173         @Override
174         public void triggerUpdateSipDelegateRegistration() {
175             executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this
176                     .updateSipDelegateRegistration(), "triggerUpdateSipDelegateRegistration");
177         }
178 
179         @Override
180         public void triggerSipDelegateDeregistration() {
181             executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this
182                     .triggerSipDelegateDeregistration(), "triggerSipDelegateDeregistration");
183         }
184 
185         // Call the methods with a clean calling identity on the executor and wait indefinitely for
186         // the future to return.
187         private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
188             try {
189                 CompletableFuture.runAsync(
190                         () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
191             } catch (CancellationException | CompletionException e) {
192                 Log.w(LOG_TAG, "ImsRegistrationImplBase Binder - " + errorLogName + " exception: "
193                         + e.getMessage());
194                 throw new RemoteException(e.getMessage());
195             }
196         }
197 
198         private void executeMethodAsyncNoException(Runnable r, String errorLogName) {
199             try {
200                 CompletableFuture.runAsync(
201                         () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
202             } catch (CancellationException | CompletionException e) {
203                 Log.w(LOG_TAG, "ImsRegistrationImplBase Binder - " + errorLogName + " exception: "
204                         + e.getMessage());
205             }
206         }
207 
208         private <T> T executeMethodAsyncForResult(Supplier<T> r,
209                 String errorLogName) throws RemoteException {
210             CompletableFuture<T> future = CompletableFuture.supplyAsync(
211                     () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
212             try {
213                 return future.get();
214             } catch (ExecutionException | InterruptedException e) {
215                 Log.w(LOG_TAG, "ImsRegistrationImplBase Binder - " + errorLogName + " exception: "
216                         + e.getMessage());
217                 throw new RemoteException(e.getMessage());
218             }
219         }
220     };
221 
222     private final RemoteCallbackListExt<IImsRegistrationCallback> mCallbacks =
223             new RemoteCallbackListExt<>();
224     private final Object mLock = new Object();
225     // Locked on mLock
226     private ImsRegistrationAttributes mRegistrationAttributes;
227     // Locked on mLock
228     private int mRegistrationState = REGISTRATION_STATE_UNKNOWN;
229     // Locked on mLock, create unspecified disconnect cause.
230     private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo();
231 
232     // We hold onto the uris each time they change so that we can send it to a callback when its
233     // first added.
234     private Uri[] mUris = new Uri[0];
235     private boolean mUrisSet = false;
236 
237     /**
238      * @hide
239      */
getBinder()240     public final IImsRegistration getBinder() {
241         return mBinder;
242     }
243 
addRegistrationCallback(IImsRegistrationCallback c)244     private void addRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
245         mCallbacks.register(c);
246         updateNewCallbackWithState(c);
247     }
248 
removeRegistrationCallback(IImsRegistrationCallback c)249     private void removeRegistrationCallback(IImsRegistrationCallback c) {
250         mCallbacks.unregister(c);
251     }
252 
253     /**
254      * Called by the framework to request that the ImsService perform the network registration
255      * of all SIP delegates associated with this ImsService.
256      * <p>
257      * If the SIP delegate feature tag configuration has changed, then this method will be
258      * called in order to let the ImsService know that it can pick up these changes in the IMS
259      * registration.
260      * @hide This API is not part of the Android public SDK API
261      */
262     @SystemApi
updateSipDelegateRegistration()263     public void updateSipDelegateRegistration() {
264         // Stub implementation, ImsService should implement this
265     }
266 
267 
268     /**
269      * Called by the framework to request that the ImsService perform the network deregistration of
270      * all SIP delegates associated with this ImsService.
271      * <p>
272      * This is typically called in situations where the user has changed the configuration of the
273      * device (for example, the default messaging application) and the framework is reconfiguring
274      * the tags associated with each IMS application.
275      * <p>
276      * This should not affect the registration of features managed by the ImsService itself, such as
277      * feature tags related to MMTEL registration.
278      * @hide This API is not part of the Android public SDK API
279      */
280     @SystemApi
triggerSipDelegateDeregistration()281     public void triggerSipDelegateDeregistration() {
282         // Stub implementation, ImsService should implement this
283     }
284 
285     /**
286      * Called by the framework to notify the ImsService that a SIP delegate connection has received
287      * a SIP message containing a permanent failure response (such as a 403) or an indication that a
288      * SIP response timer has timed out in response to an outgoing SIP message. This method will be
289      * called when this condition occurs to trigger the ImsService to tear down the full IMS
290      * registration and re-register again.
291      *
292      * @param sipCode The SIP error code that represents a permanent failure that was received in
293      *    response to a request generated by the IMS application. See RFC3261 7.2 for the general
294      *    classes of responses available here, however the codes that generate this condition may
295      *    be carrier specific.
296      * @param sipReason The reason associated with the SIP error code. {@code null} if there was no
297      *    reason associated with the error.
298      * @hide This API is not part of the Android public SDK API
299      */
300     @SystemApi
triggerFullNetworkRegistration(@ntRangefrom = 100, to = 699) int sipCode, @Nullable String sipReason)301     public void triggerFullNetworkRegistration(@IntRange(from = 100, to = 699) int sipCode,
302             @Nullable String sipReason) {
303         // Stub implementation, ImsService should implement this
304     }
305 
306 
307     /**
308      * Notify the framework that the device is connected to the IMS network.
309      *
310      * @param imsRadioTech the radio access technology.
311      * @hide This API is not part of the Android public SDK API
312      */
313     @SystemApi
onRegistered(@msRegistrationTech int imsRadioTech)314     public final void onRegistered(@ImsRegistrationTech int imsRadioTech) {
315         onRegistered(new ImsRegistrationAttributes.Builder(imsRadioTech).build());
316     }
317 
318     /**
319      * Notify the framework that the device is connected to the IMS network.
320      *
321      * @param attributes The attributes associated with the IMS registration.
322      * @hide This API is not part of the Android public SDK API
323      */
324     @SystemApi
onRegistered(@onNull ImsRegistrationAttributes attributes)325     public final void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
326         updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED);
327         mCallbacks.broadcastAction((c) -> {
328             try {
329                 c.onRegistered(attributes);
330             } catch (RemoteException e) {
331                 Log.w(LOG_TAG, e + "onRegistered(int, Set) - Skipping callback.");
332             }
333         });
334     }
335 
336     /**
337      * Notify the framework that the device is trying to connect the IMS network.
338      *
339      * @param imsRadioTech the radio access technology.
340      * @hide This API is not part of the Android public SDK API
341      */
342     @SystemApi
onRegistering(@msRegistrationTech int imsRadioTech)343     public final void onRegistering(@ImsRegistrationTech int imsRadioTech) {
344         onRegistering(new ImsRegistrationAttributes.Builder(imsRadioTech).build());
345     }
346 
347     /**
348      * Notify the framework that the device is trying to connect the IMS network.
349      *
350      * @param attributes The attributes associated with the IMS registration.
351      * @hide This API is not part of the Android public SDK API
352      */
353     @SystemApi
onRegistering(@onNull ImsRegistrationAttributes attributes)354     public final void onRegistering(@NonNull ImsRegistrationAttributes attributes) {
355         updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING);
356         mCallbacks.broadcastAction((c) -> {
357             try {
358                 c.onRegistering(attributes);
359             } catch (RemoteException e) {
360                 Log.w(LOG_TAG, e + "onRegistering(int, Set) - Skipping callback.");
361             }
362         });
363     }
364 
365     /**
366      * Notify the framework that the device is disconnected from the IMS network.
367      * <p>
368      * Note: Prior to calling {@link #onDeregistered(ImsReasonInfo)}, you should ensure that any
369      * changes to {@link android.telephony.ims.feature.ImsFeature} capability availability is sent
370      * to the framework.  For example,
371      * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}
372      * and
373      * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}
374      * may be set to unavailable to ensure the framework knows these services are no longer
375      * available due to de-registration.  If you do not report capability changes impacted by
376      * de-registration, the framework will not know which features are no longer available as a
377      * result.
378      *
379      * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
380      * @hide This API is not part of the Android public SDK API
381      */
382     @SystemApi
onDeregistered(ImsReasonInfo info)383     public final void onDeregistered(ImsReasonInfo info) {
384         updateToDisconnectedState(info);
385         // ImsReasonInfo should never be null.
386         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
387         mCallbacks.broadcastAction((c) -> {
388             try {
389                 c.onDeregistered(reasonInfo);
390             } catch (RemoteException e) {
391                 Log.w(LOG_TAG, e + "onDeregistered() - Skipping callback.");
392             }
393         });
394     }
395 
396     /**
397      * Notify the framework that the handover from the current radio technology to the technology
398      * defined in {@code imsRadioTech} has failed.
399      * @param imsRadioTech The technology that has failed to be changed. Valid values are
400      * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and
401      * {@link #REGISTRATION_TECH_CROSS_SIM}.
402      * @param info The {@link ImsReasonInfo} for the failure to change technology.
403      * @hide This API is not part of the Android public SDK API
404      */
405     @SystemApi
onTechnologyChangeFailed(@msRegistrationTech int imsRadioTech, ImsReasonInfo info)406     public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
407             ImsReasonInfo info) {
408         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
409         mCallbacks.broadcastAction((c) -> {
410             try {
411                 c.onTechnologyChangeFailed(imsRadioTech, reasonInfo);
412             } catch (RemoteException e) {
413                 Log.w(LOG_TAG, e + "onTechnologyChangeFailed() - Skipping callback.");
414             }
415         });
416     }
417 
418     /**
419      * Invoked when the {@link Uri}s associated to this device's subscriber have changed.
420      * These {@link Uri}s' are filtered out during conference calls.
421      *
422      * The {@link Uri}s are not guaranteed to be different between subsequent calls.
423      * @param uris changed uris
424      * @hide This API is not part of the Android public SDK API
425      */
426     @SystemApi
onSubscriberAssociatedUriChanged(Uri[] uris)427     public final void onSubscriberAssociatedUriChanged(Uri[] uris) {
428         synchronized (mLock) {
429             mUris = ArrayUtils.cloneOrNull(uris);
430             mUrisSet = true;
431         }
432         mCallbacks.broadcastAction((c) -> onSubscriberAssociatedUriChanged(c, uris));
433     }
434 
onSubscriberAssociatedUriChanged(IImsRegistrationCallback callback, Uri[] uris)435     private void onSubscriberAssociatedUriChanged(IImsRegistrationCallback callback, Uri[] uris) {
436         try {
437             callback.onSubscriberAssociatedUriChanged(uris);
438         } catch (RemoteException e) {
439             Log.w(LOG_TAG, e + "onSubscriberAssociatedUriChanged() - Skipping callback.");
440         }
441     }
442 
updateToState(ImsRegistrationAttributes attributes, int newState)443     private void updateToState(ImsRegistrationAttributes attributes, int newState) {
444         synchronized (mLock) {
445             mRegistrationAttributes = attributes;
446             mRegistrationState = newState;
447             mLastDisconnectCause = null;
448         }
449     }
450 
updateToDisconnectedState(ImsReasonInfo info)451     private void updateToDisconnectedState(ImsReasonInfo info) {
452         synchronized (mLock) {
453             //We don't want to send this info over if we are disconnected
454             mUrisSet = false;
455             mUris = null;
456 
457             updateToState(new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_NONE).build(),
458                     RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
459             if (info != null) {
460                 mLastDisconnectCause = info;
461             } else {
462                 Log.w(LOG_TAG, "updateToDisconnectedState: no ImsReasonInfo provided.");
463                 mLastDisconnectCause = new ImsReasonInfo();
464             }
465         }
466     }
467 
468     /**
469      * @param c the newly registered callback that will be updated with the current registration
470      *         state.
471      */
updateNewCallbackWithState(IImsRegistrationCallback c)472     private void updateNewCallbackWithState(IImsRegistrationCallback c)
473             throws RemoteException {
474         int state;
475         ImsRegistrationAttributes attributes;
476         ImsReasonInfo disconnectInfo;
477         boolean urisSet;
478         Uri[] uris;
479         synchronized (mLock) {
480             state = mRegistrationState;
481             attributes = mRegistrationAttributes;
482             disconnectInfo = mLastDisconnectCause;
483             urisSet = mUrisSet;
484             uris = mUris;
485         }
486         switch (state) {
487             case RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED: {
488                 c.onDeregistered(disconnectInfo);
489                 break;
490             }
491             case RegistrationManager.REGISTRATION_STATE_REGISTERING: {
492                 c.onRegistering(attributes);
493                 break;
494             }
495             case RegistrationManager.REGISTRATION_STATE_REGISTERED: {
496                 c.onRegistered(attributes);
497                 break;
498             }
499             case REGISTRATION_STATE_UNKNOWN: {
500                 // Do not callback if the state has not been updated yet by the ImsService.
501                 break;
502             }
503         }
504         if (urisSet) {
505             onSubscriberAssociatedUriChanged(c, uris);
506         }
507     }
508 
509     /**
510      * Set default Executor from ImsService.
511      * @param executor The default executor for the framework to use when executing the methods
512      * overridden by the implementation of Registration.
513      * @hide
514      */
setDefaultExecutor(@onNull Executor executor)515     public final void setDefaultExecutor(@NonNull Executor executor) {
516         if (mExecutor == null) {
517             mExecutor = executor;
518         }
519     }
520 }
521