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