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 com.android.ims;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.net.Uri;
22 import android.os.IBinder;
23 import android.os.Message;
24 import android.os.RemoteException;
25 import android.telephony.Rlog;
26 import android.telephony.TelephonyManager;
27 import android.telephony.ims.ImsCallProfile;
28 import android.telephony.ims.ImsReasonInfo;
29 import android.telephony.ims.aidl.IImsConfig;
30 import android.telephony.ims.aidl.IImsMmTelFeature;
31 import android.telephony.ims.aidl.IImsRegistration;
32 import android.telephony.ims.aidl.IImsRegistrationCallback;
33 import android.telephony.ims.aidl.IImsSmsListener;
34 import android.telephony.ims.feature.CapabilityChangeRequest;
35 import android.telephony.ims.feature.ImsFeature;
36 import android.telephony.ims.feature.MmTelFeature;
37 import android.telephony.ims.stub.ImsRegistrationImplBase;
38 import android.telephony.ims.stub.ImsSmsImplBase;
39 import android.util.Log;
40 
41 import com.android.ims.internal.IImsCallSession;
42 import com.android.ims.internal.IImsEcbm;
43 import com.android.ims.internal.IImsMultiEndpoint;
44 import com.android.ims.internal.IImsServiceFeatureCallback;
45 import com.android.ims.internal.IImsUt;
46 
47 import java.util.Collections;
48 import java.util.HashSet;
49 import java.util.Set;
50 import java.util.concurrent.ConcurrentHashMap;
51 
52 /**
53  * A container of the IImsServiceController binder, which implements all of the ImsFeatures that
54  * the platform currently supports: MMTel and RCS.
55  * @hide
56  */
57 
58 public class MmTelFeatureConnection {
59 
60     protected static final String TAG = "MmTelFeatureConnection";
61     protected final int mSlotId;
62     protected IBinder mBinder;
63     private Context mContext;
64 
65     private volatile boolean mIsAvailable = false;
66     // ImsFeature Status from the ImsService. Cached.
67     private Integer mFeatureStateCached = null;
68     private IFeatureUpdate mStatusCallback;
69     private final Object mLock = new Object();
70     // Updated by IImsServiceFeatureCallback when FEATURE_EMERGENCY_MMTEL is sent.
71     private boolean mSupportsEmergencyCalling = false;
72 
73     // Cache the Registration and Config interfaces as long as the MmTel feature is connected. If
74     // it becomes disconnected, invalidate.
75     private IImsRegistration mRegistrationBinder;
76     private IImsConfig mConfigBinder;
77 
78     private IBinder.DeathRecipient mDeathRecipient = () -> {
79             Log.w(TAG, "DeathRecipient triggered, binder died.");
80             onRemovedOrDied();
81     };
82 
83     private abstract class CallbackAdapterManager<T> {
84         private static final String TAG = "CallbackAdapterManager";
85 
86         protected final Set<T> mLocalCallbacks =
87                 Collections.newSetFromMap(new ConcurrentHashMap<>());
88         private boolean mHasConnected = false;
89 
addCallback(T localCallback)90         public void addCallback(T localCallback) throws RemoteException {
91             // We only one one binding to the ImsService per process.
92             // Store any more locally.
93             synchronized (mLock) {
94                 if (!mHasConnected) {
95                     // throws a RemoteException if a connection can not be established.
96                     if (createConnection()) {
97                         mHasConnected = true;
98                     } else {
99                         throw new RemoteException("Can not create connection!");
100                     }
101                 }
102             }
103             Log.i(TAG, "Local callback added: " + localCallback);
104             mLocalCallbacks.add(localCallback);
105         }
106 
removeCallback(T localCallback)107         public void removeCallback(T localCallback) {
108             // We only maintain one binding to the ImsService per process.
109             Log.i(TAG, "Local callback removed: " + localCallback);
110             mLocalCallbacks.remove(localCallback);
111             synchronized (mLock) {
112                 // If we have removed all local callbacks, remove callback to ImsService.
113                 if(mHasConnected) {
114                     if (mLocalCallbacks.isEmpty()) {
115                         removeConnection();
116                         mHasConnected = false;
117                     }
118                 }
119             }
120         }
121 
close()122         public void close() {
123             synchronized (mLock) {
124                 if (mHasConnected) {
125                     removeConnection();
126                     // Still mark the connection as disconnected, even if this fails.
127                     mHasConnected = false;
128                 }
129             }
130             Log.i(TAG, "Closing connection and clearing callbacks");
131             mLocalCallbacks.clear();
132         }
133 
createConnection()134         abstract boolean createConnection() throws RemoteException;
135 
removeConnection()136         abstract void removeConnection();
137     }
138     private ImsRegistrationCallbackAdapter mRegistrationCallbackManager
139             = new ImsRegistrationCallbackAdapter();
140     private class ImsRegistrationCallbackAdapter
141             extends CallbackAdapterManager<ImsRegistrationImplBase.Callback> {
142         private final RegistrationCallbackAdapter mRegistrationCallbackAdapter
143                 = new RegistrationCallbackAdapter();
144 
145         private class RegistrationCallbackAdapter extends IImsRegistrationCallback.Stub {
146 
147             @Override
onRegistered(int imsRadioTech)148             public void onRegistered(int imsRadioTech) {
149                 Log.i(TAG, "onRegistered ::");
150 
151                 mLocalCallbacks.forEach(l -> l.onRegistered(imsRadioTech));
152             }
153 
154             @Override
onRegistering(int imsRadioTech)155             public void onRegistering(int imsRadioTech) {
156                 Log.i(TAG, "onRegistering ::");
157 
158                 mLocalCallbacks.forEach(l -> l.onRegistering(imsRadioTech));
159             }
160 
161             @Override
onDeregistered(ImsReasonInfo imsReasonInfo)162             public void onDeregistered(ImsReasonInfo imsReasonInfo) {
163                 Log.i(TAG, "onDeregistered ::");
164 
165                 mLocalCallbacks.forEach(l -> l.onDeregistered(imsReasonInfo));
166             }
167 
168             @Override
onTechnologyChangeFailed(int targetRadioTech, ImsReasonInfo imsReasonInfo)169             public void onTechnologyChangeFailed(int targetRadioTech, ImsReasonInfo imsReasonInfo) {
170                 Log.i(TAG, "onTechnologyChangeFailed :: targetAccessTech=" + targetRadioTech +
171                         ", imsReasonInfo=" + imsReasonInfo);
172 
173                     mLocalCallbacks.forEach(l -> l.onTechnologyChangeFailed(targetRadioTech,
174                             imsReasonInfo));
175             }
176 
177             @Override
onSubscriberAssociatedUriChanged(Uri[] uris)178             public void onSubscriberAssociatedUriChanged(Uri[] uris) {
179                 Log.i(TAG, "onSubscriberAssociatedUriChanged");
180 
181                 mLocalCallbacks.forEach(l -> l.onSubscriberAssociatedUriChanged(uris));
182             }
183         }
184 
185         @Override
createConnection()186         boolean createConnection() throws RemoteException {
187             IImsRegistration imsRegistration = getRegistration();
188             if (imsRegistration != null) {
189                 getRegistration().addRegistrationCallback(mRegistrationCallbackAdapter);
190                 return true;
191             } else {
192                 Log.e(TAG, "ImsRegistration is null");
193                 return false;
194             }
195         }
196 
197         @Override
removeConnection()198         void removeConnection() {
199             IImsRegistration imsRegistration = getRegistration();
200             if (imsRegistration != null) {
201                 try {
202                     getRegistration().removeRegistrationCallback(mRegistrationCallbackAdapter);
203                 } catch (RemoteException e) {
204                     Log.w(TAG, "removeConnection: couldn't remove registration callback");
205                 }
206             } else {
207                 Log.e(TAG, "ImsRegistration is null");
208             }
209         }
210     }
211 
212     private final CapabilityCallbackManager mCapabilityCallbackManager
213             = new CapabilityCallbackManager();
214     private class CapabilityCallbackManager
215             extends CallbackAdapterManager<ImsFeature.CapabilityCallback> {
216         private final CapabilityCallbackAdapter mCallbackAdapter = new CapabilityCallbackAdapter();
217 
218         private class CapabilityCallbackAdapter extends ImsFeature.CapabilityCallback {
219             // Called when the Capabilities Status on this connection have changed.
220             @Override
onCapabilitiesStatusChanged(ImsFeature.Capabilities config)221             public void onCapabilitiesStatusChanged(ImsFeature.Capabilities config) {
222                 mLocalCallbacks.forEach(
223                         callback -> callback.onCapabilitiesStatusChanged(config));
224             }
225         }
226 
227         @Override
createConnection()228         boolean createConnection() throws RemoteException {
229             IImsMmTelFeature binder;
230             synchronized (mLock) {
231                 checkServiceIsReady();
232                 binder = getServiceInterface(mBinder);
233             }
234             if (binder != null) {
235                 binder.addCapabilityCallback(mCallbackAdapter);
236                 return true;
237             } else {
238                 Log.w(TAG, "create: Couldn't get IImsMmTelFeature binder");
239                 return false;
240             }
241         }
242 
243         @Override
removeConnection()244         void removeConnection() {
245             IImsMmTelFeature binder = null;
246             synchronized (mLock) {
247                 try {
248                     checkServiceIsReady();
249                     binder = getServiceInterface(mBinder);
250                 } catch (RemoteException e) {
251                     // binder is null
252                 }
253             }
254             if (binder != null) {
255                 try {
256                     binder.removeCapabilityCallback(mCallbackAdapter);
257                 } catch (RemoteException e) {
258                     Log.w(TAG, "remove: IImsMmTelFeature binder is dead");
259                 }
260             } else {
261                 Log.w(TAG, "remove: Couldn't get IImsMmTelFeature binder");
262             }
263         }
264     }
265 
266 
create(Context context , int slotId)267     public static MmTelFeatureConnection create(Context context , int slotId) {
268         MmTelFeatureConnection serviceProxy = new MmTelFeatureConnection(context, slotId);
269 
270         TelephonyManager tm  = getTelephonyManager(context);
271         if (tm == null) {
272             Rlog.w(TAG, "create: TelephonyManager is null!");
273             // Binder can be unset in this case because it will be torn down/recreated as part of
274             // a retry mechanism until the serviceProxy binder is set successfully.
275             return serviceProxy;
276         }
277 
278         IImsMmTelFeature binder = tm.getImsMmTelFeatureAndListen(slotId,
279                 serviceProxy.getListener());
280         if (binder != null) {
281             serviceProxy.setBinder(binder.asBinder());
282             // Trigger the cache to be updated for feature status.
283             serviceProxy.getFeatureState();
284         } else {
285             Rlog.w(TAG, "create: binder is null! Slot Id: " + slotId);
286         }
287         return serviceProxy;
288     }
289 
getTelephonyManager(Context context)290     public static TelephonyManager getTelephonyManager(Context context) {
291         return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
292     }
293 
294     public interface IFeatureUpdate {
295         /**
296          * Called when the ImsFeature has changed its state. Use
297          * {@link ImsFeature#getFeatureState()} to get the new state.
298          */
notifyStateChanged()299         void notifyStateChanged();
300 
301         /**
302          * Called when the ImsFeature has become unavailable due to the binder switching or app
303          * crashing. A new ImsServiceProxy should be requested for that feature.
304          */
notifyUnavailable()305         void notifyUnavailable();
306     }
307 
308     private final IImsServiceFeatureCallback mListenerBinder =
309             new IImsServiceFeatureCallback.Stub() {
310 
311         @Override
312         public void imsFeatureCreated(int slotId, int feature) throws RemoteException {
313             // The feature has been enabled. This happens when the feature is first created and may
314             // happen when the feature is re-enabled.
315             synchronized (mLock) {
316                 if(mSlotId != slotId) {
317                     return;
318                 }
319                 switch (feature) {
320                     case ImsFeature.FEATURE_MMTEL: {
321                         if (!mIsAvailable) {
322                             Log.i(TAG, "MmTel enabled on slotId: " + slotId);
323                             mIsAvailable = true;
324                         }
325                         break;
326                     }
327                     case ImsFeature.FEATURE_EMERGENCY_MMTEL: {
328                         mSupportsEmergencyCalling = true;
329                         Log.i(TAG, "Emergency calling enabled on slotId: " + slotId);
330                         break;
331                     }
332                 }
333 
334             }
335         }
336 
337         @Override
338         public void imsFeatureRemoved(int slotId, int feature) throws RemoteException {
339             synchronized (mLock) {
340                 if(mSlotId != slotId) {
341                     return;
342                 }
343                 switch (feature) {
344                     case ImsFeature.FEATURE_MMTEL: {
345                         Log.i(TAG, "MmTel removed on slotId: " + slotId);
346                         onRemovedOrDied();
347                         break;
348                     }
349                     case ImsFeature.FEATURE_EMERGENCY_MMTEL : {
350                         mSupportsEmergencyCalling = false;
351                         Log.i(TAG, "Emergency calling disabled on slotId: " + slotId);
352                         break;
353                     }
354                 }
355             }
356         }
357 
358         @Override
359         public void imsStatusChanged(int slotId, int feature, int status) throws RemoteException {
360             synchronized (mLock) {
361                 Log.i(TAG, "imsStatusChanged: slot: " + slotId + " feature: " + feature +
362                         " status: " + status);
363                 if (mSlotId == slotId && feature == ImsFeature.FEATURE_MMTEL) {
364                     mFeatureStateCached = status;
365                     if (mStatusCallback != null) {
366                         mStatusCallback.notifyStateChanged();
367                     }
368                 }
369             }
370         }
371     };
372 
MmTelFeatureConnection(Context context, int slotId)373     public MmTelFeatureConnection(Context context, int slotId) {
374         mSlotId = slotId;
375         mContext = context;
376     }
377 
378     /**
379      * Called when the MmTelFeature has either been removed by Telephony or crashed.
380      */
onRemovedOrDied()381     private void onRemovedOrDied() {
382         synchronized (mLock) {
383             if (mIsAvailable) {
384                 mIsAvailable = false;
385                 // invalidate caches.
386                 mRegistrationBinder = null;
387                 mConfigBinder = null;
388                 if (mBinder != null) {
389                     mBinder.unlinkToDeath(mDeathRecipient, 0);
390                 }
391                 if (mStatusCallback != null) {
392                     mStatusCallback.notifyUnavailable();
393                 }
394             }
395         }
396     }
397 
getRegistration()398     private @Nullable IImsRegistration getRegistration() {
399         synchronized (mLock) {
400             // null if cache is invalid;
401             if (mRegistrationBinder != null) {
402                 return mRegistrationBinder;
403             }
404         }
405         TelephonyManager tm = getTelephonyManager(mContext);
406         // We don't want to synchronize on a binder call to another process.
407         IImsRegistration regBinder = tm != null
408                 ? tm.getImsRegistration(mSlotId, ImsFeature.FEATURE_MMTEL) : null;
409         synchronized (mLock) {
410             // mRegistrationBinder may have changed while we tried to get the registration
411             // interface.
412             if (mRegistrationBinder == null) {
413                 mRegistrationBinder = regBinder;
414             }
415         }
416         return mRegistrationBinder;
417     }
418 
getConfig()419     private IImsConfig getConfig() {
420         synchronized (mLock) {
421             // null if cache is invalid;
422             if (mConfigBinder != null) {
423                 return mConfigBinder;
424             }
425         }
426         TelephonyManager tm = getTelephonyManager(mContext);
427         IImsConfig configBinder = tm != null
428                 ? tm.getImsConfig(mSlotId, ImsFeature.FEATURE_MMTEL) : null;
429         synchronized (mLock) {
430             // mConfigBinder may have changed while we tried to get the config interface.
431             if (mConfigBinder == null) {
432                 mConfigBinder = configBinder;
433             }
434         }
435         return mConfigBinder;
436     }
437 
isEmergencyMmTelAvailable()438     public boolean isEmergencyMmTelAvailable() {
439         return mSupportsEmergencyCalling;
440     }
441 
getListener()442     public IImsServiceFeatureCallback getListener() {
443         return mListenerBinder;
444     }
445 
setBinder(IBinder binder)446     public void setBinder(IBinder binder) {
447         synchronized (mLock) {
448             mBinder = binder;
449             try {
450                 if (mBinder != null) {
451                     mBinder.linkToDeath(mDeathRecipient, 0);
452                 }
453             } catch (RemoteException e) {
454                 // No need to do anything if the binder is already dead.
455             }
456         }
457     }
458 
459     /**
460      * Opens the connection to the {@link MmTelFeature} and establishes a listener back to the
461      * framework. Calling this method multiple times will reset the listener attached to the
462      * {@link MmTelFeature}.
463      * @param listener A {@link MmTelFeature.Listener} that will be used by the {@link MmTelFeature}
464      * to notify the framework of updates.
465      */
openConnection(MmTelFeature.Listener listener)466     public void openConnection(MmTelFeature.Listener listener) throws RemoteException {
467         synchronized (mLock) {
468             checkServiceIsReady();
469             getServiceInterface(mBinder).setListener(listener);
470         }
471     }
472 
closeConnection()473     public void closeConnection() {
474         mRegistrationCallbackManager.close();
475         mCapabilityCallbackManager.close();
476         try {
477             synchronized (mLock) {
478                 if (isBinderAlive()) {
479                     getServiceInterface(mBinder).setListener(null);
480                 }
481             }
482         } catch (RemoteException e) {
483             Log.w(TAG, "closeConnection: couldn't remove listener!");
484         }
485     }
486 
addRegistrationCallback(ImsRegistrationImplBase.Callback callback)487     public void addRegistrationCallback(ImsRegistrationImplBase.Callback callback)
488             throws RemoteException {
489         mRegistrationCallbackManager.addCallback(callback);
490     }
491 
removeRegistrationCallback(ImsRegistrationImplBase.Callback callback)492     public void removeRegistrationCallback(ImsRegistrationImplBase.Callback callback)
493             throws RemoteException {
494         mRegistrationCallbackManager.removeCallback(callback);
495     }
496 
addCapabilityCallback(ImsFeature.CapabilityCallback callback)497     public void addCapabilityCallback(ImsFeature.CapabilityCallback callback)
498             throws RemoteException {
499         mCapabilityCallbackManager.addCallback(callback);
500     }
501 
removeCapabilityCallback(ImsFeature.CapabilityCallback callback)502     public void removeCapabilityCallback(ImsFeature.CapabilityCallback callback)
503             throws RemoteException {
504         mCapabilityCallbackManager.removeCallback(callback);
505     }
506 
changeEnabledCapabilities(CapabilityChangeRequest request, ImsFeature.CapabilityCallback callback)507     public void changeEnabledCapabilities(CapabilityChangeRequest request,
508             ImsFeature.CapabilityCallback callback) throws RemoteException {
509         synchronized (mLock) {
510             checkServiceIsReady();
511             getServiceInterface(mBinder).changeCapabilitiesConfiguration(request, callback);
512         }
513     }
514 
queryEnabledCapabilities(int capability, int radioTech, ImsFeature.CapabilityCallback callback)515     public void queryEnabledCapabilities(int capability, int radioTech,
516             ImsFeature.CapabilityCallback callback) throws RemoteException {
517         synchronized (mLock) {
518             checkServiceIsReady();
519             getServiceInterface(mBinder).queryCapabilityConfiguration(capability, radioTech,
520                     callback);
521         }
522     }
523 
queryCapabilityStatus()524     public MmTelFeature.MmTelCapabilities queryCapabilityStatus() throws RemoteException {
525         synchronized (mLock) {
526             checkServiceIsReady();
527             return new MmTelFeature.MmTelCapabilities(
528                     getServiceInterface(mBinder).queryCapabilityStatus());
529         }
530     }
531 
createCallProfile(int callServiceType, int callType)532     public ImsCallProfile createCallProfile(int callServiceType, int callType)
533             throws RemoteException {
534         synchronized (mLock) {
535             checkServiceIsReady();
536             return getServiceInterface(mBinder).createCallProfile(callServiceType, callType);
537         }
538     }
539 
createCallSession(ImsCallProfile profile)540     public IImsCallSession createCallSession(ImsCallProfile profile)
541             throws RemoteException {
542         synchronized (mLock) {
543             checkServiceIsReady();
544             return getServiceInterface(mBinder).createCallSession(profile);
545         }
546     }
547 
getUtInterface()548     public IImsUt getUtInterface() throws RemoteException {
549         synchronized (mLock) {
550             checkServiceIsReady();
551             return getServiceInterface(mBinder).getUtInterface();
552         }
553     }
554 
getConfigInterface()555     public IImsConfig getConfigInterface() throws RemoteException {
556         return getConfig();
557     }
558 
getRegistrationTech()559     public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTech()
560             throws RemoteException {
561         IImsRegistration registration = getRegistration();
562         if (registration != null) {
563                 return registration.getRegistrationTechnology();
564         } else {
565             return ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
566         }
567     }
568 
getEcbmInterface()569     public IImsEcbm getEcbmInterface() throws RemoteException {
570         synchronized (mLock) {
571             checkServiceIsReady();
572             return getServiceInterface(mBinder).getEcbmInterface();
573         }
574     }
575 
setUiTTYMode(int uiTtyMode, Message onComplete)576     public void setUiTTYMode(int uiTtyMode, Message onComplete)
577             throws RemoteException {
578         synchronized (mLock) {
579             checkServiceIsReady();
580             getServiceInterface(mBinder).setUiTtyMode(uiTtyMode, onComplete);
581         }
582     }
583 
getMultiEndpointInterface()584     public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
585         synchronized (mLock) {
586             checkServiceIsReady();
587             return getServiceInterface(mBinder).getMultiEndpointInterface();
588         }
589     }
590 
sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, byte[] pdu)591     public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
592             byte[] pdu) throws RemoteException {
593         synchronized (mLock) {
594             checkServiceIsReady();
595             getServiceInterface(mBinder).sendSms(token, messageRef, format, smsc, isRetry,
596                     pdu);
597         }
598     }
599 
acknowledgeSms(int token, int messageRef, @ImsSmsImplBase.SendStatusResult int result)600     public void acknowledgeSms(int token, int messageRef,
601             @ImsSmsImplBase.SendStatusResult int result) throws RemoteException {
602         synchronized (mLock) {
603             checkServiceIsReady();
604             getServiceInterface(mBinder).acknowledgeSms(token, messageRef, result);
605         }
606     }
607 
acknowledgeSmsReport(int token, int messageRef, @ImsSmsImplBase.StatusReportResult int result)608     public void acknowledgeSmsReport(int token, int messageRef,
609             @ImsSmsImplBase.StatusReportResult int result) throws RemoteException {
610         synchronized (mLock) {
611             checkServiceIsReady();
612             getServiceInterface(mBinder).acknowledgeSmsReport(token, messageRef, result);
613         }
614     }
615 
getSmsFormat()616     public String getSmsFormat() throws RemoteException {
617         synchronized (mLock) {
618             checkServiceIsReady();
619             return getServiceInterface(mBinder).getSmsFormat();
620         }
621     }
622 
onSmsReady()623     public void onSmsReady() throws RemoteException {
624         synchronized (mLock) {
625             checkServiceIsReady();
626             getServiceInterface(mBinder).onSmsReady();
627         }
628     }
629 
setSmsListener(IImsSmsListener listener)630     public void setSmsListener(IImsSmsListener listener) throws RemoteException {
631         synchronized (mLock) {
632             checkServiceIsReady();
633             getServiceInterface(mBinder).setSmsListener(listener);
634         }
635     }
636 
shouldProcessCall(boolean isEmergency, String[] numbers)637     public @MmTelFeature.ProcessCallResult int shouldProcessCall(boolean isEmergency,
638             String[] numbers) throws RemoteException {
639         if (isEmergency && !isEmergencyMmTelAvailable()) {
640             // Don't query the ImsService if emergency calling is not available on the ImsService.
641             Log.i(TAG, "MmTel does not support emergency over IMS, fallback to CS.");
642             return MmTelFeature.PROCESS_CALL_CSFB;
643         }
644         synchronized (mLock) {
645             checkServiceIsReady();
646             return getServiceInterface(mBinder).shouldProcessCall(numbers);
647         }
648     }
649 
650     /**
651      * @return an integer describing the current Feature Status, defined in
652      * {@link ImsFeature.ImsState}.
653      */
getFeatureState()654     public int getFeatureState() {
655         synchronized (mLock) {
656             if (isBinderAlive() && mFeatureStateCached != null) {
657                 return mFeatureStateCached;
658             }
659         }
660         // Don't synchronize on Binder call.
661         Integer status = retrieveFeatureState();
662         synchronized (mLock) {
663             if (status == null) {
664                 return ImsFeature.STATE_UNAVAILABLE;
665             }
666             // Cache only non-null value for feature status.
667             mFeatureStateCached = status;
668         }
669         Log.i(TAG, "getFeatureState - returning " + status);
670         return status;
671     }
672 
673     /**
674      * Internal method used to retrieve the feature status from the corresponding ImsService.
675      */
retrieveFeatureState()676     private Integer retrieveFeatureState() {
677         if (mBinder != null) {
678             try {
679                 return getServiceInterface(mBinder).getFeatureState();
680             } catch (RemoteException e) {
681                 // Status check failed, don't update cache
682             }
683         }
684         return null;
685     }
686 
687     /**
688      * @param c Callback that will fire when the feature status has changed.
689      */
setStatusCallback(IFeatureUpdate c)690     public void setStatusCallback(IFeatureUpdate c) {
691         mStatusCallback = c;
692     }
693 
694     /**
695      * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
696      * method returns false, it doesn't mean that the Binder connection is not available (use
697      * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
698      * at this time.
699      *
700      * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
701      * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_UNAVAILABLE}.
702      */
isBinderReady()703     public boolean isBinderReady() {
704         return isBinderAlive() && getFeatureState() == ImsFeature.STATE_READY;
705     }
706 
707     /**
708      * @return false if the binder connection is no longer alive.
709      */
isBinderAlive()710     public boolean isBinderAlive() {
711         return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
712     }
713 
checkServiceIsReady()714     protected void checkServiceIsReady() throws RemoteException {
715         if (!isBinderReady()) {
716             throw new RemoteException("ImsServiceProxy is not ready to accept commands.");
717         }
718     }
719 
getServiceInterface(IBinder b)720     private IImsMmTelFeature getServiceInterface(IBinder b) {
721         return IImsMmTelFeature.Stub.asInterface(b);
722     }
723 
checkBinderConnection()724     protected void checkBinderConnection() throws RemoteException {
725         if (!isBinderAlive()) {
726             throw new RemoteException("ImsServiceProxy is not available for that feature.");
727         }
728     }
729 }
730