1 /*
2  * Copyright (C) 2019 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 com.android.ims;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.os.Handler;
22 import android.os.IBinder;
23 import android.os.Looper;
24 import android.os.RemoteException;
25 import android.telephony.TelephonyManager;
26 import android.telephony.ims.aidl.IImsRegistration;
27 import android.telephony.ims.feature.ImsFeature;
28 import android.telephony.ims.stub.ImsRegistrationImplBase;
29 import android.util.Log;
30 
31 import com.android.ims.internal.IImsServiceFeatureCallback;
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.telephony.util.HandlerExecutor;
34 
35 import java.util.concurrent.Executor;
36 
37 /**
38  * Base class of MmTelFeatureConnection and RcsFeatureConnection.
39  */
40 public abstract class FeatureConnection {
41     protected static final String TAG = "FeatureConnection";
42 
43     public interface IFeatureUpdate {
44         /**
45          * Called when the ImsFeature has changed its state. Use
46          * {@link ImsFeature#getFeatureState()} to get the new state.
47          */
notifyStateChanged()48         void notifyStateChanged();
49 
50         /**
51          * Called when the ImsFeature has become unavailable due to the binder switching or app
52          * crashing. A new ImsServiceProxy should be requested for that feature.
53          */
notifyUnavailable()54         void notifyUnavailable();
55     }
56 
57     protected static boolean sImsSupportedOnDevice = true;
58 
59     protected final int mSlotId;
60     protected Context mContext;
61     protected IBinder mBinder;
62     @VisibleForTesting
63     public Executor mExecutor;
64 
65     // We are assuming the feature is available when started.
66     protected volatile boolean mIsAvailable = true;
67     // ImsFeature Status from the ImsService. Cached.
68     protected Integer mFeatureStateCached = null;
69     protected IFeatureUpdate mStatusCallback;
70     protected IImsRegistration mRegistrationBinder;
71     protected final Object mLock = new Object();
72 
FeatureConnection(Context context, int slotId)73     public FeatureConnection(Context context, int slotId) {
74         mSlotId = slotId;
75         mContext = context;
76 
77         // Callbacks should be scheduled on the main thread.
78         if (context.getMainLooper() != null) {
79             mExecutor = context.getMainExecutor();
80         } else {
81             // Fallback to the current thread.
82             if (Looper.myLooper() == null) {
83                 Looper.prepare();
84             }
85             mExecutor = new HandlerExecutor(new Handler(Looper.myLooper()));
86         }
87     }
88 
getTelephonyManager()89     protected TelephonyManager getTelephonyManager() {
90         return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
91     }
92 
93     /**
94      * Set the binder which type is IImsMmTelFeature or IImsRcsFeature to connect to MmTelFeature
95      * or RcsFeature.
96      */
setBinder(IBinder binder)97     public void setBinder(IBinder binder) {
98         synchronized (mLock) {
99             mBinder = binder;
100             try {
101                 if (mBinder != null) {
102                     mBinder.linkToDeath(mDeathRecipient, 0);
103                 }
104             } catch (RemoteException e) {
105                 // No need to do anything if the binder is already dead.
106             }
107         }
108     }
109 
110     protected final IBinder.DeathRecipient mDeathRecipient = () -> {
111         Log.w(TAG, "DeathRecipient triggered, binder died.");
112         if (mContext != null && Looper.getMainLooper() != null) {
113             // Move this signal to the main thread, notifying ImsManager of the Binder
114             // death on another thread may lead to deadlocks.
115             mContext.getMainExecutor().execute(this::onRemovedOrDied);
116             return;
117         }
118         // No choice - execute on the current Binder thread.
119         onRemovedOrDied();
120     };
121 
122     /**
123      * Called when the MmTelFeature/RcsFeature has either been removed by Telephony or crashed.
124      */
onRemovedOrDied()125     protected void onRemovedOrDied() {
126         synchronized (mLock) {
127             if (mIsAvailable) {
128                 mIsAvailable = false;
129                 mRegistrationBinder = null;
130                 if (mBinder != null) {
131                     mBinder.unlinkToDeath(mDeathRecipient, 0);
132                 }
133                 if (mStatusCallback != null) {
134                     Log.d(TAG, "onRemovedOrDied: notifyUnavailable");
135                     mStatusCallback.notifyUnavailable();
136                     // Unlink because this FeatureConnection should no longer send callbacks.
137                     mStatusCallback = null;
138                 }
139             }
140         }
141     }
142 
143     /**
144      * The listener for ImsManger and RcsFeatureManager to receive IMS feature status changed.
145      * @param callback Callback that will fire when the feature status has changed.
146      */
setStatusCallback(IFeatureUpdate callback)147     public void setStatusCallback(IFeatureUpdate callback) {
148         mStatusCallback = callback;
149     }
150 
151     @VisibleForTesting
getListener()152     public IImsServiceFeatureCallback getListener() {
153         return mListenerBinder;
154     }
155 
156     /**
157      * The callback to receive ImsFeature status changed.
158      */
159     private final IImsServiceFeatureCallback mListenerBinder =
160         new IImsServiceFeatureCallback.Stub() {
161             @Override
162             public void imsFeatureCreated(int slotId, int feature) {
163                 mExecutor.execute(() -> {
164                     handleImsFeatureCreatedCallback(slotId, feature);
165                 });
166             }
167             @Override
168             public void imsFeatureRemoved(int slotId, int feature) {
169                 mExecutor.execute(() -> {
170                     handleImsFeatureRemovedCallback(slotId, feature);
171                 });
172             }
173             @Override
174             public void imsStatusChanged(int slotId, int feature, int status) {
175                 mExecutor.execute(() -> {
176                     handleImsStatusChangedCallback(slotId, feature, status);
177                 });
178             }
179         };
180 
getRegistrationTech()181     public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTech()
182             throws RemoteException {
183         IImsRegistration registration = getRegistration();
184         if (registration != null) {
185             return registration.getRegistrationTechnology();
186         } else {
187             Log.w(TAG, "getRegistrationTech: ImsRegistration is null");
188             return ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
189         }
190     }
191 
getRegistration()192     public @Nullable IImsRegistration getRegistration() {
193         synchronized (mLock) {
194             // null if cache is invalid;
195             if (mRegistrationBinder != null) {
196                 return mRegistrationBinder;
197             }
198         }
199         // We don't want to synchronize on a binder call to another process.
200         IImsRegistration regBinder = getRegistrationBinder();
201         synchronized (mLock) {
202             // mRegistrationBinder may have changed while we tried to get the registration
203             // interface.
204             if (mRegistrationBinder == null) {
205                 mRegistrationBinder = regBinder;
206             }
207         }
208         return mRegistrationBinder;
209     }
210 
211     @VisibleForTesting
checkServiceIsReady()212     public void checkServiceIsReady() throws RemoteException {
213         if (!sImsSupportedOnDevice) {
214             throw new RemoteException("IMS is not supported on this device.");
215         }
216         if (!isBinderReady()) {
217             throw new RemoteException("ImsServiceProxy is not ready to accept commands.");
218         }
219     }
220 
221     /**
222      * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
223      * method returns false, it doesn't mean that the Binder connection is not available (use
224      * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
225      * at this time.
226      *
227      * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
228      * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_UNAVAILABLE}.
229      */
isBinderReady()230     public boolean isBinderReady() {
231         return isBinderAlive() && getFeatureState() == ImsFeature.STATE_READY;
232     }
233 
234     /**
235      * @return false if the binder connection is no longer alive.
236      */
isBinderAlive()237     public boolean isBinderAlive() {
238         return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
239     }
240 
241     /**
242      * @return an integer describing the current Feature Status, defined in
243      * {@link ImsFeature.ImsState}.
244      */
getFeatureState()245     public int getFeatureState() {
246         synchronized (mLock) {
247             if (isBinderAlive() && mFeatureStateCached != null) {
248                 return mFeatureStateCached;
249             }
250         }
251         // Don't synchronize on Binder call.
252         Integer state = retrieveFeatureState();
253         synchronized (mLock) {
254             if (state == null) {
255                 return ImsFeature.STATE_UNAVAILABLE;
256             }
257             // Cache only non-null value for feature status.
258             mFeatureStateCached = state;
259         }
260         Log.i(TAG + " [" + mSlotId + "]", "getFeatureState - returning "
261                 + ImsFeature.STATE_LOG_MAP.get(state));
262         return state;
263     }
264 
265     /**
266      * An ImsFeature has been created for this FeatureConnection for the associated
267      * {@link ImsFeature.FeatureType}.
268      * @param slotId The slot ID associated with the event.
269      * @param feature The {@link ImsFeature.FeatureType} associated with the event.
270      */
handleImsFeatureCreatedCallback(int slotId, int feature)271     protected abstract void handleImsFeatureCreatedCallback(int slotId, int feature);
272 
273     /**
274      * An ImsFeature has been removed for this FeatureConnection for the associated
275      * {@link ImsFeature.FeatureType}.
276      * @param slotId The slot ID associated with the event.
277      * @param feature The {@link ImsFeature.FeatureType} associated with the event.
278      */
handleImsFeatureRemovedCallback(int slotId, int feature)279     protected abstract void handleImsFeatureRemovedCallback(int slotId, int feature);
280 
281     /**
282      * The status of an ImsFeature has changed for the associated {@link ImsFeature.FeatureType}.
283      * @param slotId The slot ID associated with the event.
284      * @param feature The {@link ImsFeature.FeatureType} associated with the event.
285      * @param status The new {@link ImsFeature.ImsState} associated with the ImsFeature
286      */
handleImsStatusChangedCallback(int slotId, int feature, int status)287     protected abstract void handleImsStatusChangedCallback(int slotId, int feature, int status);
288 
289     /**
290      * Internal method used to retrieve the feature status from the corresponding ImsService.
291      */
retrieveFeatureState()292     protected abstract Integer retrieveFeatureState();
293 
294     /**
295      * @return The ImsRegistration instance associated with the FeatureConnection.
296      */
getRegistrationBinder()297     protected abstract IImsRegistration getRegistrationBinder();
298 }
299