1 /* 2 * Copyright (C) 2018 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 android.telephony.ims.feature; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.SystemApi; 22 import android.annotation.TestApi; 23 import android.content.Context; 24 import android.os.IInterface; 25 import android.os.RemoteException; 26 import android.telephony.SubscriptionManager; 27 import android.telephony.ims.aidl.IImsCapabilityCallback; 28 import android.telephony.ims.stub.ImsRegistrationImplBase; 29 import android.util.Log; 30 31 import com.android.ims.internal.IImsFeatureStatusCallback; 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.telephony.util.RemoteCallbackListExt; 34 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 import java.util.Map; 38 39 /** 40 * Base class for all IMS features that are supported by the framework. Use a concrete subclass 41 * of {@link ImsFeature}, such as {@link MmTelFeature} or {@link RcsFeature}. 42 * 43 * @hide 44 */ 45 @SystemApi 46 public abstract class ImsFeature { 47 48 private static final String LOG_TAG = "ImsFeature"; 49 50 /** 51 * Invalid feature value 52 * @hide 53 */ 54 public static final int FEATURE_INVALID = -1; 55 // ImsFeatures that are defined in the Manifests. Ensure that these values match the previously 56 // defined values in ImsServiceClass for compatibility purposes. 57 /** 58 * This feature supports emergency calling over MMTEL. If defined, the framework will try to 59 * place an emergency call over IMS first. If it is not defined, the framework will only use 60 * CSFB for emergency calling. 61 * @hide 62 */ 63 @SystemApi 64 public static final int FEATURE_EMERGENCY_MMTEL = 0; 65 /** 66 * This feature supports the MMTEL feature. 67 * @hide 68 */ 69 @SystemApi 70 public static final int FEATURE_MMTEL = 1; 71 /** 72 * This feature supports the RCS feature. 73 * @hide 74 */ 75 @SystemApi 76 public static final int FEATURE_RCS = 2; 77 /** 78 * Total number of features defined 79 * @hide 80 */ 81 public static final int FEATURE_MAX = 3; 82 83 /** 84 * Used for logging purposes. 85 * @hide 86 */ 87 public static final Map<Integer, String> FEATURE_LOG_MAP = Map.of( 88 FEATURE_EMERGENCY_MMTEL, "EMERGENCY_MMTEL", 89 FEATURE_MMTEL, "MMTEL", 90 FEATURE_RCS, "RCS"); 91 92 /** 93 * Integer values defining IMS features that are supported in ImsFeature. 94 * @hide 95 */ 96 @IntDef(flag = true, 97 value = { 98 FEATURE_EMERGENCY_MMTEL, 99 FEATURE_MMTEL, 100 FEATURE_RCS 101 }) 102 @Retention(RetentionPolicy.SOURCE) 103 public @interface FeatureType {} 104 105 /** 106 * Integer values defining the state of the ImsFeature at any time. 107 * @hide 108 */ 109 @IntDef(flag = true, 110 value = { 111 STATE_UNAVAILABLE, 112 STATE_INITIALIZING, 113 STATE_READY, 114 }) 115 @Retention(RetentionPolicy.SOURCE) 116 public @interface ImsState {} 117 118 /** 119 * This {@link ImsFeature}'s state is unavailable and should not be communicated with. This will 120 * remove all bindings back to the framework. Any attempt to communicate with the framework 121 * during this time will result in an {@link IllegalStateException}. 122 * @hide 123 */ 124 @SystemApi 125 public static final int STATE_UNAVAILABLE = 0; 126 /** 127 * This {@link ImsFeature} state is initializing and should not be communicated with. This will 128 * remove all bindings back to the framework. Any attempt to communicate with the framework 129 * during this time will result in an {@link IllegalStateException}. 130 * @hide 131 */ 132 @SystemApi 133 public static final int STATE_INITIALIZING = 1; 134 /** 135 * This {@link ImsFeature} is ready for communication. Do not attempt to call framework methods 136 * until {@see #onFeatureReady()} is called. 137 * @hide 138 */ 139 @SystemApi 140 public static final int STATE_READY = 2; 141 142 /** 143 * Used for logging purposes. 144 * @hide 145 */ 146 public static final Map<Integer, String> STATE_LOG_MAP = Map.of( 147 STATE_UNAVAILABLE, "UNAVAILABLE", 148 STATE_INITIALIZING, "INITIALIZING", 149 STATE_READY, "READY"); 150 151 /** 152 * Integer values defining the result codes that should be returned from 153 * {@link #changeEnabledCapabilities} when the framework tries to set a feature's capability. 154 * @hide 155 */ 156 @IntDef(flag = true, 157 value = { 158 CAPABILITY_ERROR_GENERIC, 159 CAPABILITY_SUCCESS 160 }) 161 @Retention(RetentionPolicy.SOURCE) 162 public @interface ImsCapabilityError {} 163 164 /** 165 * The capability was unable to be changed. 166 * @hide 167 */ 168 @SystemApi 169 public static final int CAPABILITY_ERROR_GENERIC = -1; 170 /** 171 * The capability was able to be changed. 172 * @hide 173 */ 174 @SystemApi 175 public static final int CAPABILITY_SUCCESS = 0; 176 177 /** 178 * Used by the ImsFeature to call back to the CapabilityCallback that the framework has 179 * provided. 180 */ 181 protected static class CapabilityCallbackProxy { 182 private final IImsCapabilityCallback mCallback; 183 184 /** @hide */ CapabilityCallbackProxy(IImsCapabilityCallback c)185 public CapabilityCallbackProxy(IImsCapabilityCallback c) { 186 mCallback = c; 187 } 188 189 /** 190 * This method notifies the provided framework callback that the request to change the 191 * indicated capability has failed and has not changed. 192 * 193 * @param capability The Capability that will be notified to the framework, defined as 194 * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}, 195 * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}, 196 * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT}, or 197 * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS}. 198 * @param radioTech The radio tech that this capability failed for, defined as 199 * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE}, 200 * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} or 201 * {@link ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}. 202 * @param reason The reason this capability was unable to be changed, defined as 203 * {@link #CAPABILITY_ERROR_GENERIC} or {@link #CAPABILITY_SUCCESS}. 204 */ onChangeCapabilityConfigurationError(int capability, int radioTech, @ImsCapabilityError int reason)205 public void onChangeCapabilityConfigurationError(int capability, int radioTech, 206 @ImsCapabilityError int reason) { 207 if (mCallback == null) { 208 return; 209 } 210 try { 211 mCallback.onChangeCapabilityConfigurationError(capability, radioTech, reason); 212 } catch (RemoteException e) { 213 Log.e(LOG_TAG, "onChangeCapabilityConfigurationError called on dead binder."); 214 } 215 } 216 } 217 218 /** 219 * Contains the IMS capabilities defined and supported by an ImsFeature in the form of a 220 * bit-mask. 221 * 222 * @deprecated This class is not used directly, but rather extended in subclasses of 223 * {@link ImsFeature} to provide service specific capabilities. 224 * @see MmTelFeature.MmTelCapabilities 225 * @hide 226 */ 227 // Not Actually deprecated, but we need to remove it from the @SystemApi surface. 228 @Deprecated 229 @SystemApi // SystemApi only because it was leaked through type usage in a previous release. 230 @TestApi 231 public static class Capabilities { 232 /** @deprecated Use getters and accessors instead. */ 233 // Not actually deprecated, but we need to remove it from the @SystemApi surface eventually. 234 protected int mCapabilities = 0; 235 236 /** 237 * @hide 238 */ Capabilities()239 public Capabilities() { 240 } 241 242 /** 243 * @hide 244 */ Capabilities(int capabilities)245 protected Capabilities(int capabilities) { 246 mCapabilities = capabilities; 247 } 248 249 /** 250 * @param capabilities Capabilities to be added to the configuration in the form of a 251 * bit mask. 252 * @hide 253 */ addCapabilities(int capabilities)254 public void addCapabilities(int capabilities) { 255 mCapabilities |= capabilities; 256 } 257 258 /** 259 * @param capabilities Capabilities to be removed to the configuration in the form of a 260 * bit mask. 261 * @hide 262 */ removeCapabilities(int capabilities)263 public void removeCapabilities(int capabilities) { 264 mCapabilities &= ~capabilities; 265 } 266 267 /** 268 * @return true if all of the capabilities specified are capable. 269 * @hide 270 */ isCapable(int capabilities)271 public boolean isCapable(int capabilities) { 272 return (mCapabilities & capabilities) == capabilities; 273 } 274 275 /** 276 * @return a deep copy of the Capabilites. 277 * @hide 278 */ copy()279 public Capabilities copy() { 280 return new Capabilities(mCapabilities); 281 } 282 283 /** 284 * @return a bitmask containing the capability flags directly. 285 * @hide 286 */ getMask()287 public int getMask() { 288 return mCapabilities; 289 } 290 291 /** 292 * @hide 293 */ 294 @Override equals(Object o)295 public boolean equals(Object o) { 296 if (this == o) return true; 297 if (!(o instanceof Capabilities)) return false; 298 299 Capabilities that = (Capabilities) o; 300 301 return mCapabilities == that.mCapabilities; 302 } 303 304 /** 305 * @hide 306 */ 307 @Override hashCode()308 public int hashCode() { 309 return mCapabilities; 310 } 311 312 /** 313 * @hide 314 */ 315 @Override toString()316 public String toString() { 317 return "Capabilities: " + Integer.toBinaryString(mCapabilities); 318 } 319 } 320 321 /** @hide */ 322 protected Context mContext; 323 /** @hide */ 324 protected final Object mLock = new Object(); 325 326 private final RemoteCallbackListExt<IImsFeatureStatusCallback> mStatusCallbacks = 327 new RemoteCallbackListExt<>(); 328 private @ImsState int mState = STATE_UNAVAILABLE; 329 private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; 330 private final RemoteCallbackListExt<IImsCapabilityCallback> mCapabilityCallbacks = 331 new RemoteCallbackListExt<>(); 332 private Capabilities mCapabilityStatus = new Capabilities(); 333 334 /** 335 * @hide 336 */ initialize(Context context, int slotId)337 public void initialize(Context context, int slotId) { 338 mContext = context; 339 mSlotId = slotId; 340 } 341 342 /** 343 * @return The SIM slot index associated with this ImsFeature. 344 * 345 * @see SubscriptionManager#getSubscriptionId(int) for more information on getting the 346 * subscription ID associated with this slot. 347 * @hide 348 */ 349 @SystemApi getSlotIndex()350 public final int getSlotIndex() { 351 return mSlotId; 352 } 353 354 /** 355 * @return The current state of the ImsFeature, set previously by {@link #setFeatureState(int)} 356 * or {@link #STATE_UNAVAILABLE} if it has not been updated yet. 357 * @hide 358 */ 359 @SystemApi getFeatureState()360 public @ImsState int getFeatureState() { 361 synchronized (mLock) { 362 return mState; 363 } 364 } 365 366 /** 367 * Set the state of the ImsFeature. The state is used as a signal to the framework to start or 368 * stop communication, depending on the state sent. 369 * @param state The ImsFeature's state, defined as {@link #STATE_UNAVAILABLE}, 370 * {@link #STATE_INITIALIZING}, or {@link #STATE_READY}. 371 * @hide 372 */ 373 @SystemApi setFeatureState(@msState int state)374 public final void setFeatureState(@ImsState int state) { 375 boolean isNotify = false; 376 synchronized (mLock) { 377 if (mState != state) { 378 mState = state; 379 isNotify = true; 380 } 381 } 382 if (isNotify) { 383 notifyFeatureState(state); 384 } 385 } 386 387 /** 388 * Not final for testing, but shouldn't be extended! 389 * @hide 390 */ 391 @VisibleForTesting addImsFeatureStatusCallback(@onNull IImsFeatureStatusCallback c)392 public void addImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { 393 try { 394 synchronized (mStatusCallbacks) { 395 // Add the callback if the callback completes successfully without a RemoteException 396 mStatusCallbacks.register(c); 397 // If we have just connected, send queued status. 398 c.notifyImsFeatureStatus(getFeatureState()); 399 } 400 } catch (RemoteException e) { 401 Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); 402 } 403 } 404 405 /** 406 * Not final for testing, but shouldn't be extended! 407 * @hide 408 */ 409 @VisibleForTesting removeImsFeatureStatusCallback(@onNull IImsFeatureStatusCallback c)410 public void removeImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { 411 synchronized (mStatusCallbacks) { 412 mStatusCallbacks.unregister(c); 413 } 414 } 415 416 /** 417 * Internal method called by ImsFeature when setFeatureState has changed. 418 */ notifyFeatureState(@msState int state)419 private void notifyFeatureState(@ImsState int state) { 420 synchronized (mStatusCallbacks) { 421 mStatusCallbacks.broadcastAction((c) -> { 422 try { 423 c.notifyImsFeatureStatus(state); 424 } catch (RemoteException e) { 425 Log.w(LOG_TAG, e + " notifyFeatureState() - Skipping " 426 + "callback."); 427 } 428 }); 429 } 430 } 431 432 /** 433 * @hide 434 */ addCapabilityCallback(IImsCapabilityCallback c)435 public final void addCapabilityCallback(IImsCapabilityCallback c) { 436 mCapabilityCallbacks.register(c); 437 try { 438 // Notify the Capability callback that was just registered of the current capabilities. 439 c.onCapabilitiesStatusChanged(queryCapabilityStatus().mCapabilities); 440 } catch (RemoteException e) { 441 Log.w(LOG_TAG, "addCapabilityCallback: error accessing callback: " + e.getMessage()); 442 } 443 } 444 445 /** 446 * @hide 447 */ removeCapabilityCallback(IImsCapabilityCallback c)448 final void removeCapabilityCallback(IImsCapabilityCallback c) { 449 mCapabilityCallbacks.unregister(c); 450 } 451 452 /**@hide*/ queryCapabilityConfigurationInternal(int capability, int radioTech, IImsCapabilityCallback c)453 final void queryCapabilityConfigurationInternal(int capability, int radioTech, 454 IImsCapabilityCallback c) { 455 boolean enabled = queryCapabilityConfiguration(capability, radioTech); 456 try { 457 if (c != null) { 458 c.onQueryCapabilityConfiguration(capability, radioTech, enabled); 459 } 460 } catch (RemoteException e) { 461 Log.e(LOG_TAG, "queryCapabilityConfigurationInternal called on dead binder!"); 462 } 463 } 464 465 /** 466 * @return the cached capabilities status for this feature. 467 * @hide 468 */ 469 @VisibleForTesting queryCapabilityStatus()470 public Capabilities queryCapabilityStatus() { 471 synchronized (mLock) { 472 return mCapabilityStatus.copy(); 473 } 474 } 475 476 /** 477 * Called internally to request the change of enabled capabilities. 478 * @hide 479 */ 480 @VisibleForTesting requestChangeEnabledCapabilities(CapabilityChangeRequest request, IImsCapabilityCallback c)481 public final void requestChangeEnabledCapabilities(CapabilityChangeRequest request, 482 IImsCapabilityCallback c) { 483 if (request == null) { 484 throw new IllegalArgumentException( 485 "ImsFeature#requestChangeEnabledCapabilities called with invalid params."); 486 } 487 changeEnabledCapabilities(request, new CapabilityCallbackProxy(c)); 488 } 489 490 /** 491 * Called by the ImsFeature when the capabilities status has changed. 492 * 493 * @param caps the new {@link Capabilities} status of the {@link ImsFeature}. 494 * 495 * @hide 496 */ notifyCapabilitiesStatusChanged(Capabilities caps)497 protected final void notifyCapabilitiesStatusChanged(Capabilities caps) { 498 synchronized (mLock) { 499 mCapabilityStatus = caps.copy(); 500 } 501 502 synchronized (mCapabilityCallbacks) { 503 mCapabilityCallbacks.broadcastAction((callback) -> { 504 try { 505 Log.d(LOG_TAG, "ImsFeature notifyCapabilitiesStatusChanged Capabilities = " 506 + caps.mCapabilities); 507 callback.onCapabilitiesStatusChanged(caps.mCapabilities); 508 } catch (RemoteException e) { 509 Log.w(LOG_TAG, e + " notifyCapabilitiesStatusChanged() - Skipping " 510 + "callback."); 511 } 512 }); 513 } 514 } 515 516 /** 517 * Provides the ImsFeature with the ability to return the framework Capability Configuration 518 * for a provided Capability. If the framework calls {@link #changeEnabledCapabilities} and 519 * includes a capability A to enable or disable, this method should return the correct enabled 520 * status for capability A. 521 * @param capability The capability that we are querying the configuration for. 522 * @return true if the capability is enabled, false otherwise. 523 * @hide 524 */ 525 @SuppressWarnings("HiddenAbstractMethod") queryCapabilityConfiguration(int capability, int radioTech)526 public abstract boolean queryCapabilityConfiguration(int capability, int radioTech); 527 528 /** 529 * Features should override this method to receive Capability preference change requests from 530 * the framework using the provided {@link CapabilityChangeRequest}. If any of the capabilities 531 * in the {@link CapabilityChangeRequest} are not able to be completed due to an error, 532 * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError} should be called for 533 * each failed capability. 534 * 535 * @param request A {@link CapabilityChangeRequest} containing requested capabilities to 536 * enable/disable. 537 * @param c A {@link CapabilityCallbackProxy}, which will be used to call back to the framework 538 * setting a subset of these capabilities fail, using 539 * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError}. 540 */ changeEnabledCapabilities(CapabilityChangeRequest request, CapabilityCallbackProxy c)541 public abstract void changeEnabledCapabilities(CapabilityChangeRequest request, 542 CapabilityCallbackProxy c); 543 544 /** 545 * Called when the framework is removing this feature and it needs to be cleaned up. 546 */ onFeatureRemoved()547 public abstract void onFeatureRemoved(); 548 549 /** 550 * Called after this ImsFeature has been initialized and has been set to the 551 * {@link ImsState#STATE_READY} state. 552 * <p> 553 * Any attempt by this feature to access the framework before this method is called will return 554 * with an {@link IllegalStateException}. 555 * The IMS provider should use this method to trigger registration for this feature on the IMS 556 * network, if needed. 557 */ onFeatureReady()558 public abstract void onFeatureReady(); 559 560 /** 561 * @return Binder instance that the framework will use to communicate with this feature. 562 * @hide 563 */ 564 @SuppressWarnings("HiddenAbstractMethod") getBinder()565 protected abstract IInterface getBinder(); 566 } 567