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.SystemApi;
21 import android.annotation.TestApi;
22 import android.net.Uri;
23 import android.os.RemoteException;
24 import android.telephony.ims.ImsReasonInfo;
25 import android.telephony.ims.RegistrationManager;
26 import android.telephony.ims.aidl.IImsRegistration;
27 import android.telephony.ims.aidl.IImsRegistrationCallback;
28 import android.util.Log;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.telephony.util.RemoteCallbackListExt;
32 import com.android.internal.util.ArrayUtils;
33 
34 import java.lang.annotation.Retention;
35 import java.lang.annotation.RetentionPolicy;
36 
37 /**
38  * Controls IMS registration for this ImsService and notifies the framework when the IMS
39  * registration for this ImsService has changed status.
40  * @hide
41  */
42 @SystemApi
43 @TestApi
44 public class ImsRegistrationImplBase {
45 
46     private static final String LOG_TAG = "ImsRegistrationImplBase";
47 
48     /**
49      * @hide
50      */
51     // Defines the underlying radio technology type that we have registered for IMS over.
52     @IntDef(flag = true,
53             value = {
54                     REGISTRATION_TECH_NONE,
55                     REGISTRATION_TECH_LTE,
56                     REGISTRATION_TECH_IWLAN
57             })
58     @Retention(RetentionPolicy.SOURCE)
59     public @interface ImsRegistrationTech {}
60     /**
61      * No registration technology specified, used when we are not registered.
62      */
63     public static final int REGISTRATION_TECH_NONE = -1;
64     /**
65      * IMS is registered to IMS via LTE.
66      */
67     public static final int REGISTRATION_TECH_LTE = 0;
68     /**
69      * IMS is registered to IMS via IWLAN.
70      */
71     public static final int REGISTRATION_TECH_IWLAN = 1;
72 
73     // Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current
74     // state.
75     // The unknown state is set as the initialization state. This is so that we do not call back
76     // with NOT_REGISTERED in the case where the ImsService has not updated the registration state
77     // yet.
78     private static final int REGISTRATION_STATE_UNKNOWN = -1;
79 
80     private final IImsRegistration mBinder = new IImsRegistration.Stub() {
81 
82         @Override
83         public @ImsRegistrationTech int getRegistrationTechnology() throws RemoteException {
84             return getConnectionType();
85         }
86 
87         @Override
88         public void addRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
89             ImsRegistrationImplBase.this.addRegistrationCallback(c);
90         }
91 
92         @Override
93         public void removeRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
94             ImsRegistrationImplBase.this.removeRegistrationCallback(c);
95         }
96     };
97 
98     private final RemoteCallbackListExt<IImsRegistrationCallback> mCallbacks =
99             new RemoteCallbackListExt<>();
100     private final Object mLock = new Object();
101     // Locked on mLock
102     private @ImsRegistrationTech
103     int mConnectionType = REGISTRATION_TECH_NONE;
104     // Locked on mLock
105     private int mRegistrationState = REGISTRATION_STATE_UNKNOWN;
106     // Locked on mLock, create unspecified disconnect cause.
107     private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo();
108 
109     // We hold onto the uris each time they change so that we can send it to a callback when its
110     // first added.
111     private Uri[] mUris = new Uri[0];
112     private boolean mUrisSet = false;
113 
114     /**
115      * @hide
116      */
getBinder()117     public final IImsRegistration getBinder() {
118         return mBinder;
119     }
120 
addRegistrationCallback(IImsRegistrationCallback c)121     private void addRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
122         mCallbacks.register(c);
123         updateNewCallbackWithState(c);
124     }
125 
removeRegistrationCallback(IImsRegistrationCallback c)126     private void removeRegistrationCallback(IImsRegistrationCallback c) {
127         mCallbacks.unregister(c);
128     }
129 
130     /**
131      * Notify the framework that the device is connected to the IMS network.
132      *
133      * @param imsRadioTech the radio access technology. Valid values are defined as
134      * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN}.
135      */
onRegistered(@msRegistrationTech int imsRadioTech)136     public final void onRegistered(@ImsRegistrationTech int imsRadioTech) {
137         updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERED);
138         mCallbacks.broadcastAction((c) -> {
139             try {
140                 c.onRegistered(imsRadioTech);
141             } catch (RemoteException e) {
142                 Log.w(LOG_TAG, e + " " + "onRegistrationConnected() - Skipping " +
143                         "callback.");
144             }
145         });
146     }
147 
148     /**
149      * Notify the framework that the device is trying to connect the IMS network.
150      *
151      * @param imsRadioTech the radio access technology. Valid values are defined as
152      * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN}.
153      */
onRegistering(@msRegistrationTech int imsRadioTech)154     public final void onRegistering(@ImsRegistrationTech int imsRadioTech) {
155         updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERING);
156         mCallbacks.broadcastAction((c) -> {
157             try {
158                 c.onRegistering(imsRadioTech);
159             } catch (RemoteException e) {
160                 Log.w(LOG_TAG, e + " " + "onRegistrationProcessing() - Skipping " +
161                         "callback.");
162             }
163         });
164     }
165 
166     /**
167      * Notify the framework that the device is disconnected from the IMS network.
168      * <p>
169      * Note: Prior to calling {@link #onDeregistered(ImsReasonInfo)}, you should ensure that any
170      * changes to {@link android.telephony.ims.feature.ImsFeature} capability availability is sent
171      * to the framework.  For example,
172      * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}
173      * and
174      * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}
175      * may be set to unavailable to ensure the framework knows these services are no longer
176      * available due to de-registration.  If you do not report capability changes impacted by
177      * de-registration, the framework will not know which features are no longer available as a
178      * result.
179      *
180      * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
181      */
onDeregistered(ImsReasonInfo info)182     public final void onDeregistered(ImsReasonInfo info) {
183         updateToDisconnectedState(info);
184         // ImsReasonInfo should never be null.
185         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
186         mCallbacks.broadcastAction((c) -> {
187             try {
188                 c.onDeregistered(reasonInfo);
189             } catch (RemoteException e) {
190                 Log.w(LOG_TAG, e + " " + "onRegistrationDisconnected() - Skipping " +
191                         "callback.");
192             }
193         });
194     }
195 
196     /**
197      * Notify the framework that the handover from the current radio technology to the technology
198      * defined in {@code imsRadioTech} has failed.
199      * @param imsRadioTech The technology that has failed to be changed. Valid values are
200      * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN}.
201      * @param info The {@link ImsReasonInfo} for the failure to change technology.
202      */
onTechnologyChangeFailed(@msRegistrationTech int imsRadioTech, ImsReasonInfo info)203     public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
204             ImsReasonInfo info) {
205         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
206         mCallbacks.broadcastAction((c) -> {
207             try {
208                 c.onTechnologyChangeFailed(imsRadioTech, reasonInfo);
209             } catch (RemoteException e) {
210                 Log.w(LOG_TAG, e + " " + "onRegistrationChangeFailed() - Skipping " +
211                         "callback.");
212             }
213         });
214     }
215 
216     /**
217      * Invoked when the {@link Uri}s associated to this device's subscriber have changed.
218      * These {@link Uri}s' are filtered out during conference calls.
219      *
220      * The {@link Uri}s are not guaranteed to be different between subsequent calls.
221      * @param uris changed uris
222      */
onSubscriberAssociatedUriChanged(Uri[] uris)223     public final void onSubscriberAssociatedUriChanged(Uri[] uris) {
224         synchronized (mLock) {
225             mUris = ArrayUtils.cloneOrNull(uris);
226             mUrisSet = true;
227         }
228         mCallbacks.broadcastAction((c) -> onSubscriberAssociatedUriChanged(c, uris));
229     }
230 
onSubscriberAssociatedUriChanged(IImsRegistrationCallback callback, Uri[] uris)231     private void onSubscriberAssociatedUriChanged(IImsRegistrationCallback callback, Uri[] uris) {
232         try {
233             callback.onSubscriberAssociatedUriChanged(uris);
234         } catch (RemoteException e) {
235             Log.w(LOG_TAG, e + " " + "onSubscriberAssociatedUriChanged() - Skipping "
236                     + "callback.");
237         }
238     }
239 
updateToState(@msRegistrationTech int connType, int newState)240     private void updateToState(@ImsRegistrationTech int connType, int newState) {
241         synchronized (mLock) {
242             mConnectionType = connType;
243             mRegistrationState = newState;
244             mLastDisconnectCause = null;
245         }
246     }
247 
updateToDisconnectedState(ImsReasonInfo info)248     private void updateToDisconnectedState(ImsReasonInfo info) {
249         synchronized (mLock) {
250             //We don't want to send this info over if we are disconnected
251             mUrisSet = false;
252             mUris = null;
253 
254             updateToState(REGISTRATION_TECH_NONE,
255                     RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
256             if (info != null) {
257                 mLastDisconnectCause = info;
258             } else {
259                 Log.w(LOG_TAG, "updateToDisconnectedState: no ImsReasonInfo provided.");
260                 mLastDisconnectCause = new ImsReasonInfo();
261             }
262         }
263     }
264 
265     /**
266      * @return the current registration connection type. Valid values are
267      * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN}
268      * @hide
269      */
270     @VisibleForTesting
getConnectionType()271     public final @ImsRegistrationTech int getConnectionType() {
272         synchronized (mLock) {
273             return mConnectionType;
274         }
275     }
276 
277     /**
278      * @param c the newly registered callback that will be updated with the current registration
279      *         state.
280      */
updateNewCallbackWithState(IImsRegistrationCallback c)281     private void updateNewCallbackWithState(IImsRegistrationCallback c)
282             throws RemoteException {
283         int state;
284         ImsReasonInfo disconnectInfo;
285         boolean urisSet;
286         Uri[] uris;
287         synchronized (mLock) {
288             state = mRegistrationState;
289             disconnectInfo = mLastDisconnectCause;
290             urisSet = mUrisSet;
291             uris = mUris;
292         }
293         switch (state) {
294             case RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED: {
295                 c.onDeregistered(disconnectInfo);
296                 break;
297             }
298             case RegistrationManager.REGISTRATION_STATE_REGISTERING: {
299                 c.onRegistering(getConnectionType());
300                 break;
301             }
302             case RegistrationManager.REGISTRATION_STATE_REGISTERED: {
303                 c.onRegistered(getConnectionType());
304                 break;
305             }
306             case REGISTRATION_STATE_UNKNOWN: {
307                 // Do not callback if the state has not been updated yet by the ImsService.
308                 break;
309             }
310         }
311         if (urisSet) {
312             onSubscriberAssociatedUriChanged(c, uris);
313         }
314     }
315 }
316