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.RcsUceAdapter; 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 final 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 CapabilityExchangeEventListener listenerWrapper = 122 new CapabilityExchangeAidlWrapper(listener); 123 executeMethodAsync(() -> mReference.setCapabilityExchangeEventListener(listenerWrapper), 124 "setCapabilityExchangeEventListener"); 125 } 126 127 @Override publishCapabilities(@onNull String pidfXml, @NonNull IPublishResponseCallback callback)128 public void publishCapabilities(@NonNull String pidfXml, 129 @NonNull IPublishResponseCallback callback) throws RemoteException { 130 PublishResponseCallback callbackWrapper = new RcsPublishResponseAidlWrapper(callback); 131 executeMethodAsync(() -> mReference.getCapabilityExchangeImplBaseInternal() 132 .publishCapabilities(pidfXml, callbackWrapper), "publishCapabilities"); 133 } 134 135 @Override subscribeForCapabilities(@onNull List<Uri> uris, @NonNull ISubscribeResponseCallback callback)136 public void subscribeForCapabilities(@NonNull List<Uri> uris, 137 @NonNull ISubscribeResponseCallback callback) throws RemoteException { 138 SubscribeResponseCallback wrapper = new RcsSubscribeResponseAidlWrapper(callback); 139 executeMethodAsync(() -> mReference.getCapabilityExchangeImplBaseInternal() 140 .subscribeForCapabilities(uris, wrapper), "subscribeForCapabilities"); 141 } 142 143 @Override sendOptionsCapabilityRequest(@onNull Uri contactUri, @NonNull List<String> myCapabilities, @NonNull IOptionsResponseCallback callback)144 public void sendOptionsCapabilityRequest(@NonNull Uri contactUri, 145 @NonNull List<String> myCapabilities, @NonNull IOptionsResponseCallback callback) 146 throws RemoteException { 147 OptionsResponseCallback callbackWrapper = new RcsOptionsResponseAidlWrapper(callback); 148 executeMethodAsync(() -> mReference.getCapabilityExchangeImplBaseInternal() 149 .sendOptionsCapabilityRequest(contactUri, new HashSet<>(myCapabilities), 150 callbackWrapper), "sendOptionsCapabilityRequest"); 151 } 152 153 // Call the methods with a clean calling identity on the executor and wait indefinitely for 154 // the future to return. executeMethodAsync(Runnable r, String errorLogName)155 private void executeMethodAsync(Runnable r, String errorLogName) 156 throws RemoteException { 157 // call with a clean calling identity on the executor and wait indefinitely for the 158 // future to return. 159 try { 160 CompletableFuture.runAsync( 161 () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join(); 162 } catch (CancellationException | CompletionException e) { 163 Log.w(LOG_TAG, "RcsFeatureBinder - " + errorLogName + " exception: " 164 + e.getMessage()); 165 throw new RemoteException(e.getMessage()); 166 } 167 } 168 executeMethodAsyncForResult(Supplier<T> r, String errorLogName)169 private <T> T executeMethodAsyncForResult(Supplier<T> r, 170 String errorLogName) throws RemoteException { 171 // call with a clean calling identity on the executor and wait indefinitely for the 172 // future to return. 173 CompletableFuture<T> future = CompletableFuture.supplyAsync( 174 () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor); 175 try { 176 return future.get(); 177 } catch (ExecutionException | InterruptedException e) { 178 Log.w(LOG_TAG, "RcsFeatureBinder - " + errorLogName + " exception: " 179 + e.getMessage()); 180 throw new RemoteException(e.getMessage()); 181 } 182 } 183 } 184 185 /** 186 * Contains the capabilities defined and supported by a {@link RcsFeature} in the 187 * form of a bitmask. The capabilities that are used in the RcsFeature are 188 * defined as: 189 * {@link RcsUceAdatper.RcsImsCapabilityFlag#CAPABILITY_TYPE_OPTIONS_UCE} 190 * {@link RceUceAdapter.RcsImsCapabilityFlag#CAPABILITY_TYPE_PRESENCE_UCE} 191 * 192 * The enabled capabilities of this RcsFeature will be set by the framework 193 * using {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)}. 194 * After the capabilities have been set, the RcsFeature may then perform the necessary bring up 195 * of the capability and notify the capability status as true using 196 * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the 197 * framework that the capability is available for usage. 198 */ 199 public static class RcsImsCapabilities extends Capabilities { 200 /** @hide*/ 201 @Retention(RetentionPolicy.SOURCE) 202 @IntDef(prefix = "CAPABILITY_TYPE_", flag = true, value = { 203 CAPABILITY_TYPE_NONE, 204 CAPABILITY_TYPE_OPTIONS_UCE, 205 CAPABILITY_TYPE_PRESENCE_UCE 206 }) 207 public @interface RcsImsCapabilityFlag {} 208 209 /** 210 * Undefined capability type for initialization 211 */ 212 public static final int CAPABILITY_TYPE_NONE = 0; 213 214 /** 215 * This carrier supports User Capability Exchange using SIP OPTIONS as defined by the 216 * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS. 217 * If not set, this RcsFeature should not service capability requests. 218 */ 219 public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1 << 0; 220 221 /** 222 * This carrier supports User Capability Exchange using a presence server as defined by the 223 * framework. If set, the RcsFeature should support capability exchange using a presence 224 * server. If not set, this RcsFeature should not publish capabilities or service capability 225 * requests using presence. 226 */ 227 public static final int CAPABILITY_TYPE_PRESENCE_UCE = 1 << 1; 228 229 /** 230 * Create a new {@link RcsImsCapabilities} instance with the provided capabilities. 231 * @param capabilities The capabilities that are supported for RCS in the form of a 232 * bitfield. 233 */ RcsImsCapabilities(@csUceAdapter.RcsImsCapabilityFlag int capabilities)234 public RcsImsCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { 235 super(capabilities); 236 } 237 238 /** 239 * Create a new {@link RcsImsCapabilities} instance with the provided capabilities. 240 * @param capabilities The capabilities instance that are supported for RCS 241 */ RcsImsCapabilities(Capabilities capabilities)242 private RcsImsCapabilities(Capabilities capabilities) { 243 super(capabilities.getMask()); 244 } 245 246 @Override addCapabilities(@csUceAdapter.RcsImsCapabilityFlag int capabilities)247 public void addCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { 248 super.addCapabilities(capabilities); 249 } 250 251 @Override removeCapabilities(@csUceAdapter.RcsImsCapabilityFlag int capabilities)252 public void removeCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { 253 super.removeCapabilities(capabilities); 254 } 255 256 @Override isCapable(@csUceAdapter.RcsImsCapabilityFlag int capabilities)257 public boolean isCapable(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { 258 return super.isCapable(capabilities); 259 } 260 } 261 262 private final Executor mExecutor; 263 private final RcsFeatureBinder mImsRcsBinder; 264 private RcsCapabilityExchangeImplBase mCapabilityExchangeImpl; 265 private CapabilityExchangeEventListener mCapExchangeEventListener; 266 267 /** 268 * Create a new RcsFeature. 269 * <p> 270 * Method stubs called from the framework will be called asynchronously. To specify the 271 * {@link Executor} that the methods stubs will be called, use 272 * {@link RcsFeature#RcsFeature(Executor)} instead. 273 * 274 * @deprecated Use {@link #RcsFeature(Executor)} to create the RcsFeature. 275 */ 276 @Deprecated RcsFeature()277 public RcsFeature() { 278 super(); 279 mExecutor = Runnable::run; 280 // Run on the Binder threads that call them. 281 mImsRcsBinder = new RcsFeatureBinder(this, mExecutor); 282 } 283 284 /** 285 * Create a new RcsFeature using the Executor specified for methods being called by the 286 * framework. 287 * @param executor The executor for the framework to use when executing the methods overridden 288 * by the implementation of RcsFeature. 289 */ RcsFeature(@onNull Executor executor)290 public RcsFeature(@NonNull Executor executor) { 291 super(); 292 if (executor == null) { 293 throw new IllegalArgumentException("executor can not be null."); 294 } 295 mExecutor = executor; 296 // Run on the Binder thread by default. 297 mImsRcsBinder = new RcsFeatureBinder(this, mExecutor); 298 } 299 300 /** 301 * Called when the RcsFeature is initialized. 302 * 303 * @param context The context that is used in the ImsService. 304 * @param slotId The slot ID associated with the RcsFeature. 305 * @hide 306 */ 307 @Override initialize(Context context, int slotId)308 public void initialize(Context context, int slotId) { 309 super.initialize(context, slotId); 310 // Notify that the RcsFeature is ready. 311 mExecutor.execute(() -> onFeatureReady()); 312 } 313 314 /** 315 * Query the current {@link RcsImsCapabilities} status set by the RcsFeature. If a capability is 316 * set, the {@link RcsFeature} has brought up the capability and is ready for framework 317 * requests. To change the status of the capabilities 318 * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)} should be called. 319 * @return A copy of the current RcsFeature capability status. 320 */ 321 @Override queryCapabilityStatus()322 public @NonNull final RcsImsCapabilities queryCapabilityStatus() { 323 return new RcsImsCapabilities(super.queryCapabilityStatus()); 324 } 325 326 /** 327 * Notify the framework that the capabilities status has changed. If a capability is enabled, 328 * this signals to the framework that the capability has been initialized and is ready. 329 * Call {@link #queryCapabilityStatus()} to return the current capability status. 330 * @param capabilities The current capability status of the RcsFeature. 331 */ notifyCapabilitiesStatusChanged(@onNull RcsImsCapabilities capabilities)332 public final void notifyCapabilitiesStatusChanged(@NonNull RcsImsCapabilities capabilities) { 333 if (capabilities == null) { 334 throw new IllegalArgumentException("RcsImsCapabilities must be non-null!"); 335 } 336 super.notifyCapabilitiesStatusChanged(capabilities); 337 } 338 339 /** 340 * Provides the RcsFeature with the ability to return the framework capability configuration set 341 * by the framework. When the framework calls 342 * {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)} to 343 * enable or disable capability A, this method should return the correct configuration for 344 * capability A afterwards (until it has changed). 345 * @param capability The capability that we are querying the configuration for. 346 * @param radioTech The radio technology type that we are querying. 347 * @return true if the capability is enabled, false otherwise. 348 */ queryCapabilityConfiguration( @csUceAdapter.RcsImsCapabilityFlag int capability, @ImsRegistrationImplBase.ImsRegistrationTech int radioTech)349 public boolean queryCapabilityConfiguration( 350 @RcsUceAdapter.RcsImsCapabilityFlag int capability, 351 @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) { 352 // Base Implementation - Override to provide functionality 353 return false; 354 } 355 /** 356 * Called from the framework when the {@link RcsImsCapabilities} that have been configured for 357 * this {@link RcsFeature} has changed. 358 * <p> 359 * For each newly enabled capability flag, the corresponding capability should be brought up in 360 * the {@link RcsFeature} and registered on the network. For each newly disabled capability 361 * flag, the corresponding capability should be brought down, and deregistered. Once a new 362 * capability has been initialized and is ready for usage, the status of that capability should 363 * also be set to true using {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This 364 * will notify the framework that the capability is ready. 365 * <p> 366 * If for some reason one or more of these capabilities can not be enabled/disabled, 367 * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError(int, int, int)} should 368 * be called for each capability change that resulted in an error. 369 * @param request The request to change the capability. 370 * @param callback To notify the framework that the result of the capability changes. 371 */ 372 @Override changeEnabledCapabilities(@onNull CapabilityChangeRequest request, @NonNull CapabilityCallbackProxy callback)373 public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request, 374 @NonNull CapabilityCallbackProxy callback) { 375 // Base Implementation - Override to provide functionality 376 } 377 378 /** 379 * Retrieve the implementation of UCE for this {@link RcsFeature}, which can use either 380 * presence or OPTIONS for capability exchange. 381 * 382 * Will only be requested by the framework if capability exchange is configured 383 * as capable during a 384 * {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)} 385 * operation and the RcsFeature sets the status of the capability to true using 386 * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. 387 * 388 * @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange 389 * event to the framework. 390 * @return An instance of {@link RcsCapabilityExchangeImplBase} that implements capability 391 * exchange if it is supported by the device. 392 */ createCapabilityExchangeImpl( @onNull CapabilityExchangeEventListener listener)393 public @NonNull RcsCapabilityExchangeImplBase createCapabilityExchangeImpl( 394 @NonNull CapabilityExchangeEventListener listener) { 395 // Base Implementation, override to implement functionality 396 return new RcsCapabilityExchangeImplBase(); 397 } 398 399 /** 400 * Remove the given CapabilityExchangeImplBase instance. 401 * @param capExchangeImpl The {@link RcsCapabilityExchangeImplBase} instance to be destroyed. 402 */ destroyCapabilityExchangeImpl( @onNull RcsCapabilityExchangeImplBase capExchangeImpl)403 public void destroyCapabilityExchangeImpl( 404 @NonNull RcsCapabilityExchangeImplBase capExchangeImpl) { 405 // Override to implement the process of destroying RcsCapabilityExchangeImplBase instance. 406 } 407 408 /**{@inheritDoc}*/ 409 @Override onFeatureRemoved()410 public void onFeatureRemoved() { 411 412 } 413 414 /**{@inheritDoc}*/ 415 @Override onFeatureReady()416 public void onFeatureReady() { 417 418 } 419 420 /** 421 * @hide 422 */ 423 @Override getBinder()424 public final IImsRcsFeature getBinder() { 425 return mImsRcsBinder; 426 } 427 428 /** 429 * Set the capability exchange listener. 430 * @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange 431 * event to the framework. 432 */ setCapabilityExchangeEventListener( @ullable CapabilityExchangeEventListener listener)433 private void setCapabilityExchangeEventListener( 434 @Nullable CapabilityExchangeEventListener listener) { 435 synchronized (mLock) { 436 mCapExchangeEventListener = listener; 437 if (mCapExchangeEventListener != null) { 438 initRcsCapabilityExchangeImplBase(mCapExchangeEventListener); 439 } else { 440 // Remove the RcsCapabilityExchangeImplBase instance when the capability exchange 441 // instance has been removed in the framework. 442 if (mCapabilityExchangeImpl != null) { 443 destroyCapabilityExchangeImpl(mCapabilityExchangeImpl); 444 } 445 mCapabilityExchangeImpl = null; 446 } 447 } 448 } 449 450 /** 451 * Initialize the RcsCapabilityExchangeImplBase instance if the capability exchange instance 452 * has already been created in the framework. 453 * @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange 454 * event to the framework. 455 */ initRcsCapabilityExchangeImplBase( @onNull CapabilityExchangeEventListener listener)456 private void initRcsCapabilityExchangeImplBase( 457 @NonNull CapabilityExchangeEventListener listener) { 458 synchronized (mLock) { 459 // Remove the original instance 460 if (mCapabilityExchangeImpl != null) { 461 destroyCapabilityExchangeImpl(mCapabilityExchangeImpl); 462 } 463 mCapabilityExchangeImpl = createCapabilityExchangeImpl(listener); 464 } 465 } 466 467 /** 468 * @return the {@link RcsCapabilityExchangeImplBase} associated with the RcsFeature. 469 */ getCapabilityExchangeImplBaseInternal()470 private @NonNull RcsCapabilityExchangeImplBase getCapabilityExchangeImplBaseInternal() { 471 synchronized (mLock) { 472 // The method should not be called if the instance of RcsCapabilityExchangeImplBase has 473 // not been created yet. 474 if (mCapabilityExchangeImpl == null) { 475 throw new IllegalStateException("Session is not available."); 476 } 477 return mCapabilityExchangeImpl; 478 } 479 } 480 } 481