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