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