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