1 /*
2  * Copyright (C) 2021 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;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.os.Binder;
23 
24 import com.android.internal.telephony.IImsStateCallback;
25 
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 import java.lang.ref.WeakReference;
29 import java.util.concurrent.Executor;
30 
31 /**
32  * A callback class used for monitoring changes in IMS service connection states
33  * for a specific subscription.
34  * <p>
35  * @see ImsMmTelManager#registerImsStateCallback(Executor, ImsStateCallback)
36  * @see ImsRcsManager#registerImsStateCallback(Executor, ImsStateCallback)
37  */
38 public abstract class ImsStateCallback {
39 
40     /** @hide */
41     @Retention(RetentionPolicy.SOURCE)
42     @IntDef(prefix = "REASON_", value = {
43             REASON_UNKNOWN_TEMPORARY_ERROR,
44             REASON_UNKNOWN_PERMANENT_ERROR,
45             REASON_IMS_SERVICE_DISCONNECTED,
46             REASON_NO_IMS_SERVICE_CONFIGURED,
47             REASON_SUBSCRIPTION_INACTIVE,
48             REASON_IMS_SERVICE_NOT_READY
49     })
50     public @interface DisconnectedReason {}
51 
52     /**
53      * The underlying IMS service is temporarily unavailable for the
54      * associated subscription.
55      * {@link #onAvailable} will be called when the IMS service becomes
56      * available again.
57      */
58     public static final int REASON_UNKNOWN_TEMPORARY_ERROR     = 1;
59 
60     /**
61      * The underlying IMS service is permanently unavailable for the
62      * associated subscription and there will be no Manager available for
63      * this subscription.
64      */
65     public static final int REASON_UNKNOWN_PERMANENT_ERROR     = 2;
66 
67     /**
68      * The underlying IMS service has died, is reconfiguring, or has never
69      * come up yet and as a result is currently unavailable.
70      * {@link #onAvailable} will be called when the IMS service becomes
71      * available. All callbacks should be unregistered now and registered again
72      * if the IMS service moves back to available.
73      */
74     public static final int REASON_IMS_SERVICE_DISCONNECTED    = 3;
75 
76     /**
77      * There is no IMS service configured for the subscription ID specified.
78      * This is a permanent error and there will be no Manager available for
79      * this subscription.
80      */
81     public static final int REASON_NO_IMS_SERVICE_CONFIGURED   = 4;
82 
83     /**
84      * The subscription associated with this Manager has moved to an inactive
85      * state (e.g. SIM removed) and the IMS service has torn down the resources
86      * related to this subscription. This has caused this callback
87      * to be deregistered. The callback must be re-registered when this subscription
88      * becomes active in order to continue listening to the IMS service state.
89      */
90     public static final int REASON_SUBSCRIPTION_INACTIVE       = 5;
91 
92     /**
93      * The IMS service is connected, but in a NOT_READY state. Once the
94      * service moves to ready, {@link #onAvailable} will be called.
95      */
96     public static final int REASON_IMS_SERVICE_NOT_READY       = 6;
97 
98     private IImsStateCallbackStub mCallback;
99 
100     /**
101      * @hide
102      */
init(@onNull @allbackExecutor Executor executor)103     public void init(@NonNull @CallbackExecutor Executor executor) {
104         if (executor == null) {
105             throw new IllegalArgumentException("ImsStateCallback Executor must be non-null");
106         }
107         mCallback = new IImsStateCallbackStub(this, executor);
108     }
109 
110     /**
111      * Using a static class and weak reference here to avoid memory leak caused by the
112      * IImsStateCallback.Stub callback retaining references to the outside ImsStateCallback.
113      */
114     private static class IImsStateCallbackStub extends IImsStateCallback.Stub {
115         private WeakReference<ImsStateCallback> mImsStateCallbackWeakRef;
116         private Executor mExecutor;
117 
IImsStateCallbackStub(ImsStateCallback imsStateCallback, Executor executor)118         IImsStateCallbackStub(ImsStateCallback imsStateCallback, Executor executor) {
119             mImsStateCallbackWeakRef = new WeakReference<ImsStateCallback>(imsStateCallback);
120             mExecutor = executor;
121         }
122 
getExecutor()123         Executor getExecutor() {
124             return mExecutor;
125         }
126 
onAvailable()127         public void onAvailable() {
128             ImsStateCallback callback = mImsStateCallbackWeakRef.get();
129             if (callback == null) return;
130 
131             Binder.withCleanCallingIdentity(
132                     () -> mExecutor.execute(() -> callback.onAvailable()));
133         }
134 
onUnavailable(int reason)135         public void onUnavailable(int reason) {
136             ImsStateCallback callback = mImsStateCallbackWeakRef.get();
137             if (callback == null) return;
138 
139             Binder.withCleanCallingIdentity(
140                     () -> mExecutor.execute(() -> callback.onUnavailable(reason)));
141         }
142     }
143 
144     /**
145      * The IMS service has disconnected or is reporting NOT_READY and is no longer
146      * available to users. The user should clean up all related state and
147      * unregister callbacks. If it is a temporary error, {@link #onAvailable} will
148      * be called when the IMS service becomes available again.
149      *
150      * @param reason the specified reason
151      */
onUnavailable(@isconnectedReason int reason)152     public abstract void onUnavailable(@DisconnectedReason int reason);
153 
154     /**
155      * The IMS service is connected and is ready for communication over the
156      * provided Manager.
157      */
onAvailable()158     public abstract void onAvailable();
159 
160     /**
161      * An unexpected error has occurred and the Telephony process has crashed. This
162      * has caused this callback to be deregistered. The callback must be
163      * re-registered in order to continue listening to the IMS service state.
164      */
onError()165     public abstract void onError();
166 
167     /**
168      * The callback to notify the death of telephony process
169      * @hide
170      */
binderDied()171     public final void binderDied() {
172         if (mCallback != null) {
173             mCallback.getExecutor().execute(() -> onError());
174         }
175     }
176 
177     /**
178      * Return the callback binder
179      * @hide
180      */
getCallbackBinder()181     public IImsStateCallbackStub getCallbackBinder() {
182         return mCallback;
183     }
184 }
185