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.internal.telephony.ims; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.ServiceConnection; 23 import android.content.pm.IPackageManager; 24 import android.os.Handler; 25 import android.os.HandlerThread; 26 import android.os.IBinder; 27 import android.os.IInterface; 28 import android.os.RemoteException; 29 import android.os.ServiceManager; 30 import android.telephony.ims.ImsService; 31 import android.telephony.ims.aidl.IImsConfig; 32 import android.telephony.ims.aidl.IImsMmTelFeature; 33 import android.telephony.ims.aidl.IImsRcsFeature; 34 import android.telephony.ims.aidl.IImsRegistration; 35 import android.telephony.ims.aidl.IImsServiceController; 36 import android.telephony.ims.feature.ImsFeature; 37 import android.telephony.ims.stub.ImsFeatureConfiguration; 38 import android.util.Log; 39 40 import com.android.ims.internal.IImsFeatureStatusCallback; 41 import com.android.ims.internal.IImsServiceFeatureCallback; 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.telephony.ExponentialBackoff; 44 45 import java.util.HashSet; 46 import java.util.Iterator; 47 import java.util.Set; 48 import java.util.concurrent.ConcurrentHashMap; 49 50 /** 51 * Manages the Binding lifecycle of one ImsService as well as the relevant ImsFeatures that the 52 * ImsService will support. 53 * 54 * When the ImsService is first bound, {@link ImsService#createMmTelFeature(int)} and 55 * {@link ImsService#createRcsFeature(int)} will be called 56 * on each feature that the service supports. For each ImsFeature that is created, 57 * {@link ImsServiceControllerCallbacks#imsServiceFeatureCreated} will be called to notify the 58 * listener that the ImsService now supports that feature. 59 * 60 * When {@link #changeImsServiceFeatures} is called with a set of features that is different from 61 * the original set, create and {@link IImsServiceController#removeImsFeature} will be called for 62 * each feature that is created/removed. 63 */ 64 public class ImsServiceController { 65 66 class ImsDeathRecipient implements IBinder.DeathRecipient { 67 68 private ComponentName mComponentName; 69 70 ImsDeathRecipient(ComponentName name) { 71 mComponentName = name; 72 } 73 74 @Override 75 public void binderDied() { 76 Log.e(LOG_TAG, "ImsService(" + mComponentName + ") died. Restarting..."); 77 synchronized (mLock) { 78 mIsBinding = false; 79 mIsBound = false; 80 } 81 notifyAllFeaturesRemoved(); 82 cleanUpService(); 83 startDelayedRebindToService(); 84 } 85 } 86 87 class ImsServiceConnection implements ServiceConnection { 88 89 @Override 90 public void onServiceConnected(ComponentName name, IBinder service) { 91 mBackoff.stop(); 92 synchronized (mLock) { 93 mIsBound = true; 94 mIsBinding = false; 95 Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: " 96 + service); 97 if (service != null) { 98 mImsDeathRecipient = new ImsDeathRecipient(name); 99 try { 100 service.linkToDeath(mImsDeathRecipient, 0); 101 mImsServiceControllerBinder = service; 102 setServiceController(service); 103 notifyImsServiceReady(); 104 // create all associated features in the ImsService 105 for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) { 106 addImsServiceFeature(i); 107 } 108 } catch (RemoteException e) { 109 mIsBound = false; 110 mIsBinding = false; 111 // Remote exception means that the binder already died. 112 if (mImsDeathRecipient != null) { 113 mImsDeathRecipient.binderDied(); 114 } 115 Log.e(LOG_TAG, "ImsService(" + name + ") RemoteException:" 116 + e.getMessage()); 117 } 118 } 119 } 120 } 121 122 @Override 123 public void onServiceDisconnected(ComponentName name) { 124 synchronized (mLock) { 125 mIsBinding = false; 126 } 127 cleanupConnection(); 128 Log.w(LOG_TAG, "ImsService(" + name + "): onServiceDisconnected. Waiting..."); 129 // Service disconnected, but we are still technically bound. Waiting for reconnect. 130 } 131 132 @Override 133 public void onBindingDied(ComponentName name) { 134 synchronized (mLock) { 135 mIsBinding = false; 136 mIsBound = false; 137 } 138 cleanupConnection(); 139 Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDied. Starting rebind..."); 140 startDelayedRebindToService(); 141 } 142 143 private void cleanupConnection() { 144 if (isServiceControllerAvailable()) { 145 mImsServiceControllerBinder.unlinkToDeath(mImsDeathRecipient, 0); 146 } 147 notifyAllFeaturesRemoved(); 148 cleanUpService(); 149 } 150 } 151 152 private ImsService.Listener mFeatureChangedListener = new ImsService.Listener() { 153 @Override 154 public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) { 155 if (mCallbacks == null) { 156 return; 157 } 158 mCallbacks.imsServiceFeaturesChanged(c, ImsServiceController.this); 159 } 160 }; 161 162 /** 163 * Defines callbacks that are used by the ImsServiceController to notify when an ImsService 164 * has created or removed a new feature as well as the associated ImsServiceController. 165 */ 166 public interface ImsServiceControllerCallbacks { 167 /** 168 * Called by ImsServiceController when a new MMTEL or RCS feature has been created. 169 */ 170 void imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller); 171 /** 172 * Called by ImsServiceController when a new MMTEL or RCS feature has been removed. 173 */ 174 void imsServiceFeatureRemoved(int slotId, int feature, ImsServiceController controller); 175 176 /** 177 * Called by the ImsServiceController when the ImsService has notified the framework that 178 * its features have changed. 179 */ 180 void imsServiceFeaturesChanged(ImsFeatureConfiguration config, 181 ImsServiceController controller); 182 } 183 184 /** 185 * Returns the currently defined rebind retry timeout. Used for testing. 186 */ 187 @VisibleForTesting 188 public interface RebindRetry { 189 /** 190 * Returns a long in ms indicating how long the ImsServiceController should wait before 191 * rebinding for the first time. 192 */ 193 long getStartDelay(); 194 195 /** 196 * Returns a long in ms indicating the maximum time the ImsServiceController should wait 197 * before rebinding. 198 */ 199 long getMaximumDelay(); 200 } 201 202 private static final String LOG_TAG = "ImsServiceController"; 203 private static final int REBIND_START_DELAY_MS = 2 * 1000; // 2 seconds 204 private static final int REBIND_MAXIMUM_DELAY_MS = 60 * 1000; // 1 minute 205 private final ComponentName mComponentName; 206 private final HandlerThread mHandlerThread = new HandlerThread("ImsServiceControllerHandler"); 207 private final IPackageManager mPackageManager; 208 private ImsServiceControllerCallbacks mCallbacks; 209 private ExponentialBackoff mBackoff; 210 211 private boolean mIsBound = false; 212 private boolean mIsBinding = false; 213 // Set of a pair of slotId->feature 214 private HashSet<ImsFeatureConfiguration.FeatureSlotPair> mImsFeatures; 215 // Binder interfaces to the features set in mImsFeatures; 216 private HashSet<ImsFeatureContainer> mImsFeatureBinders = new HashSet<>(); 217 private IImsServiceController mIImsServiceController; 218 private IBinder mImsServiceControllerBinder; 219 private ImsServiceConnection mImsServiceConnection; 220 private ImsDeathRecipient mImsDeathRecipient; 221 private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = ConcurrentHashMap.newKeySet(); 222 // Only added or removed, never accessed on purpose. 223 private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>(); 224 225 protected final Object mLock = new Object(); 226 protected final Context mContext; 227 228 private class ImsFeatureContainer { 229 public int slotId; 230 public int featureType; 231 private IInterface mBinder; 232 233 ImsFeatureContainer(int slotId, int featureType, IInterface binder) { 234 this.slotId = slotId; 235 this.featureType = featureType; 236 this.mBinder = binder; 237 } 238 239 // Casts the IInterface into the binder class we are looking for. 240 public <T extends IInterface> T resolve(Class<T> className) { 241 return className.cast(mBinder); 242 } 243 244 @Override 245 public boolean equals(Object o) { 246 if (this == o) return true; 247 if (o == null || getClass() != o.getClass()) return false; 248 249 ImsFeatureContainer that = (ImsFeatureContainer) o; 250 251 if (slotId != that.slotId) return false; 252 if (featureType != that.featureType) return false; 253 return mBinder != null ? mBinder.equals(that.mBinder) : that.mBinder == null; 254 } 255 256 @Override 257 public int hashCode() { 258 int result = slotId; 259 result = 31 * result + featureType; 260 result = 31 * result + (mBinder != null ? mBinder.hashCode() : 0); 261 return result; 262 } 263 } 264 265 /** 266 * Container class for the IImsFeatureStatusCallback callback implementation. This class is 267 * never used directly, but we need to keep track of the IImsFeatureStatusCallback 268 * implementations explicitly. 269 */ 270 private class ImsFeatureStatusCallback { 271 private int mSlotId; 272 private int mFeatureType; 273 274 private final IImsFeatureStatusCallback mCallback = new IImsFeatureStatusCallback.Stub() { 275 276 @Override 277 public void notifyImsFeatureStatus(int featureStatus) throws RemoteException { 278 Log.i(LOG_TAG, "notifyImsFeatureStatus: slot=" + mSlotId + ", feature=" 279 + mFeatureType + ", status=" + featureStatus); 280 sendImsFeatureStatusChanged(mSlotId, mFeatureType, featureStatus); 281 } 282 }; 283 284 ImsFeatureStatusCallback(int slotId, int featureType) { 285 mSlotId = slotId; 286 mFeatureType = featureType; 287 } 288 289 public IImsFeatureStatusCallback getCallback() { 290 return mCallback; 291 } 292 } 293 294 // Retry the bind to the ImsService that has died after mRebindRetry timeout. 295 private Runnable mRestartImsServiceRunnable = new Runnable() { 296 @Override 297 public void run() { 298 synchronized (mLock) { 299 if (mIsBound) { 300 return; 301 } 302 bind(mImsFeatures); 303 } 304 } 305 }; 306 307 private RebindRetry mRebindRetry = new RebindRetry() { 308 @Override 309 public long getStartDelay() { 310 return REBIND_START_DELAY_MS; 311 } 312 313 @Override 314 public long getMaximumDelay() { 315 return REBIND_MAXIMUM_DELAY_MS; 316 } 317 }; 318 319 public ImsServiceController(Context context, ComponentName componentName, 320 ImsServiceControllerCallbacks callbacks) { 321 mContext = context; 322 mComponentName = componentName; 323 mCallbacks = callbacks; 324 mHandlerThread.start(); 325 mBackoff = new ExponentialBackoff( 326 mRebindRetry.getStartDelay(), 327 mRebindRetry.getMaximumDelay(), 328 2, /* multiplier */ 329 mHandlerThread.getLooper(), 330 mRestartImsServiceRunnable); 331 mPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); 332 } 333 334 @VisibleForTesting 335 // Creating a new HandlerThread and background handler for each test causes a segfault, so for 336 // testing, use a handler supplied by the testing system. 337 public ImsServiceController(Context context, ComponentName componentName, 338 ImsServiceControllerCallbacks callbacks, Handler handler, RebindRetry rebindRetry) { 339 mContext = context; 340 mComponentName = componentName; 341 mCallbacks = callbacks; 342 mBackoff = new ExponentialBackoff( 343 rebindRetry.getStartDelay(), 344 rebindRetry.getMaximumDelay(), 345 2, /* multiplier */ 346 handler, 347 mRestartImsServiceRunnable); 348 mPackageManager = null; 349 } 350 351 /** 352 * Sends request to bind to ImsService designated by the {@link ComponentName} with the feature 353 * set imsFeatureSet. 354 * 355 * @param imsFeatureSet a Set of Pairs that designate the slotId->featureId that need to be 356 * created once the service is bound. 357 * @return {@link true} if the service is in the process of being bound, {@link false} if it 358 * has failed. 359 */ 360 public boolean bind(HashSet<ImsFeatureConfiguration.FeatureSlotPair> imsFeatureSet) { 361 synchronized (mLock) { 362 if (!mIsBound && !mIsBinding) { 363 mIsBinding = true; 364 mImsFeatures = imsFeatureSet; 365 grantPermissionsToService(); 366 Intent imsServiceIntent = new Intent(getServiceInterface()).setComponent( 367 mComponentName); 368 mImsServiceConnection = new ImsServiceConnection(); 369 int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE 370 | Context.BIND_IMPORTANT; 371 Log.i(LOG_TAG, "Binding ImsService:" + mComponentName); 372 try { 373 boolean bindSucceeded = startBindToService(imsServiceIntent, 374 mImsServiceConnection, serviceFlags); 375 if (!bindSucceeded) { 376 mBackoff.notifyFailed(); 377 } 378 return bindSucceeded; 379 } catch (Exception e) { 380 mBackoff.notifyFailed(); 381 Log.e(LOG_TAG, "Error binding (" + mComponentName + ") with exception: " 382 + e.getMessage() + ", rebinding in " + mBackoff.getCurrentDelay() 383 + " ms"); 384 return false; 385 } 386 } else { 387 return false; 388 } 389 } 390 } 391 392 /** 393 * Starts the bind to the ImsService. Overridden by subclasses that need to access the service 394 * in a different fashion. 395 */ 396 protected boolean startBindToService(Intent intent, ImsServiceConnection connection, 397 int flags) { 398 return mContext.bindService(intent, connection, flags); 399 } 400 401 /** 402 * Calls {@link IImsServiceController#removeImsFeature} on all features that the 403 * ImsService supports and then unbinds the service. 404 */ 405 public void unbind() throws RemoteException { 406 synchronized (mLock) { 407 mBackoff.stop(); 408 if (mImsServiceConnection == null || mImsDeathRecipient == null) { 409 return; 410 } 411 // Clean up all features 412 changeImsServiceFeatures(new HashSet<>()); 413 removeImsServiceFeatureCallbacks(); 414 mImsServiceControllerBinder.unlinkToDeath(mImsDeathRecipient, 0); 415 Log.i(LOG_TAG, "Unbinding ImsService: " + mComponentName); 416 mContext.unbindService(mImsServiceConnection); 417 cleanUpService(); 418 } 419 } 420 421 /** 422 * For every feature that is added, the service calls the associated create. For every 423 * ImsFeature that is removed, {@link IImsServiceController#removeImsFeature} is called. 424 */ 425 public void changeImsServiceFeatures( 426 HashSet<ImsFeatureConfiguration.FeatureSlotPair> newImsFeatures) 427 throws RemoteException { 428 synchronized (mLock) { 429 Log.i(LOG_TAG, "Features changed (" + mImsFeatures + "->" + newImsFeatures + ") for " 430 + "ImsService: " + mComponentName); 431 HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldImsFeatures = 432 new HashSet<>(mImsFeatures); 433 // Set features first in case we lose binding and need to rebind later. 434 mImsFeatures = newImsFeatures; 435 if (mIsBound) { 436 // add features to service. 437 HashSet<ImsFeatureConfiguration.FeatureSlotPair> newFeatures = 438 new HashSet<>(mImsFeatures); 439 newFeatures.removeAll(oldImsFeatures); 440 for (ImsFeatureConfiguration.FeatureSlotPair i : newFeatures) { 441 addImsServiceFeature(i); 442 } 443 // remove old features 444 HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldFeatures = 445 new HashSet<>(oldImsFeatures); 446 oldFeatures.removeAll(mImsFeatures); 447 for (ImsFeatureConfiguration.FeatureSlotPair i : oldFeatures) { 448 removeImsServiceFeature(i); 449 } 450 } 451 } 452 } 453 454 @VisibleForTesting 455 public IImsServiceController getImsServiceController() { 456 return mIImsServiceController; 457 } 458 459 @VisibleForTesting 460 public IBinder getImsServiceControllerBinder() { 461 return mImsServiceControllerBinder; 462 } 463 464 @VisibleForTesting 465 public long getRebindDelay() { 466 return mBackoff.getCurrentDelay(); 467 } 468 469 public ComponentName getComponentName() { 470 return mComponentName; 471 } 472 473 /** 474 * Add a callback to ImsManager that signals a new feature that the ImsServiceProxy can handle. 475 */ 476 public void addImsServiceFeatureCallback(IImsServiceFeatureCallback callback) { 477 mImsStatusCallbacks.add(callback); 478 synchronized (mLock) { 479 if (mImsFeatures == null || mImsFeatures.isEmpty()) { 480 return; 481 } 482 // notify the new status callback of the features that are available. 483 try { 484 for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) { 485 callback.imsFeatureCreated(i.slotId, i.featureType); 486 } 487 } catch (RemoteException e) { 488 Log.w(LOG_TAG, "addImsServiceFeatureCallback: exception notifying callback"); 489 } 490 } 491 } 492 493 public void enableIms(int slotId) { 494 try { 495 synchronized (mLock) { 496 if (isServiceControllerAvailable()) { 497 mIImsServiceController.enableIms(slotId); 498 } 499 } 500 } catch (RemoteException e) { 501 Log.w(LOG_TAG, "Couldn't enable IMS: " + e.getMessage()); 502 } 503 } 504 505 public void disableIms(int slotId) { 506 try { 507 synchronized (mLock) { 508 if (isServiceControllerAvailable()) { 509 mIImsServiceController.disableIms(slotId); 510 } 511 } 512 } catch (RemoteException e) { 513 Log.w(LOG_TAG, "Couldn't disable IMS: " + e.getMessage()); 514 } 515 } 516 517 /** 518 * Return the {@Link MMTelFeature} binder on the slot associated with the slotId. 519 * Used for normal calling. 520 */ 521 public IImsMmTelFeature getMmTelFeature(int slotId) { 522 synchronized (mLock) { 523 ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.FEATURE_MMTEL); 524 if (f == null) { 525 Log.w(LOG_TAG, "Requested null MMTelFeature on slot " + slotId); 526 return null; 527 } 528 return f.resolve(IImsMmTelFeature.class); 529 } 530 } 531 532 /** 533 * Return the {@Link RcsFeature} binder on the slot associated with the slotId. 534 */ 535 public IImsRcsFeature getRcsFeature(int slotId) { 536 synchronized (mLock) { 537 ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.FEATURE_RCS); 538 if (f == null) { 539 Log.w(LOG_TAG, "Requested null RcsFeature on slot " + slotId); 540 return null; 541 } 542 return f.resolve(IImsRcsFeature.class); 543 } 544 } 545 546 /** 547 * @return the IImsRegistration that corresponds to the slot id specified. 548 */ 549 public IImsRegistration getRegistration(int slotId) throws RemoteException { 550 synchronized (mLock) { 551 return isServiceControllerAvailable() 552 ? mIImsServiceController.getRegistration(slotId) : null; 553 } 554 } 555 556 /** 557 * @return the IImsConfig that corresponds to the slot id specified. 558 */ 559 public IImsConfig getConfig(int slotId) throws RemoteException { 560 synchronized (mLock) { 561 return isServiceControllerAvailable() ? mIImsServiceController.getConfig(slotId) : null; 562 } 563 } 564 565 /** 566 * notify the ImsService that the ImsService is ready for feature creation. 567 */ 568 protected void notifyImsServiceReady() throws RemoteException { 569 synchronized (mLock) { 570 if (isServiceControllerAvailable()) { 571 Log.d(LOG_TAG, "notifyImsServiceReady"); 572 mIImsServiceController.setListener(mFeatureChangedListener); 573 mIImsServiceController.notifyImsServiceReadyForFeatureCreation(); 574 } 575 } 576 } 577 578 protected String getServiceInterface() { 579 return ImsService.SERVICE_INTERFACE; 580 } 581 582 /** 583 * Sets the IImsServiceController instance. Overridden by compat layers to set compatibility 584 * versions of this service controller. 585 */ 586 protected void setServiceController(IBinder serviceController) { 587 mIImsServiceController = IImsServiceController.Stub.asInterface(serviceController); 588 } 589 590 /** 591 * @return true if the controller is currently bound. 592 */ 593 public boolean isBound() { 594 synchronized (mLock) { 595 return mIsBound; 596 } 597 } 598 599 /** 600 * Check to see if the service controller is available, overridden for compat versions, 601 * @return true if available, false otherwise; 602 */ 603 protected boolean isServiceControllerAvailable() { 604 return mIImsServiceController != null; 605 } 606 607 @VisibleForTesting 608 public void removeImsServiceFeatureCallbacks() { 609 mImsStatusCallbacks.clear(); 610 } 611 612 // Only add a new rebind if there are no pending rebinds waiting. 613 private void startDelayedRebindToService() { 614 mBackoff.start(); 615 } 616 617 // Grant runtime permissions to ImsService. PackageManager ensures that the ImsService is 618 // system/signed before granting permissions. 619 private void grantPermissionsToService() { 620 Log.i(LOG_TAG, "Granting Runtime permissions to:" + getComponentName()); 621 String[] pkgToGrant = {mComponentName.getPackageName()}; 622 try { 623 if (mPackageManager != null) { 624 mPackageManager.grantDefaultPermissionsToEnabledImsServices(pkgToGrant, 625 mContext.getUserId()); 626 } 627 } catch (RemoteException e) { 628 Log.w(LOG_TAG, "Unable to grant permissions, binder died."); 629 } 630 } 631 632 private void sendImsFeatureCreatedCallback(int slot, int feature) { 633 for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator(); 634 i.hasNext(); ) { 635 IImsServiceFeatureCallback callbacks = i.next(); 636 try { 637 callbacks.imsFeatureCreated(slot, feature); 638 } catch (RemoteException e) { 639 // binder died, remove callback. 640 Log.w(LOG_TAG, "sendImsFeatureCreatedCallback: Binder died, removing " 641 + "callback. Exception:" + e.getMessage()); 642 i.remove(); 643 } 644 } 645 } 646 647 private void sendImsFeatureRemovedCallback(int slot, int feature) { 648 for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator(); 649 i.hasNext(); ) { 650 IImsServiceFeatureCallback callbacks = i.next(); 651 try { 652 callbacks.imsFeatureRemoved(slot, feature); 653 } catch (RemoteException e) { 654 // binder died, remove callback. 655 Log.w(LOG_TAG, "sendImsFeatureRemovedCallback: Binder died, removing " 656 + "callback. Exception:" + e.getMessage()); 657 i.remove(); 658 } 659 } 660 } 661 662 private void sendImsFeatureStatusChanged(int slot, int feature, int status) { 663 for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator(); 664 i.hasNext(); ) { 665 IImsServiceFeatureCallback callbacks = i.next(); 666 try { 667 callbacks.imsStatusChanged(slot, feature, status); 668 } catch (RemoteException e) { 669 // binder died, remove callback. 670 Log.w(LOG_TAG, "sendImsFeatureStatusChanged: Binder died, removing " 671 + "callback. Exception:" + e.getMessage()); 672 i.remove(); 673 } 674 } 675 } 676 677 // This method should only be called when synchronized on mLock 678 private void addImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair) 679 throws RemoteException { 680 if (!isServiceControllerAvailable() || mCallbacks == null) { 681 Log.w(LOG_TAG, "addImsServiceFeature called with null values."); 682 return; 683 } 684 if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) { 685 ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.slotId, 686 featurePair.featureType); 687 mFeatureStatusCallbacks.add(c); 688 IInterface f = createImsFeature(featurePair.slotId, featurePair.featureType, 689 c.getCallback()); 690 addImsFeatureBinder(featurePair.slotId, featurePair.featureType, f); 691 // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController 692 mCallbacks.imsServiceFeatureCreated(featurePair.slotId, featurePair.featureType, this); 693 } else { 694 // Don't update ImsService for emergency MMTEL feature. 695 Log.i(LOG_TAG, "supports emergency calling on slot " + featurePair.slotId); 696 } 697 // Send callback to ImsServiceProxy to change supported ImsFeatures including emergency 698 // MMTEL state. 699 sendImsFeatureCreatedCallback(featurePair.slotId, featurePair.featureType); 700 } 701 702 // This method should only be called when synchronized on mLock 703 private void removeImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair) 704 throws RemoteException { 705 if (!isServiceControllerAvailable() || mCallbacks == null) { 706 Log.w(LOG_TAG, "removeImsServiceFeature called with null values."); 707 return; 708 } 709 if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) { 710 ImsFeatureStatusCallback callbackToRemove = mFeatureStatusCallbacks.stream().filter(c -> 711 c.mSlotId == featurePair.slotId && c.mFeatureType == featurePair.featureType) 712 .findFirst().orElse(null); 713 // Remove status callbacks from list. 714 if (callbackToRemove != null) { 715 mFeatureStatusCallbacks.remove(callbackToRemove); 716 } 717 removeImsFeature(featurePair.slotId, featurePair.featureType, 718 (callbackToRemove != null ? callbackToRemove.getCallback() : null)); 719 removeImsFeatureBinder(featurePair.slotId, featurePair.featureType); 720 // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController 721 mCallbacks.imsServiceFeatureRemoved(featurePair.slotId, featurePair.featureType, this); 722 } else { 723 // Don't update ImsService for emergency MMTEL feature. 724 Log.i(LOG_TAG, "doesn't support emergency calling on slot " + featurePair.slotId); 725 } 726 // Send callback to ImsServiceProxy to change supported ImsFeatures 727 // Ensure that ImsServiceProxy callback occurs after ImsResolver callback. If an 728 // ImsManager requests the ImsService while it is being removed in ImsResolver, this 729 // callback will clean it up after. 730 sendImsFeatureRemovedCallback(featurePair.slotId, featurePair.featureType); 731 } 732 733 // This method should only be called when already synchronized on mLock. 734 // overridden by compat layer to create features 735 protected IInterface createImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) 736 throws RemoteException { 737 switch (featureType) { 738 case ImsFeature.FEATURE_MMTEL: { 739 return mIImsServiceController.createMmTelFeature(slotId, c); 740 } 741 case ImsFeature.FEATURE_RCS: { 742 return mIImsServiceController.createRcsFeature(slotId, c); 743 } 744 default: 745 return null; 746 } 747 } 748 749 // overridden by compat layer to remove features 750 protected void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) 751 throws RemoteException { 752 mIImsServiceController.removeImsFeature(slotId, featureType, c); 753 } 754 755 // This method should only be called when synchronized on mLock 756 private void addImsFeatureBinder(int slotId, int featureType, IInterface b) { 757 mImsFeatureBinders.add(new ImsFeatureContainer(slotId, featureType, b)); 758 } 759 760 // This method should only be called when synchronized on mLock 761 private void removeImsFeatureBinder(int slotId, int featureType) { 762 ImsFeatureContainer container = mImsFeatureBinders.stream() 763 .filter(f-> (f.slotId == slotId && f.featureType == featureType)) 764 .findFirst().orElse(null); 765 if (container != null) { 766 mImsFeatureBinders.remove(container); 767 } 768 } 769 770 private ImsFeatureContainer getImsFeatureContainer(int slotId, int featureType) { 771 return mImsFeatureBinders.stream() 772 .filter(f-> (f.slotId == slotId && f.featureType == featureType)) 773 .findFirst().orElse(null); 774 } 775 776 private void notifyAllFeaturesRemoved() { 777 if (mCallbacks == null) { 778 Log.w(LOG_TAG, "notifyAllFeaturesRemoved called with invalid callbacks."); 779 return; 780 } 781 synchronized (mLock) { 782 for (ImsFeatureConfiguration.FeatureSlotPair feature : mImsFeatures) { 783 if (feature.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) { 784 // don't update ImsServiceController for emergency MMTEL. 785 mCallbacks.imsServiceFeatureRemoved(feature.slotId, feature.featureType, this); 786 } 787 sendImsFeatureRemovedCallback(feature.slotId, feature.featureType); 788 } 789 } 790 } 791 792 private void cleanUpService() { 793 synchronized (mLock) { 794 mImsDeathRecipient = null; 795 mImsServiceConnection = null; 796 mImsServiceControllerBinder = null; 797 setServiceController(null); 798 } 799 } 800 } 801