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