Home | History | Annotate | Download | only in feature
      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
    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
    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
    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          */
    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 */
    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          */
    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 
    270         public Capabilities() {
    271         }
    272 
    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          */
    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          */
    289         public void removeCapabilities(int capabilities) {
    290             mCapabilities &= ~capabilities;
    291         }
    292 
    293         /**
    294          * @return true if all of the capabilities specified are capable.
    295          */
    296         public boolean isCapable(int capabilities) {
    297             return (mCapabilities & capabilities) == capabilities;
    298         }
    299 
    300         /**
    301          * @return a deep copy of the Capabilites.
    302          */
    303         public Capabilities copy() {
    304             return new Capabilities(mCapabilities);
    305         }
    306 
    307         /**
    308          * @return a bitmask containing the capability flags directly.
    309          */
    310         public int getMask() {
    311             return mCapabilities;
    312         }
    313 
    314         /**
    315          * @hide
    316          */
    317         @Override
    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
    331         public int hashCode() {
    332             return mCapabilities;
    333         }
    334 
    335         /**
    336          * @hide
    337          */
    338         @Override
    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      */
    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      */
    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      */
    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
    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
    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      */
    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      */
    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      */
    467     public final void addCapabilityCallback(IImsCapabilityCallback c) {
    468         mCapabilityCallbacks.register(c);
    469     }
    470 
    471     /**
    472      * @hide
    473      */
    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
    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
    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      */
    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      */
    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      */
    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      */
    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     protected abstract IInterface getBinder();
    565 }
    566