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.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.SystemApi;
22 import android.annotation.TestApi;
23 import android.content.Context;
24 import android.os.IInterface;
25 import android.os.RemoteException;
26 import android.telephony.SubscriptionManager;
27 import android.telephony.ims.aidl.IImsCapabilityCallback;
28 import android.telephony.ims.stub.ImsRegistrationImplBase;
29 import android.util.Log;
30 
31 import com.android.ims.internal.IImsFeatureStatusCallback;
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.telephony.util.RemoteCallbackListExt;
34 
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.util.HashMap;
38 import java.util.Map;
39 
40 /**
41  * Base class for all IMS features that are supported by the framework. Use a concrete subclass
42  * of {@link ImsFeature}, such as {@link MmTelFeature} or {@link RcsFeature}.
43  *
44  * @hide
45  */
46 @SystemApi
47 @TestApi
48 public abstract class ImsFeature {
49 
50     private static final String LOG_TAG = "ImsFeature";
51 
52     /**
53      * Invalid feature value
54      * @hide
55      */
56     public static final int FEATURE_INVALID = -1;
57     // ImsFeatures that are defined in the Manifests. Ensure that these values match the previously
58     // defined values in ImsServiceClass for compatibility purposes.
59     /**
60      * This feature supports emergency calling over MMTEL. If defined, the framework will try to
61      * place an emergency call over IMS first. If it is not defined, the framework will only use
62      * CSFB for emergency calling.
63      * @hide
64      */
65     @SystemApi @TestApi
66     public static final int FEATURE_EMERGENCY_MMTEL = 0;
67     /**
68      * This feature supports the MMTEL feature.
69      * @hide
70      */
71     @SystemApi @TestApi
72     public static final int FEATURE_MMTEL = 1;
73     /**
74      * This feature supports the RCS feature.
75      * @hide
76      */
77     @SystemApi @TestApi
78     public static final int FEATURE_RCS = 2;
79     /**
80      * Total number of features defined
81      * @hide
82      */
83     public static final int FEATURE_MAX = 3;
84 
85     /**
86      * Used for logging purposes.
87      * @hide
88      */
89     public static final Map<Integer, String> FEATURE_LOG_MAP = new HashMap<Integer, String>() {{
90             put(FEATURE_EMERGENCY_MMTEL, "EMERGENCY_MMTEL");
91             put(FEATURE_MMTEL, "MMTEL");
92             put(FEATURE_RCS, "RCS");
93         }};
94 
95     /**
96      * Integer values defining IMS features that are supported in ImsFeature.
97      * @hide
98      */
99     @IntDef(flag = true,
100             value = {
101                     FEATURE_EMERGENCY_MMTEL,
102                     FEATURE_MMTEL,
103                     FEATURE_RCS
104             })
105     @Retention(RetentionPolicy.SOURCE)
106     public @interface FeatureType {}
107 
108     /**
109      * Integer values defining the state of the ImsFeature at any time.
110      * @hide
111      */
112     @IntDef(flag = true,
113             value = {
114                     STATE_UNAVAILABLE,
115                     STATE_INITIALIZING,
116                     STATE_READY,
117             })
118     @Retention(RetentionPolicy.SOURCE)
119     public @interface ImsState {}
120 
121     /**
122      * This {@link ImsFeature}'s state is unavailable and should not be communicated with. This will
123      * remove all bindings back to the framework. Any attempt to communicate with the framework
124      * during this time will result in an {@link IllegalStateException}.
125      * @hide
126      */
127     @SystemApi @TestApi
128     public static final int STATE_UNAVAILABLE = 0;
129     /**
130      * This {@link ImsFeature} state is initializing and should not be communicated with. This will
131      * remove all bindings back to the framework. Any attempt to communicate with the framework
132      * during this time will result in an {@link IllegalStateException}.
133      * @hide
134      */
135     @SystemApi @TestApi
136     public static final int STATE_INITIALIZING = 1;
137     /**
138      * This {@link ImsFeature} is ready for communication. Do not attempt to call framework methods
139      * until {@see #onFeatureReady()} is called.
140      * @hide
141      */
142     @SystemApi @TestApi
143     public static final int STATE_READY = 2;
144 
145     /**
146      * Used for logging purposes.
147      * @hide
148      */
149     public static final Map<Integer, String> STATE_LOG_MAP = new HashMap<Integer, String>() {{
150             put(STATE_UNAVAILABLE, "UNAVAILABLE");
151             put(STATE_INITIALIZING, "INITIALIZING");
152             put(STATE_READY, "READY");
153         }};
154 
155     /**
156      * Integer values defining the result codes that should be returned from
157      * {@link #changeEnabledCapabilities} when the framework tries to set a feature's capability.
158      * @hide
159      */
160     @IntDef(flag = true,
161             value = {
162                     CAPABILITY_ERROR_GENERIC,
163                     CAPABILITY_SUCCESS
164             })
165     @Retention(RetentionPolicy.SOURCE)
166     public @interface ImsCapabilityError {}
167 
168     /**
169      * The capability was unable to be changed.
170      * @hide
171      */
172     @SystemApi @TestApi
173     public static final int CAPABILITY_ERROR_GENERIC = -1;
174     /**
175      * The capability was able to be changed.
176      * @hide
177      */
178     @SystemApi @TestApi
179     public static final int CAPABILITY_SUCCESS = 0;
180 
181     /**
182      * Used by the ImsFeature to call back to the CapabilityCallback that the framework has
183      * provided.
184      */
185     protected static class CapabilityCallbackProxy {
186         private final IImsCapabilityCallback mCallback;
187 
188         /** @hide */
CapabilityCallbackProxy(IImsCapabilityCallback c)189         public CapabilityCallbackProxy(IImsCapabilityCallback c) {
190             mCallback = c;
191         }
192 
193         /**
194          * This method notifies the provided framework callback that the request to change the
195          * indicated capability has failed and has not changed.
196          *
197          * @param capability The Capability that will be notified to the framework, defined as
198          * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE},
199          * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO},
200          * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT}, or
201          * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS}.
202          * @param radioTech The radio tech that this capability failed for, defined as
203          * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} or
204          * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}.
205          * @param reason The reason this capability was unable to be changed, defined as
206          * {@link #CAPABILITY_ERROR_GENERIC} or {@link #CAPABILITY_SUCCESS}.
207          */
onChangeCapabilityConfigurationError(int capability, int radioTech, @ImsCapabilityError int reason)208         public void onChangeCapabilityConfigurationError(int capability, int radioTech,
209                 @ImsCapabilityError int reason) {
210             if (mCallback == null) {
211                 return;
212             }
213             try {
214                 mCallback.onChangeCapabilityConfigurationError(capability, radioTech, reason);
215             } catch (RemoteException e) {
216                 Log.e(LOG_TAG, "onChangeCapabilityConfigurationError called on dead binder.");
217             }
218         }
219     }
220 
221     /**
222      * Contains the IMS capabilities defined and supported by an ImsFeature in the form of a
223      * bit-mask.
224      *
225      * @deprecated This class is not used directly, but rather extended in subclasses of
226      * {@link ImsFeature} to provide service specific capabilities.
227      * @see MmTelFeature.MmTelCapabilities
228      * @hide
229      */
230     // Not Actually deprecated, but we need to remove it from the @SystemApi surface.
231     @Deprecated
232     @SystemApi // SystemApi only because it was leaked through type usage in a previous release.
233     @TestApi
234     public static class Capabilities {
235         /** @deprecated Use getters and accessors instead. */
236         // Not actually deprecated, but we need to remove it from the @SystemApi surface eventually.
237         protected int mCapabilities = 0;
238 
239         /**
240          * @hide
241          */
Capabilities()242         public Capabilities() {
243         }
244 
245         /**
246          * @hide
247          */
Capabilities(int capabilities)248         protected Capabilities(int capabilities) {
249             mCapabilities = capabilities;
250         }
251 
252         /**
253          * @param capabilities Capabilities to be added to the configuration in the form of a
254          *     bit mask.
255          * @hide
256          */
addCapabilities(int capabilities)257         public void addCapabilities(int capabilities) {
258             mCapabilities |= capabilities;
259         }
260 
261         /**
262          * @param capabilities Capabilities to be removed to the configuration in the form of a
263          *     bit mask.
264          * @hide
265          */
removeCapabilities(int capabilities)266         public void removeCapabilities(int capabilities) {
267             mCapabilities &= ~capabilities;
268         }
269 
270         /**
271          * @return true if all of the capabilities specified are capable.
272          * @hide
273          */
isCapable(int capabilities)274         public boolean isCapable(int capabilities) {
275             return (mCapabilities & capabilities) == capabilities;
276         }
277 
278         /**
279          * @return a deep copy of the Capabilites.
280          * @hide
281          */
copy()282         public Capabilities copy() {
283             return new Capabilities(mCapabilities);
284         }
285 
286         /**
287          * @return a bitmask containing the capability flags directly.
288          * @hide
289          */
getMask()290         public int getMask() {
291             return mCapabilities;
292         }
293 
294         /**
295          * @hide
296          */
297         @Override
equals(Object o)298         public boolean equals(Object o) {
299             if (this == o) return true;
300             if (!(o instanceof Capabilities)) return false;
301 
302             Capabilities that = (Capabilities) o;
303 
304             return mCapabilities == that.mCapabilities;
305         }
306 
307         /**
308          * @hide
309          */
310         @Override
hashCode()311         public int hashCode() {
312             return mCapabilities;
313         }
314 
315         /**
316          * @hide
317          */
318         @Override
toString()319         public String toString() {
320             return "Capabilities: " + Integer.toBinaryString(mCapabilities);
321         }
322     }
323 
324     /** @hide */
325     protected Context mContext;
326     /** @hide */
327     protected final Object mLock = new Object();
328 
329     private final RemoteCallbackListExt<IImsFeatureStatusCallback> mStatusCallbacks =
330             new RemoteCallbackListExt<>();
331     private @ImsState int mState = STATE_UNAVAILABLE;
332     private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
333     private final RemoteCallbackListExt<IImsCapabilityCallback> mCapabilityCallbacks =
334             new RemoteCallbackListExt<>();
335     private Capabilities mCapabilityStatus = new Capabilities();
336 
337     /**
338      * @hide
339      */
initialize(Context context, int slotId)340     public final void initialize(Context context, int slotId) {
341         mContext = context;
342         mSlotId = slotId;
343     }
344 
345     /**
346      * @return The SIM slot index associated with this ImsFeature.
347      *
348      * @see SubscriptionManager#getSubscriptionIds(int) for more information on getting the
349      * subscription IDs associated with this slot.
350      * @hide
351      */
352     @SystemApi @TestApi
getSlotIndex()353     public final int getSlotIndex() {
354         return mSlotId;
355     }
356 
357     /**
358      * @return The current state of the ImsFeature, set previously by {@link #setFeatureState(int)}
359      * or {@link #STATE_UNAVAILABLE} if it has not been updated  yet.
360      * @hide
361      */
362     @SystemApi @TestApi
getFeatureState()363     public @ImsState int getFeatureState() {
364         synchronized (mLock) {
365             return mState;
366         }
367     }
368 
369     /**
370      * Set the state of the ImsFeature. The state is used as a signal to the framework to start or
371      * stop communication, depending on the state sent.
372      * @param state The ImsFeature's state, defined as {@link #STATE_UNAVAILABLE},
373      * {@link #STATE_INITIALIZING}, or {@link #STATE_READY}.
374      * @hide
375      */
376     @SystemApi @TestApi
setFeatureState(@msState int state)377     public final void setFeatureState(@ImsState int state) {
378         synchronized (mLock) {
379             if (mState != state) {
380                 mState = state;
381                 notifyFeatureState(state);
382             }
383         }
384     }
385 
386     /**
387      * Not final for testing, but shouldn't be extended!
388      * @hide
389      */
390     @VisibleForTesting
addImsFeatureStatusCallback(@onNull IImsFeatureStatusCallback c)391     public void addImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) {
392         try {
393             // If we have just connected, send queued status.
394             c.notifyImsFeatureStatus(getFeatureState());
395             // Add the callback if the callback completes successfully without a RemoteException.
396             mStatusCallbacks.register(c);
397         } catch (RemoteException e) {
398             Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage());
399         }
400     }
401 
402     /**
403      * Not final for testing, but shouldn't be extended!
404      * @hide
405      */
406     @VisibleForTesting
removeImsFeatureStatusCallback(@onNull IImsFeatureStatusCallback c)407     public void removeImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) {
408         mStatusCallbacks.unregister(c);
409     }
410 
411     /**
412      * Internal method called by ImsFeature when setFeatureState has changed.
413      */
notifyFeatureState(@msState int state)414     private void notifyFeatureState(@ImsState int state) {
415         mStatusCallbacks.broadcastAction((c) -> {
416             try {
417                 c.notifyImsFeatureStatus(state);
418             } catch (RemoteException e) {
419                 Log.w(LOG_TAG, e + " notifyFeatureState() - Skipping "
420                         + "callback.");
421             }
422         });
423     }
424 
425     /**
426      * @hide
427      */
addCapabilityCallback(IImsCapabilityCallback c)428     public final void addCapabilityCallback(IImsCapabilityCallback c) {
429         mCapabilityCallbacks.register(c);
430         try {
431             // Notify the Capability callback that was just registered of the current capabilities.
432             c.onCapabilitiesStatusChanged(queryCapabilityStatus().mCapabilities);
433         } catch (RemoteException e) {
434             Log.w(LOG_TAG, "addCapabilityCallback: error accessing callback: " + e.getMessage());
435         }
436     }
437 
438     /**
439      * @hide
440      */
removeCapabilityCallback(IImsCapabilityCallback c)441     final void removeCapabilityCallback(IImsCapabilityCallback c) {
442         mCapabilityCallbacks.unregister(c);
443     }
444 
445     /**@hide*/
queryCapabilityConfigurationInternal(int capability, int radioTech, IImsCapabilityCallback c)446     final void queryCapabilityConfigurationInternal(int capability, int radioTech,
447             IImsCapabilityCallback c) {
448         boolean enabled = queryCapabilityConfiguration(capability, radioTech);
449         try {
450             if (c != null) {
451                 c.onQueryCapabilityConfiguration(capability, radioTech, enabled);
452             }
453         } catch (RemoteException e) {
454             Log.e(LOG_TAG, "queryCapabilityConfigurationInternal called on dead binder!");
455         }
456     }
457 
458     /**
459      * @return the cached capabilities status for this feature.
460      * @hide
461      */
462     @VisibleForTesting
queryCapabilityStatus()463     public Capabilities queryCapabilityStatus() {
464         synchronized (mLock) {
465             return mCapabilityStatus.copy();
466         }
467     }
468 
469     /**
470      * Called internally to request the change of enabled capabilities.
471      * @hide
472      */
473     @VisibleForTesting
requestChangeEnabledCapabilities(CapabilityChangeRequest request, IImsCapabilityCallback c)474     public final void requestChangeEnabledCapabilities(CapabilityChangeRequest request,
475             IImsCapabilityCallback c) {
476         if (request == null) {
477             throw new IllegalArgumentException(
478                     "ImsFeature#requestChangeEnabledCapabilities called with invalid params.");
479         }
480         changeEnabledCapabilities(request, new CapabilityCallbackProxy(c));
481     }
482 
483     /**
484      * Called by the ImsFeature when the capabilities status has changed.
485      *
486      * @param caps the new {@link Capabilities} status of the {@link ImsFeature}.
487      *
488      * @hide
489      */
notifyCapabilitiesStatusChanged(Capabilities caps)490     protected final void notifyCapabilitiesStatusChanged(Capabilities caps) {
491         synchronized (mLock) {
492             mCapabilityStatus = caps.copy();
493         }
494         mCapabilityCallbacks.broadcastAction((callback) -> {
495             try {
496                 callback.onCapabilitiesStatusChanged(caps.mCapabilities);
497             } catch (RemoteException e) {
498                 Log.w(LOG_TAG, e + " notifyCapabilitiesStatusChanged() - Skipping "
499                         + "callback.");
500             }
501         });
502     }
503 
504     /**
505      * Provides the ImsFeature with the ability to return the framework Capability Configuration
506      * for a provided Capability. If the framework calls {@link #changeEnabledCapabilities} and
507      * includes a capability A to enable or disable, this method should return the correct enabled
508      * status for capability A.
509      * @param capability The capability that we are querying the configuration for.
510      * @return true if the capability is enabled, false otherwise.
511      * @hide
512      */
queryCapabilityConfiguration(int capability, int radioTech)513     public abstract boolean queryCapabilityConfiguration(int capability, int radioTech);
514 
515     /**
516      * Features should override this method to receive Capability preference change requests from
517      * the framework using the provided {@link CapabilityChangeRequest}. If any of the capabilities
518      * in the {@link CapabilityChangeRequest} are not able to be completed due to an error,
519      * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError} should be called for
520      * each failed capability.
521      *
522      * @param request A {@link CapabilityChangeRequest} containing requested capabilities to
523      *     enable/disable.
524      * @param c A {@link CapabilityCallbackProxy}, which will be used to call back to the framework
525      * setting a subset of these capabilities fail, using
526      * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError}.
527      */
changeEnabledCapabilities(CapabilityChangeRequest request, CapabilityCallbackProxy c)528     public abstract void changeEnabledCapabilities(CapabilityChangeRequest request,
529             CapabilityCallbackProxy c);
530 
531     /**
532      * Called when the framework is removing this feature and it needs to be cleaned up.
533      */
onFeatureRemoved()534     public abstract void onFeatureRemoved();
535 
536     /**
537      * Called after this ImsFeature has been initialized and has been set to the
538      * {@link ImsState#STATE_READY} state.
539      * <p>
540      * Any attempt by this feature to access the framework before this method is called will return
541      * with an {@link IllegalStateException}.
542      * The IMS provider should use this method to trigger registration for this feature on the IMS
543      * network, if needed.
544      */
onFeatureReady()545     public abstract void onFeatureReady();
546 
547     /**
548      * @return Binder instance that the framework will use to communicate with this feature.
549      * @hide
550      */
getBinder()551     protected abstract IInterface getBinder();
552 }
553