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