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