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.IBinder;
22 import android.os.Looper;
23 import android.os.RemoteException;
24 import android.telephony.TelephonyManager;
25 import android.telephony.ims.ImsService;
26 import android.telephony.ims.aidl.IImsConfig;
27 import android.telephony.ims.aidl.IImsRegistration;
28 import android.telephony.ims.aidl.ISipTransport;
29 import android.telephony.ims.feature.ImsFeature;
30 import android.telephony.ims.stub.ImsRegistrationImplBase;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import java.util.NoSuchElementException;
36 
37 /**
38  * Base class of MmTelFeatureConnection and RcsFeatureConnection.
39  */
40 public abstract class FeatureConnection {
41     protected static final String TAG = "FeatureConnection";
42 
43     protected static boolean sImsSupportedOnDevice = true;
44 
45     protected final int mSlotId;
46     protected final int mSubId;
47     protected Context mContext;
48     protected IBinder mBinder;
49 
50     // We are assuming the feature is available when started.
51     protected volatile boolean mIsAvailable = true;
52     // ImsFeature Status from the ImsService. Cached.
53     protected Integer mFeatureStateCached = null;
54     protected long mFeatureCapabilities;
55     private final IImsRegistration mRegistrationBinder;
56     private final IImsConfig mConfigBinder;
57     private final ISipTransport mSipTransportBinder;
58     protected final Object mLock = new Object();
59 
FeatureConnection(Context context, int slotId, int subId, IImsConfig c, IImsRegistration r, ISipTransport s)60     public FeatureConnection(Context context, int slotId, int subId, IImsConfig c,
61             IImsRegistration r, ISipTransport s) {
62         mSlotId = slotId;
63         mSubId = subId;
64         mContext = context;
65         mRegistrationBinder = r;
66         mConfigBinder = c;
67         mSipTransportBinder = s;
68     }
69 
getTelephonyManager()70     protected TelephonyManager getTelephonyManager() {
71         return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
72     }
73 
74     /**
75      * Set the binder which type is IImsMmTelFeature or IImsRcsFeature to connect to MmTelFeature
76      * or RcsFeature.
77      */
setBinder(IBinder binder)78     public void setBinder(IBinder binder) {
79         synchronized (mLock) {
80             mBinder = binder;
81             try {
82                 if (mBinder != null) {
83                     mBinder.linkToDeath(mDeathRecipient, 0);
84                 }
85             } catch (RemoteException e) {
86                 Log.w(TAG, "setBinder: linkToDeath on already dead Binder, setting null");
87                 mBinder = null;
88             }
89         }
90     }
91 
92     protected final IBinder.DeathRecipient mDeathRecipient = () -> {
93         Log.w(TAG, "DeathRecipient triggered, binder died.");
94         if (mContext != null && Looper.getMainLooper() != null) {
95             // Move this signal to the main thread, notifying ImsManager of the Binder
96             // death on another thread may lead to deadlocks.
97             mContext.getMainExecutor().execute(this::onRemovedOrDied);
98             return;
99         }
100         // No choice - execute on the current Binder thread.
101         onRemovedOrDied();
102     };
103 
104     /**
105      * Called when the MmTelFeature/RcsFeature has either been removed by Telephony or crashed.
106      */
onRemovedOrDied()107     protected void onRemovedOrDied() {
108         synchronized (mLock) {
109             if (mIsAvailable) {
110                 mIsAvailable = false;
111                 try {
112                     if (mBinder != null) {
113                         mBinder.unlinkToDeath(mDeathRecipient, 0);
114                     }
115                 } catch (NoSuchElementException e) {
116                     Log.w(TAG, "onRemovedOrDied: unlinkToDeath called on unlinked Binder.");
117                 }
118             }
119         }
120     }
121 
getRegistrationTech()122     public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTech()
123             throws RemoteException {
124         IImsRegistration registration = getRegistration();
125         if (registration != null) {
126             return registration.getRegistrationTechnology();
127         } else {
128             Log.w(TAG, "getRegistrationTech: ImsRegistration is null");
129             return ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
130         }
131     }
132 
getRegistration()133     public @Nullable IImsRegistration getRegistration() {
134         return mRegistrationBinder;
135     }
136 
getConfig()137     public @Nullable IImsConfig getConfig() {
138         return mConfigBinder;
139     }
140 
getSipTransport()141     public @Nullable ISipTransport getSipTransport() {
142         return mSipTransportBinder;
143     }
144 
145     @VisibleForTesting
checkServiceIsReady()146     public void checkServiceIsReady() throws RemoteException {
147         if (!sImsSupportedOnDevice) {
148             throw new RemoteException("IMS is not supported on this device.");
149         }
150         if (!isBinderReady()) {
151             throw new RemoteException("ImsServiceProxy is not ready to accept commands.");
152         }
153     }
154 
155     /**
156      * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
157      * method returns false, it doesn't mean that the Binder connection is not available (use
158      * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
159      * at this time.
160      *
161      * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
162      * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_UNAVAILABLE}.
163      */
isBinderReady()164     public boolean isBinderReady() {
165         return isBinderAlive() && getFeatureState() == ImsFeature.STATE_READY;
166     }
167 
168     /**
169      * @return false if the binder connection is no longer alive.
170      */
isBinderAlive()171     public boolean isBinderAlive() {
172         return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
173     }
174 
updateFeatureState(int state)175     public void updateFeatureState(int state) {
176         synchronized (mLock) {
177             mFeatureStateCached = state;
178         }
179     }
180 
getFeatureCapabilties()181     public long getFeatureCapabilties() {
182         synchronized (mLock) {
183             return mFeatureCapabilities;
184         }
185     }
186 
updateFeatureCapabilities(long caps)187     public void updateFeatureCapabilities(long caps) {
188         synchronized (mLock) {
189             if (mFeatureCapabilities != caps) {
190                 mFeatureCapabilities = caps;
191                 onFeatureCapabilitiesUpdated(caps);
192             }
193         }
194     }
195 
isCapable(@msService.ImsServiceCapability long capabilities)196     public boolean isCapable(@ImsService.ImsServiceCapability long capabilities)
197             throws RemoteException {
198         if (!isBinderAlive()) {
199             throw new RemoteException("isCapable: ImsService is not alive");
200         }
201         return (getFeatureCapabilties() & capabilities) > 0;
202     }
203 
204     /**
205      * @return an integer describing the current Feature Status, defined in
206      * {@link ImsFeature.ImsState}.
207      */
getFeatureState()208     public int getFeatureState() {
209         synchronized (mLock) {
210             if (isBinderAlive() && mFeatureStateCached != null) {
211                 return mFeatureStateCached;
212             }
213         }
214         // Don't synchronize on Binder call.
215         Integer state = retrieveFeatureState();
216         synchronized (mLock) {
217             if (state == null) {
218                 return ImsFeature.STATE_UNAVAILABLE;
219             }
220             // Cache only non-null value for feature status.
221             mFeatureStateCached = state;
222         }
223         Log.i(TAG + " [" + mSlotId + "]", "getFeatureState - returning "
224                 + ImsFeature.STATE_LOG_MAP.get(state));
225         return state;
226     }
227 
getSubId()228     public int getSubId() {
229         return mSubId;
230     }
231 
232     /**
233      * Internal method used to retrieve the feature status from the corresponding ImsService.
234      */
retrieveFeatureState()235     protected abstract Integer retrieveFeatureState();
236 
onFeatureCapabilitiesUpdated(long capabilities)237     protected abstract void onFeatureCapabilitiesUpdated(long capabilities);
238 }
239