| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package android.telephony.ims.feature; |
| |
| import android.annotation.CallbackExecutor; |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.SystemApi; |
| import android.net.Uri; |
| import android.os.RemoteException; |
| import android.telephony.ims.RcsUceAdapter; |
| import android.telephony.ims.aidl.ICapabilityExchangeEventListener; |
| import android.telephony.ims.aidl.IImsCapabilityCallback; |
| import android.telephony.ims.aidl.IImsRcsFeature; |
| import android.telephony.ims.aidl.IOptionsResponseCallback; |
| import android.telephony.ims.aidl.IPublishResponseCallback; |
| import android.telephony.ims.aidl.ISubscribeResponseCallback; |
| import android.telephony.ims.aidl.RcsOptionsResponseAidlWrapper; |
| import android.telephony.ims.aidl.RcsPublishResponseAidlWrapper; |
| import android.telephony.ims.aidl.RcsSubscribeResponseAidlWrapper; |
| import android.telephony.ims.stub.ImsRegistrationImplBase; |
| import android.telephony.ims.stub.RcsCapabilityExchangeImplBase; |
| import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback; |
| import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback; |
| import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback; |
| import android.util.Log; |
| |
| import com.android.internal.telephony.util.TelephonyUtils; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.List; |
| import java.util.concurrent.CancellationException; |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.CompletionException; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Executor; |
| import java.util.function.Supplier; |
| |
| /** |
| * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend |
| * this class and provide implementations of the RcsFeature methods that they support. |
| * @hide |
| */ |
| @SystemApi |
| public class RcsFeature extends ImsFeature { |
| |
| private static final String LOG_TAG = "RcsFeature"; |
| |
| private static final class RcsFeatureBinder extends IImsRcsFeature.Stub { |
| // Reference the outer class in order to have better test coverage metrics instead of |
| // creating a inner class referencing the outer class directly. |
| private final RcsFeature mReference; |
| private final Executor mExecutor; |
| |
| RcsFeatureBinder(RcsFeature classRef, @CallbackExecutor Executor executor) { |
| mReference = classRef; |
| mExecutor = executor; |
| } |
| |
| @Override |
| public int queryCapabilityStatus() throws RemoteException { |
| return executeMethodAsyncForResult( |
| () -> mReference.queryCapabilityStatus().mCapabilities, |
| "queryCapabilityStatus"); |
| } |
| |
| @Override |
| public void addCapabilityCallback(IImsCapabilityCallback c) throws RemoteException { |
| executeMethodAsync(() -> mReference.addCapabilityCallback(c), "addCapabilityCallback"); |
| } |
| |
| @Override |
| public void removeCapabilityCallback(IImsCapabilityCallback c) throws RemoteException { |
| executeMethodAsync(() -> mReference.removeCapabilityCallback(c), |
| "removeCapabilityCallback"); |
| } |
| |
| @Override |
| public void changeCapabilitiesConfiguration(CapabilityChangeRequest r, |
| IImsCapabilityCallback c) throws RemoteException { |
| executeMethodAsync(() -> mReference.requestChangeEnabledCapabilities(r, c), |
| "changeCapabilitiesConfiguration"); |
| } |
| |
| @Override |
| public void queryCapabilityConfiguration(int capability, int radioTech, |
| IImsCapabilityCallback c) throws RemoteException { |
| executeMethodAsync(() -> mReference.queryCapabilityConfigurationInternal(capability, |
| radioTech, c), "queryCapabilityConfiguration"); |
| } |
| |
| @Override |
| public int getFeatureState() throws RemoteException { |
| return executeMethodAsyncForResult(mReference::getFeatureState, "getFeatureState"); |
| } |
| |
| // RcsCapabilityExchangeImplBase specific APIs |
| @Override |
| public void setCapabilityExchangeEventListener( |
| @Nullable ICapabilityExchangeEventListener listener) throws RemoteException { |
| executeMethodAsync(() -> mReference.setCapabilityExchangeEventListener(listener), |
| "setCapabilityExchangeEventListener"); |
| } |
| |
| @Override |
| public void publishCapabilities(@NonNull String pidfXml, |
| @NonNull IPublishResponseCallback callback) throws RemoteException { |
| PublishResponseCallback callbackWrapper = new RcsPublishResponseAidlWrapper(callback); |
| executeMethodAsync(() -> mReference.getCapabilityExchangeImplBaseInternal() |
| .publishCapabilities(pidfXml, callbackWrapper), "publishCapabilities"); |
| } |
| |
| @Override |
| public void subscribeForCapabilities(@NonNull List<Uri> uris, |
| @NonNull ISubscribeResponseCallback callback) throws RemoteException { |
| SubscribeResponseCallback wrapper = new RcsSubscribeResponseAidlWrapper(callback); |
| executeMethodAsync(() -> mReference.getCapabilityExchangeImplBaseInternal() |
| .subscribeForCapabilities(uris, wrapper), "subscribeForCapabilities"); |
| } |
| |
| @Override |
| public void sendOptionsCapabilityRequest(@NonNull Uri contactUri, |
| @NonNull List<String> myCapabilities, @NonNull IOptionsResponseCallback callback) |
| throws RemoteException { |
| OptionsResponseCallback callbackWrapper = new RcsOptionsResponseAidlWrapper(callback); |
| executeMethodAsync(() -> mReference.getCapabilityExchangeImplBaseInternal() |
| .sendOptionsCapabilityRequest(contactUri, myCapabilities, callbackWrapper), |
| "sendOptionsCapabilityRequest"); |
| } |
| |
| // Call the methods with a clean calling identity on the executor and wait indefinitely for |
| // the future to return. |
| private void executeMethodAsync(Runnable r, String errorLogName) |
| throws RemoteException { |
| // call with a clean calling identity on the executor and wait indefinitely for the |
| // future to return. |
| try { |
| CompletableFuture.runAsync( |
| () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join(); |
| } catch (CancellationException | CompletionException e) { |
| Log.w(LOG_TAG, "RcsFeatureBinder - " + errorLogName + " exception: " |
| + e.getMessage()); |
| throw new RemoteException(e.getMessage()); |
| } |
| } |
| |
| private <T> T executeMethodAsyncForResult(Supplier<T> r, |
| String errorLogName) throws RemoteException { |
| // call with a clean calling identity on the executor and wait indefinitely for the |
| // future to return. |
| CompletableFuture<T> future = CompletableFuture.supplyAsync( |
| () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor); |
| try { |
| return future.get(); |
| } catch (ExecutionException | InterruptedException e) { |
| Log.w(LOG_TAG, "RcsFeatureBinder - " + errorLogName + " exception: " |
| + e.getMessage()); |
| throw new RemoteException(e.getMessage()); |
| } |
| } |
| } |
| |
| /** |
| * Contains the capabilities defined and supported by a {@link RcsFeature} in the |
| * form of a bitmask. The capabilities that are used in the RcsFeature are |
| * defined as: |
| * {@link RcsUceAdatper.RcsImsCapabilityFlag#CAPABILITY_TYPE_OPTIONS_UCE} |
| * {@link RceUceAdapter.RcsImsCapabilityFlag#CAPABILITY_TYPE_PRESENCE_UCE} |
| * |
| * The enabled capabilities of this RcsFeature will be set by the framework |
| * using {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)}. |
| * After the capabilities have been set, the RcsFeature may then perform the necessary bring up |
| * of the capability and notify the capability status as true using |
| * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the |
| * framework that the capability is available for usage. |
| * @hide |
| */ |
| public static class RcsImsCapabilities extends Capabilities { |
| /** @hide*/ |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef(prefix = "CAPABILITY_TYPE_", flag = true, value = { |
| CAPABILITY_TYPE_NONE, |
| CAPABILITY_TYPE_OPTIONS_UCE, |
| CAPABILITY_TYPE_PRESENCE_UCE |
| }) |
| public @interface RcsImsCapabilityFlag {} |
| |
| /** |
| * Undefined capability type for initialization |
| */ |
| public static final int CAPABILITY_TYPE_NONE = 0; |
| |
| /** |
| * This carrier supports User Capability Exchange using SIP OPTIONS as defined by the |
| * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS. |
| * If not set, this RcsFeature should not service capability requests. |
| */ |
| public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1 << 0; |
| |
| /** |
| * This carrier supports User Capability Exchange using a presence server as defined by the |
| * framework. If set, the RcsFeature should support capability exchange using a presence |
| * server. If not set, this RcsFeature should not publish capabilities or service capability |
| * requests using presence. |
| */ |
| public static final int CAPABILITY_TYPE_PRESENCE_UCE = 1 << 1; |
| |
| public RcsImsCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { |
| super(capabilities); |
| } |
| |
| private RcsImsCapabilities(Capabilities c) { |
| super(c.getMask()); |
| } |
| |
| @Override |
| public void addCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { |
| super.addCapabilities(capabilities); |
| } |
| |
| @Override |
| public void removeCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { |
| super.removeCapabilities(capabilities); |
| } |
| |
| @Override |
| public boolean isCapable(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { |
| return super.isCapable(capabilities); |
| } |
| } |
| |
| private final RcsFeatureBinder mImsRcsBinder; |
| private RcsCapabilityExchangeImplBase mCapabilityExchangeImpl; |
| private ICapabilityExchangeEventListener mCapExchangeEventListener; |
| |
| /** |
| * Create a new RcsFeature. |
| * <p> |
| * Method stubs called from the framework will be called asynchronously. To specify the |
| * {@link Executor} that the methods stubs will be called, use |
| * {@link RcsFeature#RcsFeature(Executor)} instead. |
| */ |
| public RcsFeature() { |
| super(); |
| // Run on the Binder threads that call them. |
| mImsRcsBinder = new RcsFeatureBinder(this, Runnable::run); |
| } |
| |
| /** |
| * Create a new RcsFeature using the Executor specified for methods being called by the |
| * framework. |
| * @param executor The executor for the framework to use when making calls to this service. |
| * @hide |
| */ |
| public RcsFeature(@NonNull Executor executor) { |
| super(); |
| if (executor == null) { |
| throw new IllegalArgumentException("executor can not be null."); |
| } |
| // Run on the Binder thread by default. |
| mImsRcsBinder = new RcsFeatureBinder(this, executor); |
| } |
| |
| /** |
| * Query the current {@link RcsImsCapabilities} status set by the RcsFeature. If a capability is |
| * set, the {@link RcsFeature} has brought up the capability and is ready for framework |
| * requests. To change the status of the capabilities |
| * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)} should be called. |
| * @hide |
| */ |
| @Override |
| public @NonNull final RcsImsCapabilities queryCapabilityStatus() { |
| return new RcsImsCapabilities(super.queryCapabilityStatus()); |
| } |
| |
| /** |
| * Notify the framework that the capabilities status has changed. If a capability is enabled, |
| * this signals to the framework that the capability has been initialized and is ready. |
| * Call {@link #queryCapabilityStatus()} to return the current capability status. |
| * @hide |
| */ |
| public final void notifyCapabilitiesStatusChanged(@NonNull RcsImsCapabilities c) { |
| if (c == null) { |
| throw new IllegalArgumentException("RcsImsCapabilities must be non-null!"); |
| } |
| super.notifyCapabilitiesStatusChanged(c); |
| } |
| |
| /** |
| * Provides the RcsFeature with the ability to return the framework capability configuration set |
| * by the framework. When the framework calls |
| * {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)} to |
| * enable or disable capability A, this method should return the correct configuration for |
| * capability A afterwards (until it has changed). |
| * @hide |
| */ |
| public boolean queryCapabilityConfiguration( |
| @RcsUceAdapter.RcsImsCapabilityFlag int capability, |
| @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) { |
| // Base Implementation - Override to provide functionality |
| return false; |
| } |
| /** |
| * Called from the framework when the {@link RcsImsCapabilities} that have been configured for |
| * this {@link RcsFeature} has changed. |
| * <p> |
| * For each newly enabled capability flag, the corresponding capability should be brought up in |
| * the {@link RcsFeature} and registered on the network. For each newly disabled capability |
| * flag, the corresponding capability should be brought down, and deregistered. Once a new |
| * capability has been initialized and is ready for usage, the status of that capability should |
| * also be set to true using {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This |
| * will notify the framework that the capability is ready. |
| * <p> |
| * If for some reason one or more of these capabilities can not be enabled/disabled, |
| * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError(int, int, int)} should |
| * be called for each capability change that resulted in an error. |
| * @hide |
| */ |
| @Override |
| public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request, |
| @NonNull CapabilityCallbackProxy c) { |
| // Base Implementation - Override to provide functionality |
| } |
| |
| /** |
| * Retrieve the implementation of UCE for this {@link RcsFeature}, which can use either |
| * presence or OPTIONS for capability exchange. |
| * |
| * Will only be requested by the framework if capability exchange is configured |
| * as capable during a |
| * {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)} |
| * operation and the RcsFeature sets the status of the capability to true using |
| * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. |
| * |
| * @return An instance of {@link RcsCapabilityExchangeImplBase} that implements presence |
| * exchange if it is supported by the device. |
| * @hide |
| */ |
| public @NonNull RcsCapabilityExchangeImplBase createCapabilityExchangeImpl() { |
| // Base Implementation, override to implement functionality |
| return new RcsCapabilityExchangeImplBase(); |
| } |
| |
| /**{@inheritDoc}*/ |
| @Override |
| public void onFeatureRemoved() { |
| |
| } |
| |
| /**{@inheritDoc}*/ |
| @Override |
| public void onFeatureReady() { |
| |
| } |
| |
| /** |
| * @hide |
| */ |
| @Override |
| public final IImsRcsFeature getBinder() { |
| return mImsRcsBinder; |
| } |
| |
| private void setCapabilityExchangeEventListener(ICapabilityExchangeEventListener listener) { |
| mCapExchangeEventListener = listener; |
| if (mCapExchangeEventListener != null) { |
| onFeatureReady(); |
| } |
| } |
| |
| private RcsCapabilityExchangeImplBase getCapabilityExchangeImplBaseInternal() { |
| synchronized (mLock) { |
| if (mCapabilityExchangeImpl == null) { |
| mCapabilityExchangeImpl = createCapabilityExchangeImpl(); |
| mCapabilityExchangeImpl.setEventListener(mCapExchangeEventListener); |
| } |
| return mCapabilityExchangeImpl; |
| } |
| } |
| } |