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.CallbackExecutor; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.SystemApi; 24 import android.content.Context; 25 import android.net.Uri; 26 import android.os.RemoteException; 27 import android.telephony.ims.ImsRcsManager; 28 import android.telephony.ims.aidl.CapabilityExchangeAidlWrapper; 29 import android.telephony.ims.aidl.ICapabilityExchangeEventListener; 30 import android.telephony.ims.aidl.IImsCapabilityCallback; 31 import android.telephony.ims.aidl.IImsRcsFeature; 32 import android.telephony.ims.aidl.IOptionsResponseCallback; 33 import android.telephony.ims.aidl.IPublishResponseCallback; 34 import android.telephony.ims.aidl.ISubscribeResponseCallback; 35 import android.telephony.ims.aidl.RcsOptionsResponseAidlWrapper; 36 import android.telephony.ims.aidl.RcsPublishResponseAidlWrapper; 37 import android.telephony.ims.aidl.RcsSubscribeResponseAidlWrapper; 38 import android.telephony.ims.stub.CapabilityExchangeEventListener; 39 import android.telephony.ims.stub.ImsRegistrationImplBase; 40 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase; 41 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback; 42 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback; 43 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback; 44 import android.util.Log; 45 46 import com.android.internal.telephony.util.TelephonyUtils; 47 48 import java.lang.annotation.Retention; 49 import java.lang.annotation.RetentionPolicy; 50 import java.util.HashSet; 51 import java.util.List; 52 import java.util.concurrent.CancellationException; 53 import java.util.concurrent.CompletableFuture; 54 import java.util.concurrent.CompletionException; 55 import java.util.concurrent.ExecutionException; 56 import java.util.concurrent.Executor; 57 import java.util.function.Supplier; 58 59 /** 60 * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend 61 * this class and provide implementations of the RcsFeature methods that they support. 62 * @hide 63 */ 64 @SystemApi 65 public class RcsFeature extends ImsFeature { 66 67 private static final String LOG_TAG = "RcsFeature"; 68 69 private static final class RcsFeatureBinder extends IImsRcsFeature.Stub { 70 // Reference the outer class in order to have better test coverage metrics instead of 71 // creating a inner class referencing the outer class directly. 72 private final RcsFeature mReference; 73 private Executor mExecutor; 74 RcsFeatureBinder(RcsFeature classRef, @CallbackExecutor Executor executor)75 RcsFeatureBinder(RcsFeature classRef, @CallbackExecutor Executor executor) { 76 mReference = classRef; 77 mExecutor = executor; 78 } 79 80 @Override queryCapabilityStatus()81 public int queryCapabilityStatus() throws RemoteException { 82 return executeMethodAsyncForResult( 83 () -> mReference.queryCapabilityStatus().mCapabilities, 84 "queryCapabilityStatus"); 85 } 86 87 @Override addCapabilityCallback(IImsCapabilityCallback c)88 public void addCapabilityCallback(IImsCapabilityCallback c) throws RemoteException { 89 executeMethodAsync(() -> mReference.addCapabilityCallback(c), "addCapabilityCallback"); 90 } 91 92 @Override removeCapabilityCallback(IImsCapabilityCallback c)93 public void removeCapabilityCallback(IImsCapabilityCallback c) throws RemoteException { 94 executeMethodAsync(() -> mReference.removeCapabilityCallback(c), 95 "removeCapabilityCallback"); 96 } 97 98 @Override changeCapabilitiesConfiguration(CapabilityChangeRequest r, IImsCapabilityCallback c)99 public void changeCapabilitiesConfiguration(CapabilityChangeRequest r, 100 IImsCapabilityCallback c) throws RemoteException { 101 executeMethodAsync(() -> mReference.requestChangeEnabledCapabilities(r, c), 102 "changeCapabilitiesConfiguration"); 103 } 104 105 @Override queryCapabilityConfiguration(int capability, int radioTech, IImsCapabilityCallback c)106 public void queryCapabilityConfiguration(int capability, int radioTech, 107 IImsCapabilityCallback c) throws RemoteException { 108 executeMethodAsync(() -> mReference.queryCapabilityConfigurationInternal(capability, 109 radioTech, c), "queryCapabilityConfiguration"); 110 } 111 112 @Override getFeatureState()113 public int getFeatureState() throws RemoteException { 114 return executeMethodAsyncForResult(mReference::getFeatureState, "getFeatureState"); 115 } 116 117 // RcsCapabilityExchangeImplBase specific APIs 118 @Override setCapabilityExchangeEventListener( @ullable ICapabilityExchangeEventListener listener)119 public void setCapabilityExchangeEventListener( 120 @Nullable ICapabilityExchangeEventListener listener) throws RemoteException { 121 // Set the listener wrapper to null if the listener passed in is null. This will notify 122 // the RcsFeature to trigger the destruction of active capability exchange interface. 123 CapabilityExchangeEventListener listenerWrapper = listener != null 124 ? new CapabilityExchangeAidlWrapper(listener) : null; 125 executeMethodAsync(() -> mReference.setCapabilityExchangeEventListener(listenerWrapper), 126 "setCapabilityExchangeEventListener"); 127 } 128 129 @Override publishCapabilities(@onNull String pidfXml, @NonNull IPublishResponseCallback callback)130 public void publishCapabilities(@NonNull String pidfXml, 131 @NonNull IPublishResponseCallback callback) throws RemoteException { 132 PublishResponseCallback callbackWrapper = new RcsPublishResponseAidlWrapper(callback); 133 executeMethodAsync(() -> mReference.getCapabilityExchangeImplBaseInternal() 134 .publishCapabilities(pidfXml, callbackWrapper), "publishCapabilities"); 135 } 136 137 @Override subscribeForCapabilities(@onNull List<Uri> uris, @NonNull ISubscribeResponseCallback callback)138 public void subscribeForCapabilities(@NonNull List<Uri> uris, 139 @NonNull ISubscribeResponseCallback callback) throws RemoteException { 140 SubscribeResponseCallback wrapper = new RcsSubscribeResponseAidlWrapper(callback); 141 executeMethodAsync(() -> mReference.getCapabilityExchangeImplBaseInternal() 142 .subscribeForCapabilities(uris, wrapper), "subscribeForCapabilities"); 143 } 144 145 @Override sendOptionsCapabilityRequest(@onNull Uri contactUri, @NonNull List<String> myCapabilities, @NonNull IOptionsResponseCallback callback)146 public void sendOptionsCapabilityRequest(@NonNull Uri contactUri, 147 @NonNull List<String> myCapabilities, @NonNull IOptionsResponseCallback callback) 148 throws RemoteException { 149 OptionsResponseCallback callbackWrapper = new RcsOptionsResponseAidlWrapper(callback); 150 executeMethodAsync(() -> mReference.getCapabilityExchangeImplBaseInternal() 151 .sendOptionsCapabilityRequest(contactUri, new HashSet<>(myCapabilities), 152 callbackWrapper), "sendOptionsCapabilityRequest"); 153 } 154 155 // Call the methods with a clean calling identity on the executor and wait indefinitely for 156 // the future to return. executeMethodAsync(Runnable r, String errorLogName)157 private void executeMethodAsync(Runnable r, String errorLogName) 158 throws RemoteException { 159 // call with a clean calling identity on the executor and wait indefinitely for the 160 // future to return. 161 try { 162 CompletableFuture.runAsync( 163 () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join(); 164 } catch (CancellationException | CompletionException e) { 165 Log.w(LOG_TAG, "RcsFeatureBinder - " + errorLogName + " exception: " 166 + e.getMessage()); 167 throw new RemoteException(e.getMessage()); 168 } 169 } 170 executeMethodAsyncForResult(Supplier<T> r, String errorLogName)171 private <T> T executeMethodAsyncForResult(Supplier<T> r, 172 String errorLogName) throws RemoteException { 173 // call with a clean calling identity on the executor and wait indefinitely for the 174 // future to return. 175 CompletableFuture<T> future = CompletableFuture.supplyAsync( 176 () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor); 177 try { 178 return future.get(); 179 } catch (ExecutionException | InterruptedException e) { 180 Log.w(LOG_TAG, "RcsFeatureBinder - " + errorLogName + " exception: " 181 + e.getMessage()); 182 throw new RemoteException(e.getMessage()); 183 } 184 } 185 } 186 187 /** 188 * Contains the capabilities defined and supported by a {@link RcsFeature} in the 189 * form of a bitmask. The capabilities that are used in the RcsFeature are 190 * defined as: 191 * {@link ImsRcsManager.RcsImsCapabilityFlag#CAPABILITY_TYPE_OPTIONS_UCE} 192 * {@link ImsRcsManager.RcsImsCapabilityFlag#CAPABILITY_TYPE_PRESENCE_UCE} 193 * 194 * The enabled capabilities of this RcsFeature will be set by the framework 195 * using {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)}. 196 * After the capabilities have been set, the RcsFeature may then perform the necessary bring up 197 * of the capability and notify the capability status as true using 198 * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the 199 * framework that the capability is available for usage. 200 */ 201 public static class RcsImsCapabilities extends Capabilities { 202 203 /** 204 * Use {@link ImsRcsManager.RcsImsCapabilityFlag} instead in case used for public API 205 * @hide 206 */ 207 @Retention(RetentionPolicy.SOURCE) 208 @IntDef(prefix = "CAPABILITY_TYPE_", flag = true, value = { 209 CAPABILITY_TYPE_NONE, 210 CAPABILITY_TYPE_OPTIONS_UCE, 211 CAPABILITY_TYPE_PRESENCE_UCE 212 }) 213 public @interface RcsImsCapabilityFlag {} 214 215 /** 216 * Undefined capability type for initialization 217 */ 218 public static final int CAPABILITY_TYPE_NONE = 0; 219 220 /** 221 * This carrier supports User Capability Exchange using SIP OPTIONS as defined by the 222 * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS. 223 * If not set, this RcsFeature should not service capability requests. 224 */ 225 public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1 << 0; 226 227 /** 228 * This carrier supports User Capability Exchange using a presence server as defined by the 229 * framework. If set, the RcsFeature should support capability exchange using a presence 230 * server. If not set, this RcsFeature should not publish capabilities or service capability 231 * requests using presence. 232 */ 233 public static final int CAPABILITY_TYPE_PRESENCE_UCE = 1 << 1; 234 235 /** 236 * This is used to check the upper range of RCS capability 237 * @hide 238 */ 239 public static final int CAPABILITY_TYPE_MAX = CAPABILITY_TYPE_PRESENCE_UCE + 1; 240 241 /** 242 * Create a new {@link RcsImsCapabilities} instance with the provided capabilities. 243 * @param capabilities The capabilities that are supported for RCS in the form of a 244 * bitfield. 245 */ RcsImsCapabilities(@msRcsManager.RcsImsCapabilityFlag int capabilities)246 public RcsImsCapabilities(@ImsRcsManager.RcsImsCapabilityFlag int capabilities) { 247 super(capabilities); 248 } 249 250 /** 251 * Create a new {@link RcsImsCapabilities} instance with the provided capabilities. 252 * @param capabilities The capabilities instance that are supported for RCS 253 */ RcsImsCapabilities(Capabilities capabilities)254 private RcsImsCapabilities(Capabilities capabilities) { 255 super(capabilities.getMask()); 256 } 257 258 @Override addCapabilities(@msRcsManager.RcsImsCapabilityFlag int capabilities)259 public void addCapabilities(@ImsRcsManager.RcsImsCapabilityFlag int capabilities) { 260 super.addCapabilities(capabilities); 261 } 262 263 @Override removeCapabilities(@msRcsManager.RcsImsCapabilityFlag int capabilities)264 public void removeCapabilities(@ImsRcsManager.RcsImsCapabilityFlag int capabilities) { 265 super.removeCapabilities(capabilities); 266 } 267 268 @Override isCapable(@msRcsManager.RcsImsCapabilityFlag int capabilities)269 public boolean isCapable(@ImsRcsManager.RcsImsCapabilityFlag int capabilities) { 270 return super.isCapable(capabilities); 271 } 272 } 273 274 private Executor mExecutor; 275 private final RcsFeatureBinder mImsRcsBinder; 276 private RcsCapabilityExchangeImplBase mCapabilityExchangeImpl; 277 private CapabilityExchangeEventListener mCapExchangeEventListener; 278 279 /** 280 * Create a new RcsFeature. 281 * <p> 282 * Method stubs called from the framework will be called asynchronously. To specify the 283 * {@link Executor} that the methods stubs will be called, use 284 * {@link RcsFeature#RcsFeature(Executor)} instead. 285 */ RcsFeature()286 public RcsFeature() { 287 super(); 288 // Run on the Binder threads that call them. 289 mImsRcsBinder = new RcsFeatureBinder(this, mExecutor); 290 } 291 292 /** 293 * Create a new RcsFeature using the Executor specified for methods being called by the 294 * framework. 295 * @param executor The executor for the framework to use when executing the methods overridden 296 * by the implementation of RcsFeature. 297 */ RcsFeature(@onNull Executor executor)298 public RcsFeature(@NonNull Executor executor) { 299 super(); 300 if (executor == null) { 301 throw new IllegalArgumentException("executor can not be null."); 302 } 303 mExecutor = executor; 304 // Run on the Binder thread by default. 305 mImsRcsBinder = new RcsFeatureBinder(this, mExecutor); 306 } 307 308 /** 309 * Called when the RcsFeature is initialized. 310 * 311 * @param context The context that is used in the ImsService. 312 * @param slotId The slot ID associated with the RcsFeature. 313 * @hide 314 */ 315 @Override initialize(@onNull Context context, @NonNull int slotId)316 public void initialize(@NonNull Context context, @NonNull int slotId) { 317 super.initialize(context, slotId); 318 // Notify that the RcsFeature is ready. 319 mExecutor.execute(() -> onFeatureReady()); 320 } 321 322 /** 323 * Query the current {@link RcsImsCapabilities} status set by the RcsFeature. If a capability is 324 * set, the {@link RcsFeature} has brought up the capability and is ready for framework 325 * requests. To change the status of the capabilities 326 * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)} should be called. 327 * @return A copy of the current RcsFeature capability status. 328 */ 329 @Override queryCapabilityStatus()330 public @NonNull final RcsImsCapabilities queryCapabilityStatus() { 331 return new RcsImsCapabilities(super.queryCapabilityStatus()); 332 } 333 334 /** 335 * Notify the framework that the capabilities status has changed. If a capability is enabled, 336 * this signals to the framework that the capability has been initialized and is ready. 337 * Call {@link #queryCapabilityStatus()} to return the current capability status. 338 * @param capabilities The current capability status of the RcsFeature. 339 */ notifyCapabilitiesStatusChanged(@onNull RcsImsCapabilities capabilities)340 public final void notifyCapabilitiesStatusChanged(@NonNull RcsImsCapabilities capabilities) { 341 if (capabilities == null) { 342 throw new IllegalArgumentException("RcsImsCapabilities must be non-null!"); 343 } 344 super.notifyCapabilitiesStatusChanged(capabilities); 345 } 346 347 /** 348 * Provides the RcsFeature with the ability to return the framework capability configuration set 349 * by the framework. When the framework calls 350 * {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)} to 351 * enable or disable capability A, this method should return the correct configuration for 352 * capability A afterwards (until it has changed). 353 * @param capability The capability that we are querying the configuration for. 354 * @param radioTech The radio technology type that we are querying. 355 * @return true if the capability is enabled, false otherwise. 356 */ queryCapabilityConfiguration( @msRcsManager.RcsImsCapabilityFlag int capability, @ImsRegistrationImplBase.ImsRegistrationTech int radioTech)357 public boolean queryCapabilityConfiguration( 358 @ImsRcsManager.RcsImsCapabilityFlag int capability, 359 @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) { 360 // Base Implementation - Override to provide functionality 361 return false; 362 } 363 /** 364 * Called from the framework when the {@link RcsImsCapabilities} that have been configured for 365 * this {@link RcsFeature} has changed. 366 * <p> 367 * For each newly enabled capability flag, the corresponding capability should be brought up in 368 * the {@link RcsFeature} and registered on the network. For each newly disabled capability 369 * flag, the corresponding capability should be brought down, and deregistered. Once a new 370 * capability has been initialized and is ready for usage, the status of that capability should 371 * also be set to true using {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This 372 * will notify the framework that the capability is ready. 373 * <p> 374 * If for some reason one or more of these capabilities can not be enabled/disabled, 375 * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError(int, int, int)} should 376 * be called for each capability change that resulted in an error. 377 * @param request The request to change the capability. 378 * @param callback To notify the framework that the result of the capability changes. 379 */ 380 @Override changeEnabledCapabilities(@onNull CapabilityChangeRequest request, @NonNull CapabilityCallbackProxy callback)381 public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request, 382 @NonNull CapabilityCallbackProxy callback) { 383 // Base Implementation - Override to provide functionality 384 } 385 386 /** 387 * Retrieve the implementation of UCE for this {@link RcsFeature}, which can use either 388 * presence or OPTIONS for capability exchange. 389 * 390 * Will only be requested by the framework if capability exchange is configured 391 * as capable during a 392 * {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)} 393 * operation and the RcsFeature sets the status of the capability to true using 394 * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. 395 * 396 * @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange 397 * event to the framework. 398 * @return An instance of {@link RcsCapabilityExchangeImplBase} that implements capability 399 * exchange if it is supported by the device. 400 */ createCapabilityExchangeImpl( @onNull CapabilityExchangeEventListener listener)401 public @NonNull RcsCapabilityExchangeImplBase createCapabilityExchangeImpl( 402 @NonNull CapabilityExchangeEventListener listener) { 403 // Base Implementation, override to implement functionality 404 return new RcsCapabilityExchangeImplBase(); 405 } 406 407 /** 408 * Remove the given CapabilityExchangeImplBase instance. 409 * @param capExchangeImpl The {@link RcsCapabilityExchangeImplBase} instance to be destroyed. 410 */ destroyCapabilityExchangeImpl( @onNull RcsCapabilityExchangeImplBase capExchangeImpl)411 public void destroyCapabilityExchangeImpl( 412 @NonNull RcsCapabilityExchangeImplBase capExchangeImpl) { 413 // Override to implement the process of destroying RcsCapabilityExchangeImplBase instance. 414 } 415 416 /**{@inheritDoc}*/ 417 @Override onFeatureRemoved()418 public void onFeatureRemoved() { 419 420 } 421 422 /**{@inheritDoc}*/ 423 @Override onFeatureReady()424 public void onFeatureReady() { 425 426 } 427 428 /** 429 * @hide 430 */ 431 @Override getBinder()432 public final IImsRcsFeature getBinder() { 433 return mImsRcsBinder; 434 } 435 436 /** 437 * Set the capability exchange listener. 438 * @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange 439 * event to the framework. 440 */ setCapabilityExchangeEventListener( @ullable CapabilityExchangeEventListener listener)441 private void setCapabilityExchangeEventListener( 442 @Nullable CapabilityExchangeEventListener listener) { 443 synchronized (mLock) { 444 mCapExchangeEventListener = listener; 445 if (mCapExchangeEventListener != null) { 446 initRcsCapabilityExchangeImplBase(mCapExchangeEventListener); 447 } else { 448 // Remove the RcsCapabilityExchangeImplBase instance when the capability exchange 449 // instance has been removed in the framework. 450 if (mCapabilityExchangeImpl != null) { 451 destroyCapabilityExchangeImpl(mCapabilityExchangeImpl); 452 } 453 mCapabilityExchangeImpl = null; 454 } 455 } 456 } 457 458 /** 459 * Initialize the RcsCapabilityExchangeImplBase instance if the capability exchange instance 460 * has already been created in the framework. 461 * @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange 462 * event to the framework. 463 */ initRcsCapabilityExchangeImplBase( @onNull CapabilityExchangeEventListener listener)464 private void initRcsCapabilityExchangeImplBase( 465 @NonNull CapabilityExchangeEventListener listener) { 466 synchronized (mLock) { 467 // Remove the original instance 468 if (mCapabilityExchangeImpl != null) { 469 destroyCapabilityExchangeImpl(mCapabilityExchangeImpl); 470 } 471 mCapabilityExchangeImpl = createCapabilityExchangeImpl(listener); 472 } 473 } 474 475 /** 476 * @return the {@link RcsCapabilityExchangeImplBase} associated with the RcsFeature. 477 */ getCapabilityExchangeImplBaseInternal()478 private @NonNull RcsCapabilityExchangeImplBase getCapabilityExchangeImplBaseInternal() { 479 synchronized (mLock) { 480 // The method should not be called if the instance of RcsCapabilityExchangeImplBase has 481 // not been created yet. 482 if (mCapabilityExchangeImpl == null) { 483 throw new IllegalStateException("Session is not available."); 484 } 485 return mCapabilityExchangeImpl; 486 } 487 } 488 489 /** 490 * Set default Executor from ImsService. 491 * @param executor The default executor for the framework to use when executing the methods 492 * overridden by the implementation of RcsFeature. 493 * @hide 494 */ setDefaultExecutor(@onNull Executor executor)495 public final void setDefaultExecutor(@NonNull Executor executor) { 496 if (mImsRcsBinder.mExecutor == null) { 497 mExecutor = executor; 498 mImsRcsBinder.mExecutor = executor; 499 } 500 } 501 } 502