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