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