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;
18 
19 import android.Manifest;
20 import android.annotation.CallbackExecutor;
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.RequiresFeature;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SdkConstant;
26 import android.annotation.SystemApi;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.PackageManager;
30 import android.os.Binder;
31 import android.os.IBinder;
32 import android.os.RemoteException;
33 import android.os.ServiceSpecificException;
34 import android.provider.Settings;
35 import android.telephony.AccessNetworkConstants;
36 import android.telephony.BinderCacheManager;
37 import android.telephony.TelephonyFrameworkInitializer;
38 import android.telephony.ims.aidl.IImsCapabilityCallback;
39 import android.telephony.ims.aidl.IImsRcsController;
40 import android.telephony.ims.feature.ImsFeature;
41 import android.telephony.ims.stub.ImsRegistrationImplBase;
42 import android.util.Log;
43 
44 import com.android.internal.telephony.IIntegerConsumer;
45 import com.android.internal.telephony.ITelephony;
46 
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 import java.util.HashMap;
50 import java.util.Map;
51 import java.util.Objects;
52 import java.util.concurrent.Executor;
53 import java.util.function.Consumer;
54 
55 /**
56  * Manager for interfacing with the framework RCS services, including the User Capability Exchange
57  * (UCE) service, as well as managing user settings.
58  *
59  * Use {@link ImsManager#getImsRcsManager(int)} to create an instance of this manager.
60  */
61 @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
62 public class ImsRcsManager {
63     private static final String TAG = "ImsRcsManager";
64 
65     /**
66      * Activity Action: Show the opt-in dialog for enabling or disabling RCS contact discovery
67      * using User Capability Exchange (UCE), which enables a service that periodically shares the
68      * phone numbers of all of the contacts in the user's address book with the carrier to refresh
69      * the RCS capabilities associated with those contacts as the local cache becomes stale.
70      * <p>
71      * An application that depends on RCS contact discovery being enabled must send this intent
72      * using {@link Context#startActivity(Intent)} to ask the user to opt-in for contacts upload for
73      * capability exchange if it is currently disabled. Whether or not RCS contact discovery has
74      * been enabled by the user can be queried using {@link RcsUceAdapter#isUceSettingEnabled()}.
75      * <p>
76      * This intent will always be handled by the system, however the application should only send
77      * this Intent if the carrier supports bulk RCS contact exchange, which will be true if either
78      * key {@link android.telephony.CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL}
79      * or {@link android.telephony.CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL} is set to true.
80      * Otherwise, the RCS contact discovery opt-in dialog will not be shown.
81      * <p>
82      * Input: A mandatory {@link Settings#EXTRA_SUB_ID} extra containing the subscription that the
83      * setting will be be shown for.
84      * <p>
85      * Output: Nothing
86      * @see RcsUceAdapter
87      */
88     @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
89     public static final String ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN =
90             "android.telephony.ims.action.SHOW_CAPABILITY_DISCOVERY_OPT_IN";
91 
92     /**
93      * This carrier supports User Capability Exchange as, defined by the framework using a
94      * presence server. If set, the RcsFeature should support capability exchange. If not set, this
95      * RcsFeature should not publish capabilities or service capability requests.
96      * @hide
97      */
98     @Retention(RetentionPolicy.SOURCE)
99     @IntDef(prefix = "CAPABILITY_TYPE_", flag = true, value = {
100             CAPABILITY_TYPE_NONE,
101             CAPABILITY_TYPE_OPTIONS_UCE,
102             CAPABILITY_TYPE_PRESENCE_UCE
103     })
104     public @interface RcsImsCapabilityFlag {}
105 
106     /**
107      * Undefined capability type for initialization
108      */
109     public static final int CAPABILITY_TYPE_NONE = 0;
110 
111     /**
112      * This carrier supports User Capability Exchange using SIP OPTIONS as defined by the
113      * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS.
114      * If not set, this RcsFeature should not service capability requests.
115      */
116     public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1 << 0;
117 
118     /**
119      * This carrier supports User Capability Exchange using a presence server as defined by the
120      * framework. If set, the RcsFeature should support capability exchange using a presence
121      * server. If not set, this RcsFeature should not publish capabilities or service capability
122      * requests using presence.
123      */
124     public static final int CAPABILITY_TYPE_PRESENCE_UCE = 1 << 1;
125 
126     /**
127      * This is used to check the upper range of RCS capability
128      * @hide
129      */
130     public static final int CAPABILITY_TYPE_MAX = CAPABILITY_TYPE_PRESENCE_UCE + 1;
131 
132     /**
133      * An application can use {@link #addOnAvailabilityChangedListener} to register a
134      * {@link OnAvailabilityChangedListener}, which will notify the user when the RCS feature
135      * availability status updates from the ImsService.
136      * @hide
137      */
138     @SystemApi
139     public interface OnAvailabilityChangedListener {
140         /**
141          * The availability of the feature's capabilities has changed to either available or
142          * unavailable.
143          * <p>
144          * If unavailable, the feature does not support the capability at the current time. This may
145          * be due to network or subscription provisioning changes, such as the IMS registration
146          * being lost, network type changing, or OMA-DM provisioning updates.
147          *
148          * @param capabilities The new availability of the capabilities.
149          */
onAvailabilityChanged(@csImsCapabilityFlag int capabilities)150         void onAvailabilityChanged(@RcsImsCapabilityFlag int capabilities);
151     }
152 
153     /**
154      * Receive the availability status changed from the ImsService and pass the status change to
155      * the associated {@link OnAvailabilityChangedListener}
156      */
157     private static class AvailabilityCallbackAdapter {
158 
159         private static class CapabilityBinder extends IImsCapabilityCallback.Stub {
160             private final OnAvailabilityChangedListener mOnAvailabilityChangedListener;
161             private final Executor mExecutor;
162 
CapabilityBinder(OnAvailabilityChangedListener listener, Executor executor)163             CapabilityBinder(OnAvailabilityChangedListener listener, Executor executor) {
164                 mExecutor = executor;
165                 mOnAvailabilityChangedListener = listener;
166             }
167 
168             @Override
onCapabilitiesStatusChanged(int config)169             public void onCapabilitiesStatusChanged(int config) {
170                 if (mOnAvailabilityChangedListener == null) return;
171 
172                 final long callingIdentity = Binder.clearCallingIdentity();
173                 try {
174                     mExecutor.execute(() ->
175                             mOnAvailabilityChangedListener.onAvailabilityChanged(config));
176                 } finally {
177                     restoreCallingIdentity(callingIdentity);
178                 }
179             }
180 
181             @Override
onQueryCapabilityConfiguration(int capability, int radioTech, boolean isEnabled)182             public void onQueryCapabilityConfiguration(int capability, int radioTech,
183                     boolean isEnabled) {
184                 // This is not used.
185             }
186 
187             @Override
onChangeCapabilityConfigurationError(int capability, int radioTech, @ImsFeature.ImsCapabilityError int reason)188             public void onChangeCapabilityConfigurationError(int capability, int radioTech,
189                     @ImsFeature.ImsCapabilityError int reason) {
190                 // This is not used.
191             }
192         }
193 
194         private final CapabilityBinder mBinder;
195 
AvailabilityCallbackAdapter(@onNull Executor executor, @NonNull OnAvailabilityChangedListener listener)196         AvailabilityCallbackAdapter(@NonNull Executor executor,
197                 @NonNull OnAvailabilityChangedListener listener) {
198             mBinder = new CapabilityBinder(listener, executor);
199         }
200 
201         /**@hide*/
getBinder()202         public final IImsCapabilityCallback getBinder() {
203             return mBinder;
204         }
205     }
206 
207     private final int mSubId;
208     private final Context mContext;
209     private final BinderCacheManager<IImsRcsController> mBinderCache;
210     private final BinderCacheManager<ITelephony> mTelephonyBinderCache;
211     private final Map<OnAvailabilityChangedListener, AvailabilityCallbackAdapter>
212             mAvailabilityChangedCallbacks;
213 
214     /**
215      * Use {@link ImsManager#getImsRcsManager(int)} to create an instance of this class.
216      * @hide
217      */
ImsRcsManager(Context context, int subId, BinderCacheManager<IImsRcsController> binderCache, BinderCacheManager<ITelephony> telephonyBinderCache)218     public ImsRcsManager(Context context, int subId,
219             BinderCacheManager<IImsRcsController> binderCache,
220             BinderCacheManager<ITelephony> telephonyBinderCache) {
221         mSubId = subId;
222         mContext = context;
223         mBinderCache = binderCache;
224         mAvailabilityChangedCallbacks = new HashMap<>();
225         mTelephonyBinderCache = telephonyBinderCache;
226     }
227 
228     /**
229      * @return A {@link RcsUceAdapter} used for User Capability Exchange (UCE) operations for
230      * this subscription.
231      */
232     @NonNull
getUceAdapter()233     public RcsUceAdapter getUceAdapter() {
234         return new RcsUceAdapter(mContext, mSubId);
235     }
236 
237     /**
238      * Registers a {@link RegistrationManager.RegistrationCallback} with the system. When the
239      * callback is registered, it will initiate the callback c to be called with the current
240      * registration state.
241      *
242      * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
243      * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
244      * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
245      *
246      * @param executor The executor the callback events should be run on.
247      * @param c The {@link RegistrationManager.RegistrationCallback} to be added.
248      * @see #unregisterImsRegistrationCallback(RegistrationManager.RegistrationCallback)
249      * @throws ImsException if the subscription associated with this callback is valid, but
250      * the {@link ImsService} associated with the subscription is not available. This can happen if
251      * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
252      * reason.
253      */
254     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
registerImsRegistrationCallback( @onNull @allbackExecutor Executor executor, @NonNull RegistrationManager.RegistrationCallback c)255     public void registerImsRegistrationCallback(
256             @NonNull @CallbackExecutor Executor executor,
257             @NonNull RegistrationManager.RegistrationCallback c)
258             throws ImsException {
259         if (c == null) {
260             throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
261         }
262         if (executor == null) {
263             throw new IllegalArgumentException("Must include a non-null Executor.");
264         }
265 
266         IImsRcsController imsRcsController = getIImsRcsController();
267         if (imsRcsController == null) {
268             Log.w(TAG, "Register registration callback: IImsRcsController is null");
269             throw new ImsException("Cannot find remote IMS service",
270                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
271         }
272 
273         c.setExecutor(executor);
274         try {
275             imsRcsController.registerImsRegistrationCallback(mSubId, c.getBinder());
276         } catch (ServiceSpecificException e) {
277             throw new ImsException(e.toString(), e.errorCode);
278         } catch (RemoteException | IllegalStateException e) {
279             throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
280         }
281     }
282 
283     /**
284      * Removes an existing {@link RegistrationManager.RegistrationCallback}.
285      *
286      * When the subscription associated with this callback is removed (SIM removed, ESIM swap,
287      * etc...), this callback will automatically be removed. If this method is called for an
288      * inactive subscription, it will result in a no-op.
289      *
290      * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
291      * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
292      * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
293      *
294      * @param c The {@link RegistrationManager.RegistrationCallback} to be removed.
295      * @see android.telephony.SubscriptionManager.OnSubscriptionsChangedListener
296      * @see #registerImsRegistrationCallback(Executor, RegistrationCallback)
297      */
298     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
unregisterImsRegistrationCallback( @onNull RegistrationManager.RegistrationCallback c)299     public void unregisterImsRegistrationCallback(
300             @NonNull RegistrationManager.RegistrationCallback c) {
301         if (c == null) {
302             throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
303         }
304 
305         IImsRcsController imsRcsController = getIImsRcsController();
306         if (imsRcsController == null) {
307             Log.w(TAG, "Unregister registration callback: IImsRcsController is null");
308             throw new IllegalStateException("Cannot find remote IMS service");
309         }
310 
311         try {
312             imsRcsController.unregisterImsRegistrationCallback(mSubId, c.getBinder());
313         } catch (RemoteException e) {
314             throw e.rethrowAsRuntimeException();
315         }
316     }
317 
318     /**
319      * Gets the registration state of the IMS service.
320      *
321      * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
322      * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
323      * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
324      *
325      * @param executor The {@link Executor} that will be used to call the IMS registration state
326      * callback.
327      * @param stateCallback A callback called on the supplied {@link Executor} that will contain the
328      * registration state of the IMS service, which will be one of the
329      * following: {@link RegistrationManager#REGISTRATION_STATE_NOT_REGISTERED},
330      * {@link RegistrationManager#REGISTRATION_STATE_REGISTERING}, or
331      * {@link RegistrationManager#REGISTRATION_STATE_REGISTERED}.
332      */
333     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
getRegistrationState(@onNull @allbackExecutor Executor executor, @NonNull @RegistrationManager.ImsRegistrationState Consumer<Integer> stateCallback)334     public void getRegistrationState(@NonNull @CallbackExecutor Executor executor,
335             @NonNull @RegistrationManager.ImsRegistrationState Consumer<Integer> stateCallback) {
336         if (stateCallback == null) {
337             throw new IllegalArgumentException("Must include a non-null stateCallback.");
338         }
339         if (executor == null) {
340             throw new IllegalArgumentException("Must include a non-null Executor.");
341         }
342 
343         IImsRcsController imsRcsController = getIImsRcsController();
344         if (imsRcsController == null) {
345             Log.w(TAG, "Get registration state error: IImsRcsController is null");
346             throw new IllegalStateException("Cannot find remote IMS service");
347         }
348 
349         try {
350             imsRcsController.getImsRcsRegistrationState(mSubId, new IIntegerConsumer.Stub() {
351                 @Override
352                 public void accept(int result) {
353                     final long identity = Binder.clearCallingIdentity();
354                     try {
355                         executor.execute(() -> stateCallback.accept(result));
356                     } finally {
357                         Binder.restoreCallingIdentity(identity);
358                     }
359                 }
360             });
361         } catch (ServiceSpecificException | RemoteException e) {
362             Log.w(TAG, "Get registration state error: " + e);
363             executor.execute(() -> stateCallback.accept(
364                     RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED));
365         }
366     }
367 
368     /**
369      * Gets the Transport Type associated with the current IMS registration.
370      *
371      * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
372      * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges
373      * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
374      *
375      * @param executor The {@link Executor} that will be used to call the transportTypeCallback.
376      * @param transportTypeCallback The transport type associated with the current IMS registration,
377      * which will be one of following:
378      * {@see AccessNetworkConstants#TRANSPORT_TYPE_WWAN},
379      * {@see AccessNetworkConstants#TRANSPORT_TYPE_WLAN}, or
380      * {@see AccessNetworkConstants#TRANSPORT_TYPE_INVALID}.
381      */
382     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
getRegistrationTransportType(@onNull @allbackExecutor Executor executor, @NonNull @AccessNetworkConstants.TransportType Consumer<Integer> transportTypeCallback)383     public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor,
384             @NonNull @AccessNetworkConstants.TransportType
385                     Consumer<Integer> transportTypeCallback) {
386         if (transportTypeCallback == null) {
387             throw new IllegalArgumentException("Must include a non-null transportTypeCallback.");
388         }
389         if (executor == null) {
390             throw new IllegalArgumentException("Must include a non-null Executor.");
391         }
392 
393         IImsRcsController imsRcsController = getIImsRcsController();
394         if (imsRcsController == null) {
395             Log.w(TAG, "Get registration transport type error: IImsRcsController is null");
396             throw new IllegalStateException("Cannot find remote IMS service");
397         }
398 
399         try {
400             imsRcsController.getImsRcsRegistrationTransportType(mSubId,
401                     new IIntegerConsumer.Stub() {
402                         @Override
403                         public void accept(int result) {
404                             final long identity = Binder.clearCallingIdentity();
405                             try {
406                                 executor.execute(() -> transportTypeCallback.accept(result));
407                             } finally {
408                                 Binder.restoreCallingIdentity(identity);
409                             }
410                         }
411                     });
412         } catch (ServiceSpecificException | RemoteException e) {
413             Log.w(TAG, "Get registration transport type error: " + e);
414             executor.execute(() -> transportTypeCallback.accept(
415                     AccessNetworkConstants.TRANSPORT_TYPE_INVALID));
416         }
417     }
418 
419     /**
420      * Add an {@link OnAvailabilityChangedListener} with the system, which will provide RCS
421      * availability updates for the subscription specified.
422      *
423      * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to
424      * subscription changed events and call
425      * {@link #removeOnAvailabilityChangedListener(OnAvailabilityChangedListener)} to clean up
426      * after a subscription is removed.
427      * <p>
428      * When the listener is registered, it will initiate the callback listener to be called with
429      * the current capabilities.
430      *
431      * @param executor The executor the callback events should be run on.
432      * @param listener The RCS {@link OnAvailabilityChangedListener} to be registered.
433      * @see #removeOnAvailabilityChangedListener(OnAvailabilityChangedListener)
434      * @throws ImsException if the subscription associated with this instance of
435      * {@link ImsRcsManager} is valid, but the ImsService associated with the subscription is not
436      * available. This can happen if the ImsService has crashed, for example, or if the subscription
437      * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
438      * @hide
439      */
440     @SystemApi
441     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
addOnAvailabilityChangedListener(@onNull @allbackExecutor Executor executor, @NonNull OnAvailabilityChangedListener listener)442     public void addOnAvailabilityChangedListener(@NonNull @CallbackExecutor Executor executor,
443             @NonNull OnAvailabilityChangedListener listener) throws ImsException {
444         if (listener == null) {
445             throw new IllegalArgumentException("Must include a non-null"
446                     + "OnAvailabilityChangedListener.");
447         }
448         if (executor == null) {
449             throw new IllegalArgumentException("Must include a non-null Executor.");
450         }
451 
452         IImsRcsController imsRcsController = getIImsRcsController();
453         if (imsRcsController == null) {
454             Log.w(TAG, "Add availability changed listener: IImsRcsController is null");
455             throw new ImsException("Cannot find remote IMS service",
456                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
457         }
458 
459         AvailabilityCallbackAdapter adapter =
460                 addAvailabilityChangedListenerToCollection(executor, listener);
461         try {
462             imsRcsController.registerRcsAvailabilityCallback(mSubId, adapter.getBinder());
463         } catch (ServiceSpecificException e) {
464             throw new ImsException(e.toString(), e.errorCode);
465         } catch (RemoteException e) {
466             Log.w(TAG, "Error calling IImsRcsController#registerRcsAvailabilityCallback", e);
467             throw new ImsException("Remote IMS Service is not available",
468                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
469         }
470     }
471 
472      /**
473      * Removes an existing RCS {@link OnAvailabilityChangedListener}.
474      * <p>
475      * When the subscription associated with this callback is removed (SIM removed, ESIM swap,
476      * etc...), this callback will automatically be unregistered. If this method is called for an
477      * inactive subscription, it will result in a no-op.
478      * @param listener The RCS {@link OnAvailabilityChangedListener} to be removed.
479      * @see #addOnAvailabilityChangedListener(Executor, OnAvailabilityChangedListener)
480      * @throws ImsException if the IMS service is not available when calling this method.
481      * See {@link ImsException#getCode()} for more information on the error codes.
482      * @hide
483      */
484     @SystemApi
485     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
removeOnAvailabilityChangedListener( @onNull OnAvailabilityChangedListener listener)486     public void removeOnAvailabilityChangedListener(
487             @NonNull OnAvailabilityChangedListener listener) {
488         if (listener == null) {
489             throw new IllegalArgumentException("Must include a non-null"
490                     + "OnAvailabilityChangedListener.");
491         }
492 
493         IImsRcsController imsRcsController = getIImsRcsController();
494         if (imsRcsController == null) {
495             Log.w(TAG, "Remove availability changed listener: IImsRcsController is null");
496             return;
497         }
498 
499         AvailabilityCallbackAdapter callback =
500                 removeAvailabilityChangedListenerFromCollection(listener);
501         if (callback == null) {
502             return;
503         }
504 
505         try {
506             imsRcsController.unregisterRcsAvailabilityCallback(mSubId, callback.getBinder());
507         } catch (RemoteException e) {
508             Log.w(TAG, "Error calling IImsRcsController#unregisterRcsAvailabilityCallback", e);
509         }
510     }
511 
512     /**
513      * Query for the capability of an IMS RCS service provided by the framework.
514      * <p>
515      * This only reports the status of RCS capabilities provided by the framework, not necessarily
516      * RCS capabilities provided over-the-top by applications.
517      *
518      * @param capability The RCS capability to query.
519      * @param radioTech The radio technology type that we are querying.
520      * @return true if the RCS capability is capable for this subscription, false otherwise. This
521      * does not necessarily mean that we are registered for IMS and the capability is available, but
522      * rather the subscription is capable of this service over IMS.
523      * @see #isAvailable(int, int)
524      * @see android.telephony.CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL
525      * @see android.telephony.CarrierConfigManager.Ims#KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL
526      * @throws ImsException if the IMS service is not available when calling this method.
527      * See {@link ImsException#getCode()} for more information on the error codes.
528      * @hide
529      */
530     @SystemApi
531     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
isCapable(@csImsCapabilityFlag int capability, @ImsRegistrationImplBase.ImsRegistrationTech int radioTech)532     public boolean isCapable(@RcsImsCapabilityFlag int capability,
533             @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) throws ImsException {
534         IImsRcsController imsRcsController = getIImsRcsController();
535         if (imsRcsController == null) {
536             Log.w(TAG, "isCapable: IImsRcsController is null");
537             throw new ImsException("Cannot find remote IMS service",
538                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
539         }
540 
541         try {
542             return imsRcsController.isCapable(mSubId, capability, radioTech);
543         } catch (ServiceSpecificException e) {
544             throw new ImsException(e.getMessage(), e.errorCode);
545         } catch (RemoteException e) {
546             Log.w(TAG, "Error calling IImsRcsController#isCapable", e);
547             throw new ImsException("Remote IMS Service is not available",
548                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
549         }
550     }
551 
552     /**
553      * Query the availability of an IMS RCS capability.
554      * <p>
555      * This only reports the status of RCS capabilities provided by the framework, not necessarily
556      * RCS capabilities provided by over-the-top by applications.
557      *
558      * @param capability the RCS capability to query.
559      * @param radioTech The radio technology type that we are querying.
560      * @return true if the RCS capability is currently available for the associated subscription,
561      * false otherwise. If the capability is available, IMS is registered and the service is
562      * currently available over IMS.
563      * @see #isCapable(int, int)
564      * @throws ImsException if the IMS service is not available when calling this method.
565      * See {@link ImsException#getCode()} for more information on the error codes.
566      * @hide
567      */
568     @SystemApi
569     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
isAvailable(@csImsCapabilityFlag int capability, @ImsRegistrationImplBase.ImsRegistrationTech int radioTech)570     public boolean isAvailable(@RcsImsCapabilityFlag int capability,
571             @ImsRegistrationImplBase.ImsRegistrationTech int radioTech)
572             throws ImsException {
573         IImsRcsController imsRcsController = getIImsRcsController();
574         if (imsRcsController == null) {
575             Log.w(TAG, "isAvailable: IImsRcsController is null");
576             throw new ImsException("Cannot find remote IMS service",
577                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
578         }
579 
580         try {
581             return imsRcsController.isAvailable(mSubId, capability, radioTech);
582         } catch (ServiceSpecificException e) {
583             throw new ImsException(e.getMessage(), e.errorCode);
584         } catch (RemoteException e) {
585             Log.w(TAG, "Error calling IImsRcsController#isAvailable", e);
586             throw new ImsException("Remote IMS Service is not available",
587                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
588         }
589     }
590 
591     /**
592      * Register a new callback, which is used to notify the registrant of changes to
593      * the state of the underlying IMS service that is attached to telephony to
594      * implement IMS functionality. If the manager is created for
595      * the {@link android.telephony.SubscriptionManager#DEFAULT_SUBSCRIPTION_ID},
596      * this throws an {@link ImsException}.
597      *
598      * <p>Requires Permission:
599      * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE READ_PRECISE_PHONE_STATE}
600      * or that the calling app has carrier privileges
601      * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}).
602      *
603      * @param executor the Executor that will be used to call the {@link ImsStateCallback}.
604      * @param callback The callback instance being registered.
605      * @throws ImsException in the case that the callback can not be registered.
606      * See {@link ImsException#getCode} for more information on when this is called.
607      */
608     @RequiresPermission(anyOf = {Manifest.permission.READ_PRECISE_PHONE_STATE,
609             Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
610             Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE})
registerImsStateCallback(@onNull Executor executor, @NonNull ImsStateCallback callback)611     public void registerImsStateCallback(@NonNull Executor executor,
612             @NonNull ImsStateCallback callback) throws ImsException {
613         Objects.requireNonNull(callback, "Must include a non-null ImsStateCallback.");
614         Objects.requireNonNull(executor, "Must include a non-null Executor.");
615 
616         callback.init(executor);
617         ITelephony telephony = mTelephonyBinderCache.listenOnBinder(callback, callback::binderDied);
618         if (telephony == null) {
619             throw new ImsException("Telephony server is down",
620                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
621         }
622 
623         try {
624             telephony.registerImsStateCallback(
625                     mSubId, ImsFeature.FEATURE_RCS,
626                     callback.getCallbackBinder(), mContext.getOpPackageName());
627         } catch (ServiceSpecificException e) {
628             throw new ImsException(e.getMessage(), e.errorCode);
629         } catch (RemoteException | IllegalStateException e) {
630             throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
631         }
632     }
633 
634     /**
635      * Unregisters a previously registered callback.
636      *
637      * @param callback The callback instance to be unregistered.
638      */
unregisterImsStateCallback(@onNull ImsStateCallback callback)639     public void unregisterImsStateCallback(@NonNull ImsStateCallback callback) {
640         Objects.requireNonNull(callback, "Must include a non-null ImsStateCallback.");
641 
642         ITelephony telephony = mTelephonyBinderCache.removeRunnable(callback);
643         try {
644             if (telephony != null) {
645                 telephony.unregisterImsStateCallback(callback.getCallbackBinder());
646             }
647         } catch (RemoteException ignore) {
648             // ignore it
649         }
650     }
651 
652     /**
653      * Add the {@link OnAvailabilityChangedListener} to collection for tracking.
654      * @param executor The executor that will be used when the publish state is changed and the
655      * {@link OnAvailabilityChangedListener} is called.
656      * @param listener The {@link OnAvailabilityChangedListener} to call the publish state changed.
657      * @return The {@link AvailabilityCallbackAdapter} to wrapper the
658      * {@link OnAvailabilityChangedListener}
659      */
addAvailabilityChangedListenerToCollection( @onNull Executor executor, @NonNull OnAvailabilityChangedListener listener)660     private AvailabilityCallbackAdapter addAvailabilityChangedListenerToCollection(
661             @NonNull Executor executor, @NonNull OnAvailabilityChangedListener listener) {
662         AvailabilityCallbackAdapter adapter = new AvailabilityCallbackAdapter(executor, listener);
663         synchronized (mAvailabilityChangedCallbacks) {
664             mAvailabilityChangedCallbacks.put(listener, adapter);
665         }
666         return adapter;
667     }
668 
669     /**
670      * Remove the existing {@link OnAvailabilityChangedListener} from the collection.
671      * @param listener The {@link OnAvailabilityChangedListener} to remove from the collection.
672      * @return The wrapper class {@link AvailabilityCallbackAdapter} associated with the
673      * {@link OnAvailabilityChangedListener}.
674      */
removeAvailabilityChangedListenerFromCollection( @onNull OnAvailabilityChangedListener listener)675     private AvailabilityCallbackAdapter removeAvailabilityChangedListenerFromCollection(
676             @NonNull OnAvailabilityChangedListener listener) {
677         synchronized (mAvailabilityChangedCallbacks) {
678             return mAvailabilityChangedCallbacks.remove(listener);
679         }
680     }
681 
getIImsRcsController()682     private IImsRcsController getIImsRcsController() {
683         IBinder binder = TelephonyFrameworkInitializer
684                 .getTelephonyServiceManager()
685                 .getTelephonyImsServiceRegisterer()
686                 .get();
687         return IImsRcsController.Stub.asInterface(binder);
688     }
689 }
690