| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.phone; |
| |
| import static android.telephony.ims.ImsRcsManager.CAPABILITY_TYPE_OPTIONS_UCE; |
| import static android.telephony.ims.ImsRcsManager.CAPABILITY_TYPE_PRESENCE_UCE; |
| import static android.telephony.ims.ProvisioningManager.KEY_EAB_PROVISIONING_STATUS; |
| import static android.telephony.ims.ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE; |
| import static android.telephony.ims.ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS; |
| import static android.telephony.ims.ProvisioningManager.KEY_VT_PROVISIONING_STATUS; |
| import static android.telephony.ims.ProvisioningManager.PROVISIONING_VALUE_DISABLED; |
| import static android.telephony.ims.ProvisioningManager.PROVISIONING_VALUE_ENABLED; |
| import static android.telephony.ims.feature.ImsFeature.FEATURE_MMTEL; |
| import static android.telephony.ims.feature.ImsFeature.FEATURE_RCS; |
| import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER; |
| import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS; |
| import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT; |
| import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO; |
| import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE; |
| import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM; |
| import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN; |
| import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE; |
| import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_MAX; |
| import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE; |
| import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NR; |
| |
| import android.annotation.Nullable; |
| import android.content.Context; |
| import android.os.AsyncResult; |
| import android.os.Binder; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.PersistableBundle; |
| import android.os.RemoteCallbackList; |
| import android.os.RemoteException; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.CarrierConfigManager.Ims; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyRegistryManager; |
| import android.telephony.ims.ProvisioningManager; |
| import android.telephony.ims.aidl.IFeatureProvisioningCallback; |
| import android.telephony.ims.aidl.IImsConfig; |
| import android.telephony.ims.aidl.IImsConfigCallback; |
| import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities; |
| import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities; |
| import android.telephony.ims.stub.ImsConfigImplBase; |
| import android.telephony.ims.stub.ImsRegistrationImplBase; |
| import android.util.SparseArray; |
| |
| import com.android.ims.FeatureConnector; |
| import com.android.ims.ImsConfig; |
| import com.android.ims.ImsException; |
| import com.android.ims.ImsManager; |
| import com.android.ims.RcsFeatureManager; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.PhoneConfigurationManager; |
| import com.android.internal.telephony.util.HandlerExecutor; |
| import com.android.telephony.Rlog; |
| |
| import java.util.Arrays; |
| import java.util.Map; |
| import java.util.concurrent.Executor; |
| |
| /** |
| * Provides APIs for MMTEL and RCS provisioning status. This class handles provisioning status and |
| * notifies the status changing for each capability |
| * {{@link MmTelCapabilities.MmTelCapability} for MMTel services} |
| * {{@link RcsImsCapabilities.RcsImsCapabilityFlag} for RCS services} |
| */ |
| public class ImsProvisioningController { |
| private static final String TAG = "ImsProvisioningController"; |
| private static final int INVALID_VALUE = -1; |
| |
| private static final int EVENT_SUB_CHANGED = 1; |
| private static final int EVENT_PROVISIONING_CAPABILITY_CHANGED = 2; |
| @VisibleForTesting |
| protected static final int EVENT_MULTI_SIM_CONFIGURATION_CHANGE = 3; |
| private static final int EVENT_PROVISIONING_VALUE_CHANGED = 4; |
| |
| // Provisioning Keys that are handled via AOSP cache and not sent to the ImsService |
| private static final int[] LOCAL_IMS_CONFIG_KEYS = { |
| KEY_VOLTE_PROVISIONING_STATUS, |
| KEY_VT_PROVISIONING_STATUS, |
| KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE, |
| KEY_EAB_PROVISIONING_STATUS |
| }; |
| private static final int[] LOCAL_RADIO_TECHS = { |
| REGISTRATION_TECH_LTE, |
| REGISTRATION_TECH_IWLAN, |
| REGISTRATION_TECH_CROSS_SIM, |
| REGISTRATION_TECH_NR |
| }; |
| |
| private static final int MMTEL_CAPABILITY_MIN = MmTelCapabilities.CAPABILITY_TYPE_NONE; |
| private static final int MMTEL_CAPABILITY_MAX = MmTelCapabilities.CAPABILITY_TYPE_MAX; |
| |
| private static final int RCS_CAPABILITY_MIN = RcsImsCapabilities.CAPABILITY_TYPE_NONE; |
| private static final int RCS_CAPABILITY_MAX = RcsImsCapabilities.CAPABILITY_TYPE_MAX; |
| |
| private static final int[] LOCAL_MMTEL_CAPABILITY = { |
| CAPABILITY_TYPE_VOICE, |
| CAPABILITY_TYPE_VIDEO, |
| CAPABILITY_TYPE_UT, |
| CAPABILITY_TYPE_SMS, |
| CAPABILITY_TYPE_CALL_COMPOSER |
| }; |
| |
| /** |
| * map the MmTelCapabilities.MmTelCapability and |
| * CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_VOICE_INT |
| * CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_VIDEO_INT |
| * CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_UT_INT |
| * CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_SMS_INT |
| * CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT |
| */ |
| private static final Map<Integer, String> KEYS_MMTEL_CAPABILITY = Map.of( |
| CAPABILITY_TYPE_VOICE, Ims.KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY, |
| CAPABILITY_TYPE_VIDEO, Ims.KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY, |
| CAPABILITY_TYPE_UT, Ims.KEY_CAPABILITY_TYPE_UT_INT_ARRAY, |
| CAPABILITY_TYPE_SMS, Ims.KEY_CAPABILITY_TYPE_SMS_INT_ARRAY, |
| CAPABILITY_TYPE_CALL_COMPOSER, Ims.KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY |
| ); |
| |
| /** |
| * map the RcsImsCapabilities.RcsImsCapabilityFlag and |
| * CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_OPTIONS_UCE |
| * CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_PRESENCE_UCE |
| */ |
| private static final Map<Integer, String> KEYS_RCS_CAPABILITY = Map.of( |
| CAPABILITY_TYPE_OPTIONS_UCE, Ims.KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY, |
| CAPABILITY_TYPE_PRESENCE_UCE, Ims.KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY |
| ); |
| |
| /** |
| * Create a FeatureConnector for this class to use to connect to an ImsManager. |
| */ |
| @VisibleForTesting |
| public interface MmTelFeatureConnector { |
| /** |
| * Create a FeatureConnector for this class to use to connect to an ImsManager. |
| * @param listener will receive ImsManager instance. |
| * @param executor that the Listener callbacks will be called on. |
| * @return A FeatureConnector |
| */ |
| FeatureConnector<ImsManager> create(Context context, int slotId, |
| String logPrefix, FeatureConnector.Listener<ImsManager> listener, |
| Executor executor); |
| } |
| |
| /** |
| * Create a FeatureConnector for this class to use to connect to an RcsFeatureManager. |
| */ |
| @VisibleForTesting |
| public interface RcsFeatureConnector { |
| /** |
| * Create a FeatureConnector for this class to use to connect to an RcsFeatureManager. |
| * @param listener will receive RcsFeatureManager instance. |
| * @param executor that the Listener callbacks will be called on. |
| * @return A FeatureConnector |
| */ |
| FeatureConnector<RcsFeatureManager> create(Context context, int slotId, |
| FeatureConnector.Listener<RcsFeatureManager> listener, |
| Executor executor, String logPrefix); |
| } |
| |
| private static ImsProvisioningController sInstance; |
| |
| private final PhoneGlobals mApp; |
| private final Handler mHandler; |
| private final CarrierConfigManager mCarrierConfigManager; |
| private final SubscriptionManager mSubscriptionManager; |
| private final TelephonyRegistryManager mTelephonyRegistryManager; |
| private final MmTelFeatureConnector mMmTelFeatureConnector; |
| private final RcsFeatureConnector mRcsFeatureConnector; |
| |
| // maps a slotId to a list of MmTelFeatureListeners |
| private final SparseArray<MmTelFeatureListener> mMmTelFeatureListenersSlotMap = |
| new SparseArray<>(); |
| // maps a slotId to a list of RcsFeatureListeners |
| private final SparseArray<RcsFeatureListener> mRcsFeatureListenersSlotMap = |
| new SparseArray<>(); |
| // map a slotId to a list of ProvisioningCallbackManager |
| private final SparseArray<ProvisioningCallbackManager> mProvisioningCallbackManagersSlotMap = |
| new SparseArray<>(); |
| private final ImsProvisioningLoader mImsProvisioningLoader; |
| |
| private int mNumSlot; |
| |
| /** |
| * This class contains the provisioning status to notify changes. |
| * {{@link MmTelCapabilities.MmTelCapability} for MMTel services} |
| * {{@link android.telephony.ims.ImsRcsManager.RcsImsCapabilityFlag} for RCS services} |
| * {{@link ImsRegistrationImplBase.ImsRegistrationTech} for Registration tech} |
| */ |
| private static final class FeatureProvisioningData { |
| public final int mCapability; |
| public final int mTech; |
| public final boolean mProvisioned; |
| public final boolean mIsMmTel; |
| |
| FeatureProvisioningData(int capability, int tech, boolean provisioned, boolean isMmTel) { |
| mCapability = capability; |
| mTech = tech; |
| mProvisioned = provisioned; |
| mIsMmTel = isMmTel; |
| } |
| } |
| |
| private final class MessageHandler extends Handler { |
| private static final String LOG_PREFIX = "Handler"; |
| MessageHandler(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case EVENT_SUB_CHANGED: |
| onSubscriptionsChanged(); |
| break; |
| case EVENT_PROVISIONING_CAPABILITY_CHANGED: |
| try { |
| mProvisioningCallbackManagersSlotMap.get(msg.arg1) |
| .notifyProvisioningCapabilityChanged( |
| (FeatureProvisioningData) msg.obj); |
| } catch (NullPointerException e) { |
| logw(LOG_PREFIX, msg.arg1, |
| "can not find callback manager message" + msg.what); |
| } |
| break; |
| case EVENT_MULTI_SIM_CONFIGURATION_CHANGE: |
| int activeModemCount = (int) ((AsyncResult) msg.obj).result; |
| onMultiSimConfigChanged(activeModemCount); |
| break; |
| case EVENT_PROVISIONING_VALUE_CHANGED: |
| log("subId " + msg.arg1 + " changed provisioning value item : " + msg.arg2 |
| + " value : " + (int) msg.obj); |
| updateCapabilityTechFromKey(msg.arg1, msg.arg2, (int) msg.obj); |
| break; |
| default: |
| log("unknown message " + msg); |
| break; |
| } |
| } |
| } |
| |
| private final SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener = |
| new SubscriptionManager.OnSubscriptionsChangedListener() { |
| @Override |
| public void onSubscriptionsChanged() { |
| if (!mHandler.hasMessages(EVENT_SUB_CHANGED)) { |
| mHandler.sendEmptyMessage(EVENT_SUB_CHANGED); |
| } |
| } |
| }; |
| |
| private final class ProvisioningCallbackManager { |
| private static final String LOG_PREFIX = "ProvisioningCallbackManager"; |
| private RemoteCallbackList<IFeatureProvisioningCallback> mIFeatureProvisioningCallbackList; |
| private int mSubId; |
| private int mSlotId; |
| |
| ProvisioningCallbackManager(int slotId) { |
| mIFeatureProvisioningCallbackList = |
| new RemoteCallbackList<IFeatureProvisioningCallback>(); |
| mSlotId = slotId; |
| mSubId = getSubId(slotId); |
| log(LOG_PREFIX, mSlotId, "ProvisioningCallbackManager create"); |
| } |
| |
| public void clear() { |
| log(LOG_PREFIX, mSlotId, "ProvisioningCallbackManager clear "); |
| |
| mIFeatureProvisioningCallbackList.kill(); |
| |
| // All registered callbacks are unregistered, and the list is disabled |
| // need to create again |
| mIFeatureProvisioningCallbackList = |
| new RemoteCallbackList<IFeatureProvisioningCallback>(); |
| } |
| |
| public void registerCallback(IFeatureProvisioningCallback localCallback) { |
| if (!mIFeatureProvisioningCallbackList.register(localCallback, (Object) mSubId)) { |
| log(LOG_PREFIX, mSlotId, "registration callback fail"); |
| } |
| } |
| |
| public void unregisterCallback(IFeatureProvisioningCallback localCallback) { |
| mIFeatureProvisioningCallbackList.unregister(localCallback); |
| } |
| |
| public void setSubId(int subId) { |
| if (mSubId == subId) { |
| log(LOG_PREFIX, mSlotId, "subId is not changed "); |
| return; |
| } |
| |
| mSubId = subId; |
| mSlotId = getSlotId(subId); |
| |
| // subId changed means the registered callbacks are not available. |
| clear(); |
| } |
| |
| public boolean hasCallblacks() { |
| int size = mIFeatureProvisioningCallbackList.beginBroadcast(); |
| mIFeatureProvisioningCallbackList.finishBroadcast(); |
| |
| return (size > 0); |
| } |
| |
| public void notifyProvisioningCapabilityChanged(FeatureProvisioningData data) { |
| int size = mIFeatureProvisioningCallbackList.beginBroadcast(); |
| for (int index = 0; index < size; index++) { |
| try { |
| IFeatureProvisioningCallback imsFeatureProvisioningCallback = |
| mIFeatureProvisioningCallbackList.getBroadcastItem(index); |
| |
| // MMTEL |
| if (data.mIsMmTel |
| && Arrays.stream(LOCAL_MMTEL_CAPABILITY) |
| .anyMatch(value -> value == data.mCapability)) { |
| imsFeatureProvisioningCallback.onFeatureProvisioningChanged( |
| data.mCapability, data.mTech, data.mProvisioned); |
| logi(LOG_PREFIX, mSlotId, "notifyProvisioningCapabilityChanged : " |
| + "onFeatureProvisioningChanged" |
| + " capability " + data.mCapability |
| + " tech " + data.mTech |
| + " isProvisioned " + data.mProvisioned); |
| } else if (data.mCapability == CAPABILITY_TYPE_PRESENCE_UCE) { |
| imsFeatureProvisioningCallback.onRcsFeatureProvisioningChanged( |
| data.mCapability, data.mTech, data.mProvisioned); |
| logi(LOG_PREFIX, mSlotId, "notifyProvisioningCapabilityChanged : " |
| + "onRcsFeatureProvisioningChanged" |
| + " capability " + data.mCapability |
| + " tech " + data.mTech |
| + " isProvisioned " + data.mProvisioned); |
| } else { |
| loge(LOG_PREFIX, mSlotId, "notifyProvisioningCapabilityChanged : " |
| + "unknown capability " |
| + data.mCapability); |
| } |
| } catch (RemoteException e) { |
| loge(LOG_PREFIX, mSlotId, |
| "notifyProvisioningChanged: callback #" + index + " failed"); |
| } |
| } |
| mIFeatureProvisioningCallbackList.finishBroadcast(); |
| } |
| } |
| |
| private final class MmTelFeatureListener implements FeatureConnector.Listener<ImsManager> { |
| private static final String LOG_PREFIX = "MmTelFeatureListener"; |
| private FeatureConnector<ImsManager> mConnector; |
| private ImsManager mImsManager; |
| private boolean mReady = false; |
| // stores whether the initial provisioning key value should be notified to ImsService |
| private boolean mRequiredNotify = false; |
| private int mSubId; |
| private int mSlotId; |
| private ConfigCallback mConfigCallback; |
| |
| MmTelFeatureListener(int slotId) { |
| log(LOG_PREFIX, slotId, "created"); |
| |
| mSlotId = slotId; |
| mSubId = getSubId(slotId); |
| mConfigCallback = new ConfigCallback(mSubId); |
| |
| mConnector = mMmTelFeatureConnector.create( |
| mApp, slotId, TAG, this, new HandlerExecutor(mHandler)); |
| mConnector.connect(); |
| } |
| |
| public void setSubId(int subId) { |
| if (mRequiredNotify && mReady) { |
| mRequiredNotify = false; |
| setInitialProvisioningKeys(subId); |
| } |
| if (mSubId == subId) { |
| log(LOG_PREFIX, mSlotId, "subId is not changed"); |
| return; |
| } |
| |
| mSubId = subId; |
| mSlotId = getSlotId(subId); |
| mConfigCallback.setSubId(subId); |
| } |
| |
| public void destroy() { |
| log("destroy"); |
| if (mImsManager != null) { |
| try { |
| ImsConfig imsConfig = getImsConfig(mImsManager); |
| if (imsConfig != null) { |
| imsConfig.removeConfigCallback(mConfigCallback); |
| } |
| } catch (ImsException e) { |
| logw(LOG_PREFIX, mSlotId, "destroy : " + e.getMessage()); |
| } |
| } |
| mConfigCallback = null; |
| mConnector.disconnect(); |
| mConnector = null; |
| mReady = false; |
| mImsManager = null; |
| } |
| |
| public @Nullable ImsManager getImsManager() { |
| return mImsManager; |
| } |
| |
| @Override |
| public void connectionReady(ImsManager manager, int subId) { |
| log(LOG_PREFIX, mSlotId, "connection ready"); |
| mReady = true; |
| mImsManager = manager; |
| |
| if (mImsManager != null) { |
| try { |
| ImsConfig imsConfig = getImsConfig(mImsManager); |
| if (imsConfig != null) { |
| imsConfig.addConfigCallback(mConfigCallback); |
| } |
| } catch (ImsException e) { |
| logw(LOG_PREFIX, mSlotId, "addConfigCallback : " + e.getMessage()); |
| } |
| } |
| |
| onMmTelAvailable(); |
| } |
| |
| @Override |
| public void connectionUnavailable(int reason) { |
| log(LOG_PREFIX, mSlotId, "connection unavailable " + reason); |
| |
| mReady = false; |
| mImsManager = null; |
| |
| // keep the callback for other reason |
| if (reason == FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED) { |
| onMmTelUnavailable(); |
| } |
| } |
| |
| public int setProvisioningValue(int key, int value) { |
| int retVal = ImsConfigImplBase.CONFIG_RESULT_FAILED; |
| |
| if (!mReady) { |
| loge(LOG_PREFIX, mSlotId, "service is Unavailable"); |
| return retVal; |
| } |
| try { |
| // getConfigInterface() will return not null or throw the ImsException |
| // need not null checking |
| ImsConfig imsConfig = getImsConfig(mImsManager); |
| retVal = imsConfig.setConfig(key, value); |
| log(LOG_PREFIX, mSlotId, "setConfig called with key " + key + " value " + value); |
| } catch (ImsException e) { |
| logw(LOG_PREFIX, mSlotId, |
| "setConfig operation failed for key =" + key |
| + ", value =" + value + ". Exception:" + e.getMessage()); |
| } |
| return retVal; |
| } |
| |
| public int getProvisioningValue(int key) { |
| if (!mReady) { |
| loge(LOG_PREFIX, mSlotId, "service is Unavailable"); |
| return INVALID_VALUE; |
| } |
| |
| int retValue = INVALID_VALUE; |
| try { |
| // getConfigInterface() will return not null or throw the ImsException |
| // need not null checking |
| ImsConfig imsConfig = getImsConfig(mImsManager); |
| retValue = imsConfig.getConfigInt(key); |
| } catch (ImsException e) { |
| logw(LOG_PREFIX, mSlotId, |
| "getConfig operation failed for key =" + key |
| + ", value =" + retValue + ". Exception:" + e.getMessage()); |
| } |
| return retValue; |
| } |
| |
| public void onMmTelAvailable() { |
| log(LOG_PREFIX, mSlotId, "onMmTelAvailable"); |
| |
| if (isValidSubId(mSubId)) { |
| mRequiredNotify = false; |
| |
| // notify provisioning key value to ImsService |
| setInitialProvisioningKeys(mSubId); |
| } else { |
| // wait until subId is valid |
| mRequiredNotify = true; |
| } |
| } |
| |
| public void onMmTelUnavailable() { |
| log(LOG_PREFIX, mSlotId, "onMmTelUnavailable"); |
| |
| try { |
| // delete all callbacks reference from ProvisioningManager |
| mProvisioningCallbackManagersSlotMap.get(getSlotId(mSubId)).clear(); |
| } catch (NullPointerException e) { |
| logw(LOG_PREFIX, getSlotId(mSubId), "can not find callback manager to clear"); |
| } |
| } |
| |
| private void setInitialProvisioningKeys(int subId) { |
| boolean required; |
| int value = ImsProvisioningLoader.STATUS_NOT_SET; |
| |
| // updating KEY_VOLTE_PROVISIONING_STATUS |
| try { |
| required = isImsProvisioningRequiredForCapability(subId, CAPABILITY_TYPE_VOICE, |
| REGISTRATION_TECH_LTE); |
| } catch (IllegalArgumentException e) { |
| logw("setInitialProvisioningKeys: KEY_VOLTE_PROVISIONING_STATUS failed for" |
| + " subId=" + subId + ", exception: " + e.getMessage()); |
| return; |
| } |
| |
| log(LOG_PREFIX, mSlotId, |
| "setInitialProvisioningKeys provisioning required(voice, lte) " + required); |
| if (required) { |
| value = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_MMTEL, |
| CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_LTE); |
| if (value != ImsProvisioningLoader.STATUS_NOT_SET) { |
| value = (value == ImsProvisioningLoader.STATUS_PROVISIONED) |
| ? PROVISIONING_VALUE_ENABLED : PROVISIONING_VALUE_DISABLED; |
| setProvisioningValue(KEY_VOLTE_PROVISIONING_STATUS, value); |
| } |
| } |
| |
| // updating KEY_VT_PROVISIONING_STATUS |
| try { |
| required = isImsProvisioningRequiredForCapability(subId, CAPABILITY_TYPE_VIDEO, |
| REGISTRATION_TECH_LTE); |
| } catch (IllegalArgumentException e) { |
| logw("setInitialProvisioningKeys: KEY_VT_PROVISIONING_STATUS failed for" |
| + " subId=" + subId + ", exception: " + e.getMessage()); |
| return; |
| } |
| |
| log(LOG_PREFIX, mSlotId, |
| "setInitialProvisioningKeys provisioning required(video, lte) " + required); |
| if (required) { |
| value = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_MMTEL, |
| CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_LTE); |
| if (value != ImsProvisioningLoader.STATUS_NOT_SET) { |
| value = (value == ImsProvisioningLoader.STATUS_PROVISIONED) |
| ? PROVISIONING_VALUE_ENABLED : PROVISIONING_VALUE_DISABLED; |
| setProvisioningValue(KEY_VT_PROVISIONING_STATUS, value); |
| } |
| } |
| |
| // updating KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE |
| try { |
| required = isImsProvisioningRequiredForCapability(subId, CAPABILITY_TYPE_VOICE, |
| REGISTRATION_TECH_IWLAN); |
| } catch (IllegalArgumentException e) { |
| logw("setInitialProvisioningKeys: KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE failed" |
| + " for subId=" + subId + ", exception: " + e.getMessage()); |
| return; |
| } |
| |
| log(LOG_PREFIX, mSlotId, |
| "setInitialProvisioningKeys provisioning required(voice, iwlan) " + required); |
| if (required) { |
| value = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_MMTEL, |
| CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_IWLAN); |
| if (value != ImsProvisioningLoader.STATUS_NOT_SET) { |
| value = (value == ImsProvisioningLoader.STATUS_PROVISIONED) |
| ? PROVISIONING_VALUE_ENABLED : PROVISIONING_VALUE_DISABLED; |
| setProvisioningValue(KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE, value); |
| } |
| } |
| } |
| } |
| |
| private final class RcsFeatureListener implements FeatureConnector.Listener<RcsFeatureManager> { |
| private static final String LOG_PREFIX = "RcsFeatureListener"; |
| private FeatureConnector<RcsFeatureManager> mConnector; |
| private RcsFeatureManager mRcsFeatureManager; |
| private boolean mReady = false; |
| // stores whether the initial provisioning key value should be notified to ImsService |
| private boolean mRequiredNotify = false; |
| private int mSubId; |
| private int mSlotId; |
| private ConfigCallback mConfigCallback; |
| |
| RcsFeatureListener(int slotId) { |
| log(LOG_PREFIX, slotId, "created"); |
| |
| mSlotId = slotId; |
| mSubId = getSubId(slotId); |
| mConfigCallback = new ConfigCallback(mSubId); |
| |
| mConnector = mRcsFeatureConnector.create( |
| mApp, slotId, this, new HandlerExecutor(mHandler), TAG); |
| mConnector.connect(); |
| } |
| |
| public void setSubId(int subId) { |
| if (mRequiredNotify && mReady) { |
| mRequiredNotify = false; |
| setInitialProvisioningKeys(subId); |
| } |
| if (mSubId == subId) { |
| log(LOG_PREFIX, mSlotId, "subId is not changed"); |
| return; |
| } |
| |
| mSubId = subId; |
| mSlotId = getSlotId(subId); |
| mConfigCallback.setSubId(subId); |
| } |
| |
| public void destroy() { |
| log(LOG_PREFIX, mSlotId, "destroy"); |
| if (mRcsFeatureManager != null) { |
| try { |
| ImsConfig imsConfig = getImsConfig(mRcsFeatureManager.getConfig()); |
| if (imsConfig != null) { |
| imsConfig.removeConfigCallback(mConfigCallback); |
| } |
| } catch (ImsException e) { |
| logw(LOG_PREFIX, mSlotId, "destroy :" + e.getMessage()); |
| } |
| } |
| mConfigCallback = null; |
| mConnector.disconnect(); |
| mConnector = null; |
| mReady = false; |
| mRcsFeatureManager = null; |
| } |
| |
| @Override |
| public void connectionReady(RcsFeatureManager manager, int subId) { |
| log(LOG_PREFIX, mSlotId, "connection ready"); |
| mReady = true; |
| mRcsFeatureManager = manager; |
| |
| if (mRcsFeatureManager != null) { |
| try { |
| ImsConfig imsConfig = getImsConfig(mRcsFeatureManager.getConfig()); |
| if (imsConfig != null) { |
| imsConfig.addConfigCallback(mConfigCallback); |
| } |
| } catch (ImsException e) { |
| logw(LOG_PREFIX, mSlotId, "addConfigCallback :" + e.getMessage()); |
| } |
| } |
| |
| onRcsAvailable(); |
| } |
| |
| @Override |
| public void connectionUnavailable(int reason) { |
| log(LOG_PREFIX, mSlotId, "connection unavailable"); |
| mReady = false; |
| mRcsFeatureManager = null; |
| |
| // keep the callback for other reason |
| if (reason == FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED) { |
| onRcsUnavailable(); |
| } |
| } |
| |
| public int setProvisioningValue(int key, int value) { |
| int retVal = ImsConfigImplBase.CONFIG_RESULT_FAILED; |
| |
| if (!mReady) { |
| loge(LOG_PREFIX, mSlotId, "service is Unavailable"); |
| return retVal; |
| } |
| |
| try { |
| // getConfigInterface() will return not null or throw the ImsException |
| // need not null checking |
| ImsConfig imsConfig = getImsConfig(mRcsFeatureManager.getConfig()); |
| retVal = imsConfig.setConfig(key, value); |
| log(LOG_PREFIX, mSlotId, "setConfig called with key " + key + " value " + value); |
| } catch (ImsException e) { |
| logw(LOG_PREFIX, mSlotId, |
| "setConfig operation failed for key =" + key |
| + ", value =" + value + ". Exception:" + e.getMessage()); |
| } |
| return retVal; |
| } |
| |
| public int getProvisioningValue(int key) { |
| if (!mReady) { |
| loge(LOG_PREFIX, mSlotId, "service is Unavailable"); |
| return INVALID_VALUE; |
| } |
| |
| int retValue = INVALID_VALUE; |
| try { |
| // getConfigInterface() will return not null or throw the ImsException |
| // need not null checking |
| ImsConfig imsConfig = getImsConfig(mRcsFeatureManager.getConfig()); |
| retValue = imsConfig.getConfigInt(key); |
| } catch (ImsException e) { |
| logw(LOG_PREFIX, mSlotId, |
| "getConfig operation failed for key =" + key |
| + ", value =" + retValue + ". Exception:" + e.getMessage()); |
| } |
| return retValue; |
| } |
| |
| public boolean isConnectionReady() { |
| return mReady; |
| } |
| |
| public void onRcsAvailable() { |
| log(LOG_PREFIX, mSlotId, "onRcsAvailable"); |
| |
| if (isValidSubId(mSubId)) { |
| mRequiredNotify = false; |
| |
| // notify provisioning key value to ImsService |
| setInitialProvisioningKeys(mSubId); |
| } else { |
| // wait until subId is valid |
| mRequiredNotify = true; |
| } |
| } |
| |
| public void onRcsUnavailable() { |
| log(LOG_PREFIX, mSlotId, "onRcsUnavailable"); |
| |
| try { |
| // delete all callbacks reference from ProvisioningManager |
| mProvisioningCallbackManagersSlotMap.get(getSlotId(mSubId)).clear(); |
| } catch (NullPointerException e) { |
| logw(LOG_PREFIX, getSlotId(mSubId), "can not find callback manager to clear"); |
| } |
| } |
| |
| private void setInitialProvisioningKeys(int subId) { |
| boolean required; |
| int value = ImsProvisioningLoader.STATUS_NOT_SET; |
| |
| // KEY_EAB_PROVISIONING_STATUS |
| int capability = CAPABILITY_TYPE_PRESENCE_UCE; |
| // Assume that all radio techs have the same provisioning value |
| int tech = REGISTRATION_TECH_LTE; |
| |
| try { |
| required = isRcsProvisioningRequiredForCapability(subId, capability, tech); |
| } catch (IllegalArgumentException e) { |
| logw("setInitialProvisioningKeys: KEY_EAB_PROVISIONING_STATUS failed for" |
| + " subId=" + subId + ", exception: " + e.getMessage()); |
| return; |
| } |
| |
| if (required) { |
| value = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_RCS, |
| capability, tech); |
| if (value != ImsProvisioningLoader.STATUS_NOT_SET) { |
| value = (value == ImsProvisioningLoader.STATUS_PROVISIONED) |
| ? PROVISIONING_VALUE_ENABLED : PROVISIONING_VALUE_DISABLED; |
| setProvisioningValue(KEY_EAB_PROVISIONING_STATUS, value); |
| } |
| } |
| } |
| } |
| |
| // When vendor ImsService changed provisioning data, which should be updated in AOSP. |
| // Catch the event using IImsConfigCallback. |
| private final class ConfigCallback extends IImsConfigCallback.Stub { |
| private int mSubId; |
| |
| ConfigCallback(int subId) { |
| mSubId = subId; |
| } |
| |
| public void setSubId(int subId) { |
| mSubId = subId; |
| } |
| |
| @Override |
| public void onIntConfigChanged(int item, int value) throws RemoteException { |
| if (!Arrays.stream(LOCAL_IMS_CONFIG_KEYS).anyMatch(keyValue -> keyValue == item)) { |
| return; |
| } |
| |
| final long callingIdentity = Binder.clearCallingIdentity(); |
| try { |
| if (mHandler != null) { |
| mHandler.sendMessage(mHandler.obtainMessage( |
| EVENT_PROVISIONING_VALUE_CHANGED, mSubId, item, (Object) value)); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(callingIdentity); |
| } |
| } |
| |
| @Override |
| public void onStringConfigChanged(int item, String value) throws RemoteException { |
| // Ignore this callback. |
| } |
| } |
| |
| /** |
| * Do NOT use this directly, instead use {@link #getInstance()}. |
| */ |
| @VisibleForTesting |
| public ImsProvisioningController(PhoneGlobals app, int numSlot, Looper looper, |
| MmTelFeatureConnector mmTelFeatureConnector, RcsFeatureConnector rcsFeatureConnector, |
| ImsProvisioningLoader imsProvisioningLoader) { |
| log("ImsProvisioningController"); |
| mApp = app; |
| mNumSlot = numSlot; |
| mHandler = new MessageHandler(looper); |
| mMmTelFeatureConnector = mmTelFeatureConnector; |
| mRcsFeatureConnector = rcsFeatureConnector; |
| mCarrierConfigManager = mApp.getSystemService(CarrierConfigManager.class); |
| mSubscriptionManager = mApp.getSystemService(SubscriptionManager.class); |
| mTelephonyRegistryManager = mApp.getSystemService(TelephonyRegistryManager.class); |
| mTelephonyRegistryManager.addOnSubscriptionsChangedListener( |
| mSubChangedListener, mSubChangedListener.getHandlerExecutor()); |
| mImsProvisioningLoader = imsProvisioningLoader; |
| |
| PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler, |
| EVENT_MULTI_SIM_CONFIGURATION_CHANGE, null); |
| |
| initialize(numSlot); |
| } |
| |
| private void initialize(int numSlot) { |
| for (int i = 0; i < numSlot; i++) { |
| MmTelFeatureListener m = new MmTelFeatureListener(i); |
| mMmTelFeatureListenersSlotMap.put(i, m); |
| |
| RcsFeatureListener r = new RcsFeatureListener(i); |
| mRcsFeatureListenersSlotMap.put(i, r); |
| |
| ProvisioningCallbackManager p = new ProvisioningCallbackManager(i); |
| mProvisioningCallbackManagersSlotMap.put(i, p); |
| } |
| } |
| |
| private void onMultiSimConfigChanged(int newNumSlot) { |
| log("onMultiSimConfigChanged: NumSlot " + mNumSlot + " newNumSlot " + newNumSlot); |
| |
| if (mNumSlot < newNumSlot) { |
| for (int i = mNumSlot; i < newNumSlot; i++) { |
| MmTelFeatureListener m = new MmTelFeatureListener(i); |
| mMmTelFeatureListenersSlotMap.put(i, m); |
| |
| RcsFeatureListener r = new RcsFeatureListener(i); |
| mRcsFeatureListenersSlotMap.put(i, r); |
| |
| ProvisioningCallbackManager p = new ProvisioningCallbackManager(i); |
| mProvisioningCallbackManagersSlotMap.put(i, p); |
| } |
| } else if (mNumSlot > newNumSlot) { |
| for (int i = (mNumSlot - 1); i > (newNumSlot - 1); i--) { |
| MmTelFeatureListener m = mMmTelFeatureListenersSlotMap.get(i); |
| mMmTelFeatureListenersSlotMap.remove(i); |
| m.destroy(); |
| |
| RcsFeatureListener r = mRcsFeatureListenersSlotMap.get(i); |
| mRcsFeatureListenersSlotMap.remove(i); |
| r.destroy(); |
| |
| ProvisioningCallbackManager p = mProvisioningCallbackManagersSlotMap.get(i); |
| mProvisioningCallbackManagersSlotMap.remove(i); |
| p.clear(); |
| } |
| } |
| |
| mNumSlot = newNumSlot; |
| } |
| |
| /** |
| * destroy the instance |
| */ |
| @VisibleForTesting |
| public void destroy() { |
| log("destroy"); |
| |
| mHandler.getLooper().quit(); |
| |
| mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener); |
| |
| for (int i = 0; i < mMmTelFeatureListenersSlotMap.size(); i++) { |
| mMmTelFeatureListenersSlotMap.get(i).destroy(); |
| } |
| mMmTelFeatureListenersSlotMap.clear(); |
| |
| for (int i = 0; i < mRcsFeatureListenersSlotMap.size(); i++) { |
| mRcsFeatureListenersSlotMap.get(i).destroy(); |
| } |
| mRcsFeatureListenersSlotMap.clear(); |
| |
| for (int i = 0; i < mProvisioningCallbackManagersSlotMap.size(); i++) { |
| mProvisioningCallbackManagersSlotMap.get(i).clear(); |
| } |
| } |
| |
| /** |
| * create an instance |
| */ |
| @VisibleForTesting |
| public static ImsProvisioningController make(PhoneGlobals app, int numSlot) { |
| synchronized (ImsProvisioningController.class) { |
| if (sInstance == null) { |
| Rlog.i(TAG, "ImsProvisioningController created"); |
| HandlerThread handlerThread = new HandlerThread(TAG); |
| handlerThread.start(); |
| sInstance = new ImsProvisioningController(app, numSlot, handlerThread.getLooper(), |
| ImsManager::getConnector, RcsFeatureManager::getConnector, |
| new ImsProvisioningLoader(app)); |
| } |
| } |
| return sInstance; |
| } |
| |
| /** |
| * Gets a ImsProvisioningController instance |
| */ |
| @VisibleForTesting |
| public static ImsProvisioningController getInstance() { |
| synchronized (ImsProvisioningController.class) { |
| return sInstance; |
| } |
| } |
| |
| /** |
| * Register IFeatureProvisioningCallback from ProvisioningManager |
| */ |
| |
| @VisibleForTesting |
| public void addFeatureProvisioningChangedCallback(int subId, |
| IFeatureProvisioningCallback callback) { |
| if (callback == null) { |
| throw new IllegalArgumentException("provisioning callback can't be null"); |
| } |
| int slotId = getSlotId(subId); |
| if (slotId < 0 || slotId >= mNumSlot) { |
| throw new IllegalArgumentException("subscription id is not available"); |
| } |
| |
| try { |
| mProvisioningCallbackManagersSlotMap.get(slotId).registerCallback(callback); |
| log("Feature Provisioning Callback registered."); |
| } catch (NullPointerException e) { |
| logw("can not access callback manager to add callback"); |
| } |
| } |
| |
| /** |
| * Remove IFeatureProvisioningCallback |
| */ |
| @VisibleForTesting |
| public void removeFeatureProvisioningChangedCallback(int subId, |
| IFeatureProvisioningCallback callback) { |
| if (callback == null) { |
| throw new IllegalArgumentException("provisioning callback can't be null"); |
| } |
| |
| int slotId = getSlotId(subId); |
| if (slotId < 0 || slotId >= mNumSlot) { |
| throw new IllegalArgumentException("subscription id is not available"); |
| } |
| |
| try { |
| mProvisioningCallbackManagersSlotMap.get(slotId).unregisterCallback(callback); |
| log("Feature Provisioning Callback removed."); |
| } catch (NullPointerException e) { |
| logw("can not access callback manager to remove callback"); |
| } |
| } |
| |
| /** |
| * return the boolean whether MmTel capability is required provisioning or not |
| */ |
| @VisibleForTesting |
| public boolean isImsProvisioningRequiredForCapability(int subId, int capability, int tech) { |
| // check subId |
| int slotId = getSlotId(subId); |
| if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX || slotId >= mNumSlot) { |
| loge("Fail to retrieve slotId from subId"); |
| throw new IllegalArgumentException("subscribe id is invalid"); |
| } |
| |
| // check valid capability |
| if (!(MMTEL_CAPABILITY_MIN < capability && capability < MMTEL_CAPABILITY_MAX)) { |
| throw new IllegalArgumentException("MmTel capability '" + capability + "' is invalid"); |
| } |
| |
| // check valid radio tech |
| if (!(REGISTRATION_TECH_NONE < tech && tech < REGISTRATION_TECH_MAX)) { |
| log("Ims not matched radio tech " + tech); |
| throw new IllegalArgumentException("Registration technology '" + tech + "' is invalid"); |
| } |
| |
| // check new carrier config first KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE |
| boolean retVal = isProvisioningRequired(subId, capability, tech, /*isMmTel*/true); |
| |
| // if that returns false, check deprecated carrier config |
| // KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL |
| if (!retVal && (capability == CAPABILITY_TYPE_VOICE |
| || capability == CAPABILITY_TYPE_VIDEO |
| || capability == CAPABILITY_TYPE_UT)) { |
| String key = CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL; |
| if (capability == CAPABILITY_TYPE_UT) { |
| key = CarrierConfigManager.KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL; |
| } |
| |
| PersistableBundle imsCarrierConfigs = mCarrierConfigManager.getConfigForSubId(subId); |
| if (imsCarrierConfigs != null) { |
| retVal = imsCarrierConfigs.getBoolean(key); |
| } else { |
| retVal = CarrierConfigManager.getDefaultConfig().getBoolean(key); |
| } |
| } |
| |
| log("isImsProvisioningRequiredForCapability capability " + capability |
| + " tech " + tech + " return value " + retVal); |
| |
| return retVal; |
| } |
| |
| /** |
| * return the boolean whether RCS capability is required provisioning or not |
| */ |
| @VisibleForTesting |
| public boolean isRcsProvisioningRequiredForCapability(int subId, int capability, int tech) { |
| // check slotId and Phone object |
| int slotId = getSlotId(subId); |
| if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX || slotId >= mNumSlot) { |
| loge("Fail to retrieve slotId from subId"); |
| throw new IllegalArgumentException("subscribe id is invalid"); |
| } |
| |
| // check valid capability |
| if (!(RCS_CAPABILITY_MIN < capability && capability < RCS_CAPABILITY_MAX)) { |
| throw new IllegalArgumentException("Rcs capability '" + capability + "' is invalid"); |
| } |
| |
| // check valid radio tech |
| if (!(REGISTRATION_TECH_NONE < tech && tech < REGISTRATION_TECH_MAX)) { |
| log("Rcs not matched radio tech " + tech); |
| throw new IllegalArgumentException("Registration technology '" + tech + "' is invalid"); |
| } |
| |
| // check new carrier config first KEY_RCS_REQUIRES_PROVISIONING_BUNDLE |
| boolean retVal = isProvisioningRequired(subId, capability, tech, /*isMmTel*/false); |
| |
| // if that returns false, check deprecated carrier config |
| // KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL |
| if (!retVal) { |
| PersistableBundle imsCarrierConfigs = mCarrierConfigManager.getConfigForSubId(subId); |
| if (imsCarrierConfigs != null) { |
| retVal = imsCarrierConfigs.getBoolean( |
| CarrierConfigManager.KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL); |
| } else { |
| retVal = CarrierConfigManager.getDefaultConfig().getBoolean( |
| CarrierConfigManager.KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL); |
| } |
| } |
| |
| log("isRcsProvisioningRequiredForCapability capability " + capability |
| + " tech " + tech + " return value " + retVal); |
| |
| return retVal; |
| } |
| |
| /** |
| * return the provisioning status for MmTel capability in specific radio tech |
| */ |
| @VisibleForTesting |
| public boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech) { |
| boolean mmTelProvisioned = isImsProvisioningRequiredForCapability(subId, capability, tech); |
| if (!mmTelProvisioned) { // provisioning not required |
| log("getImsProvisioningStatusForCapability : not required " |
| + " capability " + capability + " tech " + tech); |
| return true; |
| } |
| |
| // read value from ImsProvisioningLoader |
| int result = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_MMTEL, |
| capability, tech); |
| if (result == ImsProvisioningLoader.STATUS_NOT_SET) { |
| // not set means initial value |
| // read data from vendor ImsService and store that in ImsProvisioningLoader |
| result = getValueFromImsService(subId, capability, tech); |
| mmTelProvisioned = getBoolValue(result); |
| if (result != ProvisioningManager.PROVISIONING_RESULT_UNKNOWN) { |
| setAndNotifyMmTelProvisioningValue(subId, capability, tech, mmTelProvisioned); |
| } |
| } else { |
| mmTelProvisioned = getBoolValue(result); |
| } |
| |
| log("getImsProvisioningStatusForCapability : " |
| + " capability " + capability |
| + " tech " + tech |
| + " result " + mmTelProvisioned); |
| return mmTelProvisioned; |
| } |
| |
| /** |
| * set MmTel provisioning status in specific tech |
| */ |
| @VisibleForTesting |
| public void setImsProvisioningStatusForCapability(int subId, int capability, int tech, |
| boolean isProvisioned) { |
| boolean mmTelProvisioned = isImsProvisioningRequiredForCapability(subId, capability, tech); |
| if (!mmTelProvisioned) { // provisioning not required |
| log("setImsProvisioningStatusForCapability : not required " |
| + " capability " + capability + " tech " + tech); |
| return; |
| } |
| |
| // write value to ImsProvisioningLoader |
| boolean isChanged = setAndNotifyMmTelProvisioningValue(subId, capability, tech, |
| isProvisioned); |
| if (!isChanged) { |
| log("status not changed mmtel capability " + capability + " tech " + tech); |
| return; |
| } |
| |
| int slotId = getSlotId(subId); |
| // find matched key from capability and tech |
| int value = getIntValue(isProvisioned); |
| int key = getKeyFromCapability(capability, tech); |
| if (key != INVALID_VALUE) { |
| log("setImsProvisioningStatusForCapability : matched key " + key); |
| try { |
| // set key and value to vendor ImsService for MmTel |
| mMmTelFeatureListenersSlotMap.get(slotId).setProvisioningValue(key, value); |
| } catch (NullPointerException e) { |
| loge("can not access MmTelFeatureListener with capability " + capability); |
| } |
| } |
| } |
| |
| /** |
| * return the provisioning status for RCS capability in specific radio tech |
| */ |
| @VisibleForTesting |
| public boolean getRcsProvisioningStatusForCapability(int subId, int capability, int tech) { |
| boolean rcsProvisioned = isRcsProvisioningRequiredForCapability(subId, capability, tech); |
| if (!rcsProvisioned) { // provisioning not required |
| log("getRcsProvisioningStatusForCapability : not required" |
| + " capability " + capability + " tech " + tech); |
| return true; |
| } |
| |
| // read data from ImsProvisioningLoader |
| int result = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_RCS, |
| capability, tech); |
| if (result == ImsProvisioningLoader.STATUS_NOT_SET) { |
| // not set means initial value |
| // read data from vendor ImsService and store that in ImsProvisioningLoader |
| result = getRcsValueFromImsService(subId, capability); |
| rcsProvisioned = getBoolValue(result); |
| if (result != ProvisioningManager.PROVISIONING_RESULT_UNKNOWN) { |
| setAndNotifyRcsProvisioningValueForAllTech(subId, capability, rcsProvisioned); |
| } |
| } else { |
| rcsProvisioned = getBoolValue(result); |
| } |
| |
| log("getRcsProvisioningStatusForCapability : " |
| + " capability " + capability |
| + " tech " + tech |
| + " result " + rcsProvisioned); |
| return rcsProvisioned; |
| } |
| |
| /** |
| * set RCS provisioning status in specific tech |
| */ |
| @VisibleForTesting |
| public void setRcsProvisioningStatusForCapability(int subId, int capability, int tech, |
| boolean isProvisioned) { |
| boolean rcsProvisioned = isRcsProvisioningRequiredForCapability(subId, capability, tech); |
| if (!rcsProvisioned) { // provisioning not required |
| log("set rcs provisioning status but not required"); |
| return; |
| } |
| |
| // write status using ImsProvisioningLoader |
| boolean isChanged = setAndNotifyRcsProvisioningValue(subId, capability, tech, |
| isProvisioned); |
| if (!isChanged) { |
| log("status not changed rcs capability " + capability + " tech " + tech); |
| return; |
| } |
| |
| int slotId = getSlotId(subId); |
| int key = ProvisioningManager.KEY_EAB_PROVISIONING_STATUS; |
| int value = getIntValue(isProvisioned); |
| try { |
| // On some older devices, EAB is managed on the MmTel ImsService when the RCS |
| // ImsService is not configured. If there is no RCS ImsService defined, fallback to |
| // MmTel. In the rare case that we hit a race condition where the RCS ImsService has |
| // crashed or has not come up yet, the value will be synchronized via |
| // setInitialProvisioningKeys(). |
| if (mRcsFeatureListenersSlotMap.get(slotId).isConnectionReady()) { |
| mRcsFeatureListenersSlotMap.get(slotId).setProvisioningValue(key, value); |
| } |
| |
| // EAB provisioning status should be updated to both the Rcs and MmTel ImsService, |
| // because the provisioning callback is listening to only MmTel provisioning key |
| // changes. |
| mMmTelFeatureListenersSlotMap.get(slotId).setProvisioningValue(key, value); |
| } catch (NullPointerException e) { |
| loge("can not access RcsFeatureListener with capability " + capability); |
| } |
| } |
| |
| /** |
| * set RCS provisioning status in specific key and value |
| * @param key integer key, defined as one of |
| * {@link ProvisioningManager#KEY_VOLTE_PROVISIONING_STATUS} |
| * {@link ProvisioningManager#KEY_VT_PROVISIONING_STATUS} |
| * {@link ProvisioningManager#KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE} |
| * {@link ProvisioningManager#KEY_EAB_PROVISIONING_STATUS} |
| * @param value in Integer format. |
| * @return the result of setting the configuration value, defined as one of |
| * {@link ImsConfigImplBase#CONFIG_RESULT_FAILED} or |
| * {@link ImsConfigImplBase#CONFIG_RESULT_SUCCESS} or |
| */ |
| @VisibleForTesting |
| public int setProvisioningValue(int subId, int key, int value) { |
| log("setProvisioningValue"); |
| |
| int retVal = ImsConfigImplBase.CONFIG_RESULT_FAILED; |
| // check key value |
| if (!Arrays.stream(LOCAL_IMS_CONFIG_KEYS).anyMatch(keyValue -> keyValue == key)) { |
| log("not matched key " + key); |
| return ImsConfigImplBase.CONFIG_RESULT_UNKNOWN; |
| } |
| |
| // check subId |
| int slotId = getSlotId(subId); |
| if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX || slotId >= mNumSlot) { |
| loge("Fail to retrieve slotId from subId"); |
| return ImsConfigImplBase.CONFIG_RESULT_FAILED; |
| } |
| |
| try { |
| // set key and value to vendor ImsService for MmTel |
| // EAB provisioning status should be updated to both the Rcs and MmTel ImsService, |
| // because the provisioning callback is listening to only MmTel provisioning key |
| // changes. |
| retVal = mMmTelFeatureListenersSlotMap.get(slotId).setProvisioningValue(key, value); |
| |
| // If the Rcs ImsService is not available, the EAB provisioning status will be written |
| // to the MmTel ImsService for backwards compatibility. In the rare case that this is |
| // hit due to RCS ImsService temporarily unavailable, the value will be synchronized |
| // via setInitialProvisioningKeys() when the RCS ImsService comes back up. |
| if (key == KEY_EAB_PROVISIONING_STATUS |
| && mRcsFeatureListenersSlotMap.get(slotId).isConnectionReady()) { |
| // set key and value to vendor ImsService for RCS and use retVal from RCS if |
| // related to EAB when possible. |
| retVal = mRcsFeatureListenersSlotMap.get(slotId).setProvisioningValue(key, value); |
| } |
| } catch (NullPointerException e) { |
| loge("can not access FeatureListener to set provisioning value"); |
| return ImsConfigImplBase.CONFIG_RESULT_FAILED; |
| } |
| |
| // update and notify provisioning status changed capability and tech from key |
| updateCapabilityTechFromKey(subId, key, value); |
| |
| return retVal; |
| } |
| |
| /** |
| * get RCS provisioning status in specific key and value |
| * @param key integer key, defined as one of |
| * {@link ProvisioningManager#KEY_VOLTE_PROVISIONING_STATUS} |
| * {@link ProvisioningManager#KEY_VT_PROVISIONING_STATUS} |
| * {@link ProvisioningManager#KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE} |
| * {@link ProvisioningManager#KEY_EAB_PROVISIONING_STATUS} |
| * @return the result of setting the configuration value, defined as one of |
| * {@link ImsConfigImplBase#CONFIG_RESULT_FAILED} or |
| * {@link ImsConfigImplBase#CONFIG_RESULT_SUCCESS} or |
| * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} |
| */ |
| @VisibleForTesting |
| public int getProvisioningValue(int subId, int key) { |
| // check key value |
| if (!Arrays.stream(LOCAL_IMS_CONFIG_KEYS).anyMatch(keyValue -> keyValue == key)) { |
| log("not matched key " + key); |
| return ImsConfigImplBase.CONFIG_RESULT_UNKNOWN; |
| } |
| |
| // check subId |
| int slotId = getSlotId(subId); |
| if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX || slotId >= mNumSlot) { |
| loge("Fail to retrieve slotId from subId"); |
| return ImsConfigImplBase.CONFIG_RESULT_UNKNOWN; |
| } |
| |
| // check data from ImsProvisioningLoader |
| int capability = getCapabilityFromKey(key); |
| int tech = getTechFromKey(key); |
| int result; |
| if (capability != INVALID_VALUE && tech != INVALID_VALUE) { |
| if (key == KEY_EAB_PROVISIONING_STATUS) { |
| result = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_RCS, |
| capability, tech); |
| } else { |
| result = mImsProvisioningLoader.getProvisioningStatus(subId, FEATURE_MMTEL, |
| capability, tech); |
| } |
| if (result != ImsProvisioningLoader.STATUS_NOT_SET) { |
| log("getProvisioningValue from loader : key " + key + " result " + result); |
| return result; |
| } |
| } |
| |
| // get data from ImsService, update it in ImsProvisioningLoader |
| if (key == KEY_EAB_PROVISIONING_STATUS) { |
| result = getRcsValueFromImsService(subId, capability); |
| if (result == ImsConfigImplBase.CONFIG_RESULT_UNKNOWN) { |
| logw("getProvisioningValue : fail to get data from ImsService capability" |
| + capability); |
| return result; |
| } |
| log("getProvisioningValue from vendor : key " + key + " result " + result); |
| |
| setAndNotifyRcsProvisioningValueForAllTech(subId, capability, getBoolValue(result)); |
| return result; |
| } else { |
| result = getValueFromImsService(subId, capability, tech); |
| if (result == ImsConfigImplBase.CONFIG_RESULT_UNKNOWN) { |
| logw("getProvisioningValue : fail to get data from ImsService capability" |
| + capability); |
| return result; |
| } |
| log("getProvisioningValue from vendor : key " + key + " result " + result); |
| |
| setAndNotifyMmTelProvisioningValue(subId, capability, tech, getBoolValue(result)); |
| return result; |
| } |
| } |
| |
| /** |
| * get the handler |
| */ |
| @VisibleForTesting |
| public Handler getHandler() { |
| return mHandler; |
| } |
| |
| private boolean isProvisioningRequired(int subId, int capability, int tech, boolean isMmTel) { |
| int[] techArray; |
| techArray = getTechsFromCarrierConfig(subId, capability, isMmTel); |
| if (techArray == null) { |
| logw("isProvisioningRequired : getTechsFromCarrierConfig failed"); |
| // not exist in CarrierConfig that means provisioning is not required |
| return false; |
| } |
| |
| // compare with carrier config |
| if (Arrays.stream(techArray).anyMatch(keyValue -> keyValue == tech)) { |
| // existing same tech means provisioning required |
| return true; |
| } |
| |
| log("isProvisioningRequired : not matched capability " + capability + " tech " + tech); |
| return false; |
| } |
| |
| private int[] getTechsFromCarrierConfig(int subId, int capability, boolean isMmTel) { |
| String featureKey; |
| String capabilityKey; |
| if (isMmTel) { |
| featureKey = CarrierConfigManager.Ims.KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE; |
| capabilityKey = KEYS_MMTEL_CAPABILITY.get(capability); |
| } else { |
| featureKey = CarrierConfigManager.Ims.KEY_RCS_REQUIRES_PROVISIONING_BUNDLE; |
| capabilityKey = KEYS_RCS_CAPABILITY.get(capability); |
| } |
| |
| if (capabilityKey != null) { |
| PersistableBundle imsCarrierConfigs = mCarrierConfigManager.getConfigForSubId(subId); |
| if (imsCarrierConfigs == null) { |
| log("getTechsFromCarrierConfig : imsCarrierConfigs null"); |
| return null; |
| } |
| |
| PersistableBundle provisioningBundle = |
| imsCarrierConfigs.getPersistableBundle(featureKey); |
| if (provisioningBundle == null) { |
| log("getTechsFromCarrierConfig : provisioningBundle null"); |
| return null; |
| } |
| |
| return provisioningBundle.getIntArray(capabilityKey); |
| } |
| |
| return null; |
| } |
| |
| private int getValueFromImsService(int subId, int capability, int tech) { |
| int config = ImsConfigImplBase.CONFIG_RESULT_UNKNOWN; |
| |
| // operation is based on capability |
| switch (capability) { |
| case CAPABILITY_TYPE_VOICE: |
| int item = (tech == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN) |
| ? ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE |
| : ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS; |
| // read data from vendor ImsService |
| config = mMmTelFeatureListenersSlotMap.get(getSlotId(subId)) |
| .getProvisioningValue(item); |
| break; |
| case CAPABILITY_TYPE_VIDEO: |
| // read data from vendor ImsService |
| config = mMmTelFeatureListenersSlotMap.get(getSlotId(subId)) |
| .getProvisioningValue(ProvisioningManager.KEY_VT_PROVISIONING_STATUS); |
| break; |
| default: |
| log("Capability " + capability + " has been provisioning"); |
| break; |
| } |
| |
| return config; |
| } |
| |
| private int getRcsValueFromImsService(int subId, int capability) { |
| int config = ImsConfigImplBase.CONFIG_RESULT_UNKNOWN; |
| int slotId = getSlotId(subId); |
| |
| if (capability != CAPABILITY_TYPE_PRESENCE_UCE) { |
| log("Capability " + capability + " has been provisioning"); |
| return config; |
| } |
| try { |
| if (mRcsFeatureListenersSlotMap.get(slotId).isConnectionReady()) { |
| config = mRcsFeatureListenersSlotMap.get(slotId) |
| .getProvisioningValue(ProvisioningManager.KEY_EAB_PROVISIONING_STATUS); |
| } else { |
| log("Rcs ImsService is not available, " |
| + "EAB provisioning status should be read from MmTel ImsService"); |
| config = mMmTelFeatureListenersSlotMap.get(slotId) |
| .getProvisioningValue(ProvisioningManager.KEY_EAB_PROVISIONING_STATUS); |
| } |
| } catch (NullPointerException e) { |
| logw("can not access FeatureListener : " + e.getMessage()); |
| } |
| |
| return config; |
| } |
| |
| private void onSubscriptionsChanged() { |
| for (int index = 0; index < mMmTelFeatureListenersSlotMap.size(); index++) { |
| MmTelFeatureListener m = mMmTelFeatureListenersSlotMap.get(index); |
| m.setSubId(getSubId(index)); |
| } |
| for (int index = 0; index < mRcsFeatureListenersSlotMap.size(); index++) { |
| RcsFeatureListener r = mRcsFeatureListenersSlotMap.get(index); |
| r.setSubId(getSubId(index)); |
| } |
| for (int index = 0; index < mProvisioningCallbackManagersSlotMap.size(); index++) { |
| ProvisioningCallbackManager m = mProvisioningCallbackManagersSlotMap.get(index); |
| m.setSubId(getSubId(index)); |
| } |
| } |
| |
| private void updateCapabilityTechFromKey(int subId, int key, int value) { |
| boolean isProvisioned = getBoolValue(value); |
| int capability = getCapabilityFromKey(key); |
| int tech = getTechFromKey(key); |
| |
| if (capability == INVALID_VALUE || tech == INVALID_VALUE) { |
| logw("updateCapabilityTechFromKey : unknown key " + key); |
| return; |
| } |
| |
| if (key == KEY_VOLTE_PROVISIONING_STATUS |
| || key == KEY_VT_PROVISIONING_STATUS |
| || key == KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE) { |
| setAndNotifyMmTelProvisioningValue(subId, capability, tech, isProvisioned); |
| } |
| if (key == KEY_EAB_PROVISIONING_STATUS) { |
| setAndNotifyRcsProvisioningValueForAllTech(subId, capability, isProvisioned); |
| } |
| } |
| |
| private int getCapabilityFromKey(int key) { |
| int capability; |
| switch (key) { |
| case ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS: |
| // intentional fallthrough |
| case ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE: |
| capability = CAPABILITY_TYPE_VOICE; |
| break; |
| case ProvisioningManager.KEY_VT_PROVISIONING_STATUS: |
| capability = CAPABILITY_TYPE_VIDEO; |
| break; |
| case ProvisioningManager.KEY_EAB_PROVISIONING_STATUS: |
| // default CAPABILITY_TYPE_PRESENCE_UCE used for KEY_EAB_PROVISIONING_STATUS |
| capability = CAPABILITY_TYPE_PRESENCE_UCE; |
| break; |
| default: |
| capability = INVALID_VALUE; |
| break; |
| } |
| return capability; |
| } |
| |
| private int getTechFromKey(int key) { |
| int tech; |
| switch (key) { |
| case ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE: |
| tech = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN; |
| break; |
| case ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS: |
| // intentional fallthrough |
| case ProvisioningManager.KEY_VT_PROVISIONING_STATUS: |
| // intentional fallthrough |
| case ProvisioningManager.KEY_EAB_PROVISIONING_STATUS: |
| tech = ImsRegistrationImplBase.REGISTRATION_TECH_LTE; |
| break; |
| default: |
| tech = INVALID_VALUE; |
| break; |
| } |
| return tech; |
| } |
| |
| private int getKeyFromCapability(int capability, int tech) { |
| int key = INVALID_VALUE; |
| if (capability == CAPABILITY_TYPE_VOICE && tech == REGISTRATION_TECH_IWLAN) { |
| key = ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE; |
| } else if (capability == CAPABILITY_TYPE_VOICE && tech == REGISTRATION_TECH_LTE) { |
| key = ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS; |
| } else if (capability == CAPABILITY_TYPE_VIDEO && tech == REGISTRATION_TECH_LTE) { |
| key = ProvisioningManager.KEY_VT_PROVISIONING_STATUS; |
| } |
| |
| return key; |
| } |
| |
| protected int getSubId(int slotId) { |
| return SubscriptionManager.getSubscriptionId(slotId); |
| } |
| |
| protected int getSlotId(int subId) { |
| return mSubscriptionManager.getPhoneId(subId); |
| } |
| |
| protected ImsConfig getImsConfig(ImsManager imsManager) throws ImsException { |
| return imsManager.getConfigInterface(); |
| } |
| |
| protected ImsConfig getImsConfig(IImsConfig iImsConfig) { |
| return new ImsConfig(iImsConfig); |
| } |
| |
| private int getIntValue(boolean isProvisioned) { |
| return isProvisioned ? ProvisioningManager.PROVISIONING_VALUE_ENABLED |
| : ProvisioningManager.PROVISIONING_VALUE_DISABLED; |
| } |
| |
| private boolean getBoolValue(int value) { |
| return value == ProvisioningManager.PROVISIONING_VALUE_ENABLED ? true : false; |
| } |
| |
| private boolean setAndNotifyMmTelProvisioningValue(int subId, int capability, int tech, |
| boolean isProvisioned) { |
| boolean changed = mImsProvisioningLoader.setProvisioningStatus(subId, FEATURE_MMTEL, |
| capability, tech, isProvisioned); |
| // notify MmTel capability changed |
| if (changed) { |
| mHandler.sendMessage(mHandler.obtainMessage(EVENT_PROVISIONING_CAPABILITY_CHANGED, |
| getSlotId(subId), 0, (Object) new FeatureProvisioningData( |
| capability, tech, isProvisioned, /*isMmTel*/true))); |
| } |
| |
| return changed; |
| } |
| |
| private boolean setAndNotifyRcsProvisioningValue(int subId, int capability, int tech, |
| boolean isProvisioned) { |
| boolean isChanged = mImsProvisioningLoader.setProvisioningStatus(subId, FEATURE_RCS, |
| capability, tech, isProvisioned); |
| |
| if (isChanged) { |
| int slotId = getSlotId(subId); |
| |
| // notify RCS capability changed |
| mHandler.sendMessage(mHandler.obtainMessage(EVENT_PROVISIONING_CAPABILITY_CHANGED, |
| slotId, 0, (Object) new FeatureProvisioningData( |
| capability, tech, isProvisioned, /*isMmtel*/false))); |
| } |
| |
| return isChanged; |
| } |
| |
| private boolean setAndNotifyRcsProvisioningValueForAllTech(int subId, int capability, |
| boolean isProvisioned) { |
| boolean isChanged = false; |
| |
| for (int tech : LOCAL_RADIO_TECHS) { |
| isChanged |= setAndNotifyRcsProvisioningValue(subId, capability, tech, isProvisioned); |
| } |
| |
| return isChanged; |
| } |
| |
| protected boolean isValidSubId(int subId) { |
| int slotId = getSlotId(subId); |
| if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX || slotId >= mNumSlot) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| private void log(String s) { |
| Rlog.d(TAG, s); |
| } |
| |
| private void log(String prefix, int slotId, String s) { |
| Rlog.d(TAG, prefix + "[" + slotId + "] " + s); |
| } |
| |
| private void logi(String prefix, int slotId, String s) { |
| Rlog.i(TAG, prefix + "[" + slotId + "] " + s); |
| } |
| |
| private void logw(String s) { |
| Rlog.w(TAG, s); |
| } |
| |
| private void logw(String prefix, int slotId, String s) { |
| Rlog.w(TAG, prefix + "[" + slotId + "] " + s); |
| } |
| |
| private void loge(String s) { |
| Rlog.e(TAG, s); |
| } |
| |
| private void loge(String prefix, int slotId, String s) { |
| Rlog.e(TAG, prefix + "[" + slotId + "] " + s); |
| } |
| } |