Home | History | Annotate | Download | only in ims
      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 
     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 
    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 
    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 
    134         abstract boolean createConnection() throws RemoteException;
    135 
    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
    148             public void onRegistered(int imsRadioTech) {
    149                 Log.i(TAG, "onRegistered ::");
    150 
    151                 mLocalCallbacks.forEach(l -> l.onRegistered(imsRadioTech));
    152             }
    153 
    154             @Override
    155             public void onRegistering(int imsRadioTech) {
    156                 Log.i(TAG, "onRegistering ::");
    157 
    158                 mLocalCallbacks.forEach(l -> l.onRegistering(imsRadioTech));
    159             }
    160 
    161             @Override
    162             public void onDeregistered(ImsReasonInfo imsReasonInfo) {
    163                 Log.i(TAG, "onDeregistered ::");
    164 
    165                 mLocalCallbacks.forEach(l -> l.onDeregistered(imsReasonInfo));
    166             }
    167 
    168             @Override
    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
    178             public void onSubscriberAssociatedUriChanged(Uri[] uris) {
    179                 Log.i(TAG, "onSubscriberAssociatedUriChanged");
    180 
    181                 mLocalCallbacks.forEach(l -> l.onSubscriberAssociatedUriChanged(uris));
    182             }
    183         }
    184 
    185         @Override
    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
    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
    221             public void onCapabilitiesStatusChanged(ImsFeature.Capabilities config) {
    222                 mLocalCallbacks.forEach(
    223                         callback -> callback.onCapabilitiesStatusChanged(config));
    224             }
    225         }
    226 
    227         @Override
    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
    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 
    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 
    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          */
    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          */
    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 
    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      */
    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 
    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 
    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 
    438     public boolean isEmergencyMmTelAvailable() {
    439         return mSupportsEmergencyCalling;
    440     }
    441 
    442     public IImsServiceFeatureCallback getListener() {
    443         return mListenerBinder;
    444     }
    445 
    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      */
    466     public void openConnection(MmTelFeature.Listener listener) throws RemoteException {
    467         synchronized (mLock) {
    468             checkServiceIsReady();
    469             getServiceInterface(mBinder).setListener(listener);
    470         }
    471     }
    472 
    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 
    487     public void addRegistrationCallback(ImsRegistrationImplBase.Callback callback)
    488             throws RemoteException {
    489         mRegistrationCallbackManager.addCallback(callback);
    490     }
    491 
    492     public void removeRegistrationCallback(ImsRegistrationImplBase.Callback callback)
    493             throws RemoteException {
    494         mRegistrationCallbackManager.removeCallback(callback);
    495     }
    496 
    497     public void addCapabilityCallback(ImsFeature.CapabilityCallback callback)
    498             throws RemoteException {
    499         mCapabilityCallbackManager.addCallback(callback);
    500     }
    501 
    502     public void removeCapabilityCallback(ImsFeature.CapabilityCallback callback)
    503             throws RemoteException {
    504         mCapabilityCallbackManager.removeCallback(callback);
    505     }
    506 
    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 
    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 
    524     public MmTelFeature.MmTelCapabilities queryCapabilityStatus() throws RemoteException {
    525         synchronized (mLock) {
    526             checkServiceIsReady();
    527             return new MmTelFeature.MmTelCapabilities(
    528                     getServiceInterface(mBinder).queryCapabilityStatus());
    529         }
    530     }
    531 
    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 
    540     public IImsCallSession createCallSession(ImsCallProfile profile)
    541             throws RemoteException {
    542         synchronized (mLock) {
    543             checkServiceIsReady();
    544             return getServiceInterface(mBinder).createCallSession(profile);
    545         }
    546     }
    547 
    548     public IImsUt getUtInterface() throws RemoteException {
    549         synchronized (mLock) {
    550             checkServiceIsReady();
    551             return getServiceInterface(mBinder).getUtInterface();
    552         }
    553     }
    554 
    555     public IImsConfig getConfigInterface() throws RemoteException {
    556         return getConfig();
    557     }
    558 
    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 
    569     public IImsEcbm getEcbmInterface() throws RemoteException {
    570         synchronized (mLock) {
    571             checkServiceIsReady();
    572             return getServiceInterface(mBinder).getEcbmInterface();
    573         }
    574     }
    575 
    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 
    584     public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
    585         synchronized (mLock) {
    586             checkServiceIsReady();
    587             return getServiceInterface(mBinder).getMultiEndpointInterface();
    588         }
    589     }
    590 
    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 
    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 
    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 
    616     public String getSmsFormat() throws RemoteException {
    617         synchronized (mLock) {
    618             checkServiceIsReady();
    619             return getServiceInterface(mBinder).getSmsFormat();
    620         }
    621     }
    622 
    623     public void onSmsReady() throws RemoteException {
    624         synchronized (mLock) {
    625             checkServiceIsReady();
    626             getServiceInterface(mBinder).onSmsReady();
    627         }
    628     }
    629 
    630     public void setSmsListener(IImsSmsListener listener) throws RemoteException {
    631         synchronized (mLock) {
    632             checkServiceIsReady();
    633             getServiceInterface(mBinder).setSmsListener(listener);
    634         }
    635     }
    636 
    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      */
    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      */
    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      */
    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      */
    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      */
    710     public boolean isBinderAlive() {
    711         return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
    712     }
    713 
    714     protected void checkServiceIsReady() throws RemoteException {
    715         if (!isBinderReady()) {
    716             throw new RemoteException("ImsServiceProxy is not ready to accept commands.");
    717         }
    718     }
    719 
    720     private IImsMmTelFeature getServiceInterface(IBinder b) {
    721         return IImsMmTelFeature.Stub.asInterface(b);
    722     }
    723 
    724     protected void checkBinderConnection() throws RemoteException {
    725         if (!isBinderAlive()) {
    726             throw new RemoteException("ImsServiceProxy is not available for that feature.");
    727         }
    728     }
    729 }
    730