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