diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml index 4f16e12f394..fc99ae279a0 100644 --- a/res/xml/mobile_network_settings.xml +++ b/res/xml/mobile_network_settings.xml @@ -221,10 +221,11 @@ + diff --git a/src/com/android/settings/network/ims/VtQueryImsState.java b/src/com/android/settings/network/ims/VtQueryImsState.java index 5c48ff00380..7351b83b18f 100644 --- a/src/com/android/settings/network/ims/VtQueryImsState.java +++ b/src/com/android/settings/network/ims/VtQueryImsState.java @@ -18,24 +18,17 @@ package com.android.settings.network.ims; import android.content.Context; import android.telecom.TelecomManager; -import android.telephony.AccessNetworkConstants; import android.telephony.SubscriptionManager; -import android.telephony.ims.ImsException; -import android.telephony.ims.feature.MmTelFeature; -import android.telephony.ims.stub.ImsRegistrationImplBase; -import android.util.Log; import androidx.annotation.VisibleForTesting; /** * Controller class for querying VT status */ -public class VtQueryImsState extends ImsQueryController { +public class VtQueryImsState { - private static final String LOG_TAG = "VtQueryImsState"; - - private Context mContext; - private int mSubId; + private final Context mContext; + private final int mSubId; /** * Constructor @@ -44,9 +37,6 @@ public class VtQueryImsState extends ImsQueryController { * @param subId subscription's id */ public VtQueryImsState(Context context, int subId) { - super(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO, - ImsRegistrationImplBase.REGISTRATION_TECH_LTE, - AccessNetworkConstants.TRANSPORT_TYPE_WWAN); mContext = context; mSubId = subId; } @@ -62,24 +52,6 @@ public class VtQueryImsState extends ImsQueryController { return (new ImsQueryVtUserSetting(subId)).query(); } - /** - * Check whether Video Call can be perform or not on this subscription - * - * @return true when Video Call can be performed, otherwise false - */ - public boolean isReadyToVideoCall() { - if (!isProvisionedOnDevice(mSubId)) { - return false; - } - - try { - return isEnabledByPlatform(mSubId) && isServiceStateReady(mSubId); - } catch (InterruptedException | IllegalArgumentException | ImsException exception) { - Log.w(LOG_TAG, "fail to get Vt ready. subId=" + mSubId, exception); - } - return false; - } - /** * Get allowance status for user to alter configuration * @@ -89,8 +61,7 @@ public class VtQueryImsState extends ImsQueryController { if (!SubscriptionManager.isValidSubscriptionId(mSubId)) { return false; } - return ((!isTtyEnabled(mContext)) - || (isTtyOnVolteEnabled(mSubId))); + return !isTtyEnabled(mContext) || new ImsQueryTtyOnVolteStat(mSubId).query(); } @VisibleForTesting diff --git a/src/com/android/settings/network/telephony/CarrierConfigRepository.kt b/src/com/android/settings/network/telephony/CarrierConfigRepository.kt index 3f5c06efb93..88525402e34 100644 --- a/src/com/android/settings/network/telephony/CarrierConfigRepository.kt +++ b/src/com/android/settings/network/telephony/CarrierConfigRepository.kt @@ -50,7 +50,7 @@ class CarrierConfigRepository(private val context: Context) { private val keysToRetrieve = mutableMapOf() override fun getBoolean(key: String): Boolean { - check(key.endsWith("_bool")) { "Boolean key should ends with _bool" } + checkBooleanKey(key) val value = cache[key] return if (value == null) { keysToRetrieve += key to KeyType.BOOLEAN @@ -186,9 +186,18 @@ class CarrierConfigRepository(private val context: Context) { ListenerRegistered.getAndSet(false) } + private val BooleanKeysWhichNotFollowingsNamingConventions = + listOf(CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS) + + private fun checkBooleanKey(key: String) { + check(key.endsWith("_bool") || key in BooleanKeysWhichNotFollowingsNamingConventions) { + "Boolean key should ends with _bool" + } + } + @VisibleForTesting fun setBooleanForTest(subId: Int, key: String, value: Boolean) { - check(key.endsWith("_bool")) { "Boolean key should ends with _bool" } + checkBooleanKey(key) getPerSubCache(subId)[key] = BooleanConfigValue(value) } diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndex.kt b/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndex.kt index c63e7d23bf1..4f31e0f71c7 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndex.kt +++ b/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndex.kt @@ -26,6 +26,7 @@ import com.android.settings.network.telephony.DataUsagePreferenceController.Comp import com.android.settings.network.telephony.MmsMessagePreferenceController.Companion.MmsMessageSearchItem import com.android.settings.network.telephony.NrAdvancedCallingPreferenceController.Companion.NrAdvancedCallingSearchItem import com.android.settings.network.telephony.RoamingPreferenceController.Companion.RoamingSearchItem +import com.android.settings.network.telephony.VideoCallingPreferenceController.Companion.VideoCallingSearchItem import com.android.settings.network.telephony.WifiCallingPreferenceController.Companion.WifiCallingSearchItem import com.android.settings.spa.SpaSearchLanding.BundleValue import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingFragment @@ -122,6 +123,7 @@ class MobileNetworkSettingsSearchIndex( NrAdvancedCallingSearchItem(context), PreferredNetworkModeSearchItem(context), RoamingSearchItem(context), + VideoCallingSearchItem(context), WifiCallingSearchItem(context), ) } diff --git a/src/com/android/settings/network/telephony/VideoCallingPreferenceController.java b/src/com/android/settings/network/telephony/VideoCallingPreferenceController.java deleted file mode 100644 index f803efdc1b0..00000000000 --- a/src/com/android/settings/network/telephony/VideoCallingPreferenceController.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2018 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.settings.network.telephony; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.PersistableBundle; -import android.telephony.CarrierConfigManager; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyCallback; -import android.telephony.TelephonyManager; -import android.telephony.ims.ImsMmTelManager; -import android.util.Log; - -import androidx.annotation.VisibleForTesting; -import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; -import androidx.preference.TwoStatePreference; - -import com.android.internal.telephony.flags.Flags; -import com.android.settings.network.CarrierConfigCache; -import com.android.settings.network.MobileDataEnabledListener; -import com.android.settings.network.ims.VolteQueryImsState; -import com.android.settings.network.ims.VtQueryImsState; -import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnStart; -import com.android.settingslib.core.lifecycle.events.OnStop; - -/** - * Preference controller for "Video Calling" - */ -public class VideoCallingPreferenceController extends TelephonyTogglePreferenceController implements - LifecycleObserver, OnStart, OnStop, - MobileDataEnabledListener.Client, - Enhanced4gBasePreferenceController.On4gLteUpdateListener { - - private static final String TAG = "VideoCallingPreference"; - - private Preference mPreference; - private PhoneTelephonyCallback mTelephonyCallback; - @VisibleForTesting - Integer mCallState; - private MobileDataEnabledListener mDataContentObserver; - private CallingPreferenceCategoryController mCallingPreferenceCategoryController; - - public VideoCallingPreferenceController(Context context, String key) { - super(context, key); - mDataContentObserver = new MobileDataEnabledListener(context, this); - mTelephonyCallback = new PhoneTelephonyCallback(); - } - - @Override - public int getAvailabilityStatus(int subId) { - return SubscriptionManager.isValidSubscriptionId(subId) - && isVideoCallEnabled(subId) - ? AVAILABLE - : CONDITIONALLY_UNAVAILABLE; - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mPreference = screen.findPreference(getPreferenceKey()); - } - - @Override - public void onStart() { - mTelephonyCallback.register(mContext, mSubId); - mDataContentObserver.start(mSubId); - } - - @Override - public void onStop() { - mTelephonyCallback.unregister(); - mDataContentObserver.stop(); - } - - @Override - public void updateState(Preference preference) { - super.updateState(preference); - if ((mCallState == null) || (preference == null)) { - Log.d(TAG, "Skip update under mCallState=" + mCallState); - return; - } - final TwoStatePreference switchPreference = (TwoStatePreference) preference; - final boolean videoCallEnabled = isVideoCallEnabled(mSubId); - switchPreference.setVisible(videoCallEnabled); - mCallingPreferenceCategoryController - .updateChildVisible(getPreferenceKey(), videoCallEnabled); - if (videoCallEnabled) { - final boolean videoCallEditable = queryVoLteState(mSubId).isEnabledByUser() - && queryImsState(mSubId).isAllowUserControl(); - preference.setEnabled(videoCallEditable - && mCallState == TelephonyManager.CALL_STATE_IDLE); - switchPreference.setChecked(videoCallEditable && isChecked()); - } - } - - @Override - public boolean setChecked(boolean isChecked) { - if (!SubscriptionManager.isValidSubscriptionId(mSubId)) { - return false; - } - final ImsMmTelManager imsMmTelManager = ImsMmTelManager.createForSubscriptionId(mSubId); - if (imsMmTelManager == null) { - return false; - } - try { - imsMmTelManager.setVtSettingEnabled(isChecked); - return true; - } catch (IllegalArgumentException exception) { - Log.w(TAG, "Unable to set VT status " + isChecked + ". subId=" + mSubId, - exception); - } - return false; - } - - @Override - public boolean isChecked() { - return queryImsState(mSubId).isEnabledByUser(); - } - - @VisibleForTesting - protected boolean isImsSupported() { - return mContext.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_TELEPHONY_IMS); - } - - /** - * Init instance of VideoCallingPreferenceController. - */ - public VideoCallingPreferenceController init( - int subId, CallingPreferenceCategoryController callingPreferenceCategoryController) { - mSubId = subId; - mCallingPreferenceCategoryController = callingPreferenceCategoryController; - - return this; - } - - @VisibleForTesting - boolean isVideoCallEnabled(int subId) { - if (!SubscriptionManager.isValidSubscriptionId(subId)) { - return false; - } - - final PersistableBundle carrierConfig = - CarrierConfigCache.getInstance(mContext).getConfigForSubId(subId); - if (carrierConfig == null) { - return false; - } - - if (!carrierConfig.getBoolean( - CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS) - && (!mContext.getSystemService(TelephonyManager.class) - .createForSubscriptionId(subId).isDataEnabled())) { - return false; - } - - return isImsSupported() && queryImsState(subId).isReadyToVideoCall(); - } - - @Override - public void on4gLteUpdated() { - updateState(mPreference); - } - - private class PhoneTelephonyCallback extends TelephonyCallback implements - TelephonyCallback.CallStateListener { - - private TelephonyManager mTelephonyManager; - - @Override - public void onCallStateChanged(int state) { - mCallState = state; - updateState(mPreference); - } - - public void register(Context context, int subId) { - mTelephonyManager = context.getSystemService(TelephonyManager.class); - if (SubscriptionManager.isValidSubscriptionId(subId)) { - mTelephonyManager = mTelephonyManager.createForSubscriptionId(subId); - } - // assign current call state so that it helps to show correct preference state even - // before first onCallStateChanged() by initial registration. - if (Flags.enforceTelephonyFeatureMappingForPublicApis()) { - try { - mCallState = mTelephonyManager.getCallState(subId); - } catch (UnsupportedOperationException e) { - // Device doesn't support FEATURE_TELEPHONY_CALLING - mCallState = TelephonyManager.CALL_STATE_IDLE; - } - } else { - mCallState = mTelephonyManager.getCallState(subId); - } - mTelephonyManager.registerTelephonyCallback(context.getMainExecutor(), this); - } - - public void unregister() { - mCallState = null; - mTelephonyManager.unregisterTelephonyCallback(this); - } - } - - /** - * Implementation of MobileDataEnabledListener.Client - */ - public void onMobileDataEnabledChange() { - updateState(mPreference); - } - - @VisibleForTesting - VtQueryImsState queryImsState(int subId) { - return new VtQueryImsState(mContext, subId); - } - - @VisibleForTesting - VolteQueryImsState queryVoLteState(int subId) { - return new VolteQueryImsState(mContext, subId); - } -} diff --git a/src/com/android/settings/network/telephony/VideoCallingPreferenceController.kt b/src/com/android/settings/network/telephony/VideoCallingPreferenceController.kt new file mode 100644 index 00000000000..e6b3f315425 --- /dev/null +++ b/src/com/android/settings/network/telephony/VideoCallingPreferenceController.kt @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2024 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.settings.network.telephony + +import android.content.Context +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.telephony.ims.ImsManager +import android.util.Log +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LifecycleOwner +import androidx.preference.Preference +import androidx.preference.PreferenceScreen +import androidx.preference.TwoStatePreference +import com.android.settings.R +import com.android.settings.core.TogglePreferenceController +import com.android.settings.network.ims.VolteQueryImsState +import com.android.settings.network.ims.VtQueryImsState +import com.android.settings.network.telephony.Enhanced4gBasePreferenceController.On4gLteUpdateListener +import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchItem +import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchResult +import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking + +/** Preference controller for "Video Calling" */ +class VideoCallingPreferenceController +@JvmOverloads +constructor( + context: Context, + key: String, + private val callStateRepository: CallStateRepository = CallStateRepository(context), +) : TogglePreferenceController(context, key), On4gLteUpdateListener { + + private var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID + private var preference: TwoStatePreference? = null + private var callingPreferenceCategoryController: CallingPreferenceCategoryController? = null + private val repository = VideoCallingRepository(context) + + private var videoCallEditable = false + private var isInCall = false + + /** Init instance of VideoCallingPreferenceController. */ + fun init( + subId: Int, + callingPreferenceCategoryController: CallingPreferenceCategoryController?, + ): VideoCallingPreferenceController { + this.subId = subId + this.callingPreferenceCategoryController = callingPreferenceCategoryController + + return this + } + + // Availability is controlled in onViewCreated() and VideoCallingSearchItem. + override fun getAvailabilityStatus() = AVAILABLE + + override fun displayPreference(screen: PreferenceScreen) { + super.displayPreference(screen) + preference = screen.findPreference(preferenceKey) + } + + override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { + repository.isVideoCallReadyFlow(subId).collectLatestWithLifecycle(viewLifecycleOwner) { + isReady -> + preference?.isVisible = isReady + callingPreferenceCategoryController?.updateChildVisible(preferenceKey, isReady) + } + callStateRepository.callStateFlow(subId).collectLatestWithLifecycle(viewLifecycleOwner) { + callState -> + isInCall = callState != TelephonyManager.CALL_STATE_IDLE + updatePreference() + } + } + + override fun updateState(preference: Preference) { + super.updateState(preference) + videoCallEditable = + queryVoLteState(subId).isEnabledByUser && queryImsState(subId).isAllowUserControl + updatePreference() + } + + private fun updatePreference() { + preference?.isEnabled = videoCallEditable && !isInCall + preference?.isChecked = videoCallEditable && isChecked + } + + override fun getSliceHighlightMenuRes() = NO_RES + + override fun setChecked(isChecked: Boolean): Boolean { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + return false + } + val imsMmTelManager = ImsManager(mContext).getImsMmTelManager(subId) + try { + imsMmTelManager.isVtSettingEnabled = isChecked + return true + } catch (exception: IllegalArgumentException) { + Log.w(TAG, "[$subId] Unable to set VT status $isChecked", exception) + } + return false + } + + override fun isChecked(): Boolean = queryImsState(subId).isEnabledByUser + + override fun on4gLteUpdated() { + preference?.let { updateState(it) } + } + + @VisibleForTesting fun queryImsState(subId: Int) = VtQueryImsState(mContext, subId) + + @VisibleForTesting fun queryVoLteState(subId: Int) = VolteQueryImsState(mContext, subId) + + companion object { + private const val TAG = "VideoCallingPreferenceController" + + class VideoCallingSearchItem(private val context: Context) : + MobileNetworkSettingsSearchItem { + private val repository = VideoCallingRepository(context) + + private fun isAvailable(subId: Int): Boolean = runBlocking { + repository.isVideoCallReadyFlow(subId).first() + } + + override fun getSearchResult(subId: Int): MobileNetworkSettingsSearchResult? { + if (!isAvailable(subId)) return null + return MobileNetworkSettingsSearchResult( + key = "video_calling_key", + title = context.getString(R.string.video_calling_settings_title), + ) + } + } + } +} diff --git a/src/com/android/settings/network/telephony/VideoCallingRepository.kt b/src/com/android/settings/network/telephony/VideoCallingRepository.kt new file mode 100644 index 00000000000..634eb285cdd --- /dev/null +++ b/src/com/android/settings/network/telephony/VideoCallingRepository.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 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.settings.network.telephony + +import android.content.Context +import android.telephony.AccessNetworkConstants +import android.telephony.CarrierConfigManager +import android.telephony.SubscriptionManager +import android.telephony.ims.feature.MmTelFeature +import android.telephony.ims.stub.ImsRegistrationImplBase +import com.android.settings.network.telephony.ims.ImsFeatureRepository +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf + +@OptIn(ExperimentalCoroutinesApi::class) +class VideoCallingRepository( + context: Context, + private val mobileDataRepository: MobileDataRepository = MobileDataRepository(context), + private val imsFeatureRepositoryFactory: (Int) -> ImsFeatureRepository = { subId -> + ImsFeatureRepository(context, subId) + }, +) { + private val carrierConfigRepository = CarrierConfigRepository(context) + + fun isVideoCallReadyFlow(subId: Int): Flow { + if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false) + + return isPreconditionMeetFlow(subId).flatMapLatest { isPreconditionMeet -> + if (isPreconditionMeet) { + imsFeatureRepositoryFactory(subId) + .isReadyFlow( + capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO, + tech = ImsRegistrationImplBase.REGISTRATION_TECH_LTE, + transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + ) + } else { + flowOf(false) + } + } + } + + private fun isPreconditionMeetFlow(subId: Int): Flow = + if (carrierConfigRepository.getBoolean( + subId, CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS)) { + flowOf(true) + } else { + mobileDataRepository.isMobileDataEnabledFlow(subId) + } +} diff --git a/tests/robotests/src/com/android/settings/network/ims/MockVolteQueryImsState.java b/tests/robotests/src/com/android/settings/network/ims/MockVolteQueryImsState.java deleted file mode 100644 index 515ab5b659b..00000000000 --- a/tests/robotests/src/com/android/settings/network/ims/MockVolteQueryImsState.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2020 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.settings.network.ims; - -import android.content.Context; -import android.telephony.ims.ImsException; - -/** - * Controller class for mock VoLte status - */ -public class MockVolteQueryImsState extends VolteQueryImsState { - - private Boolean mIsTtyOnVolteEnabled; - private Boolean mIsSupported; - private Boolean mIsProvisionedOnDevice; - private Boolean mIsServiceStateReady; - private Boolean mIsEnabledByUser; - - /** - * Constructor - * - * @param context {@link Context} - * @param subId subscription's id - */ - public MockVolteQueryImsState(Context context, int subId) { - super(context, subId); - } - - public void setIsTtyOnVolteEnabled(boolean enabled) { - mIsTtyOnVolteEnabled = enabled; - } - - @Override - boolean isTtyOnVolteEnabled(int subId) { - if (mIsTtyOnVolteEnabled != null) { - return mIsTtyOnVolteEnabled; - } - return super.isTtyOnVolteEnabled(subId); - } - - public void setEnabledByPlatform(boolean isSupported) { - mIsSupported = isSupported; - } - - @Override - boolean isEnabledByPlatform(int subId) throws InterruptedException, ImsException, - IllegalArgumentException { - if (mIsSupported != null) { - return mIsSupported; - } - return super.isEnabledByPlatform(subId); - } - - public void setIsProvisionedOnDevice(boolean isProvisioned) { - mIsProvisionedOnDevice = isProvisioned; - } - - @Override - boolean isProvisionedOnDevice(int subId) { - if (mIsProvisionedOnDevice != null) { - return mIsProvisionedOnDevice; - } - return super.isProvisionedOnDevice(subId); - } - - public void setServiceStateReady(boolean isReady) { - mIsServiceStateReady = isReady; - } - - @Override - boolean isServiceStateReady(int subId) throws InterruptedException, ImsException, - IllegalArgumentException { - if (mIsServiceStateReady != null) { - return mIsServiceStateReady; - } - return super.isServiceStateReady(subId); - } - - public void setIsEnabledByUser(boolean enabled) { - mIsEnabledByUser = enabled; - } - - @Override - boolean isEnabledByUser(int subId) { - if (mIsEnabledByUser != null) { - return mIsEnabledByUser; - } - return super.isEnabledByUser(subId); - } - -} diff --git a/tests/robotests/src/com/android/settings/network/ims/MockVtQueryImsState.java b/tests/robotests/src/com/android/settings/network/ims/MockVtQueryImsState.java deleted file mode 100644 index 0949f1c0262..00000000000 --- a/tests/robotests/src/com/android/settings/network/ims/MockVtQueryImsState.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2020 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.settings.network.ims; - -import android.content.Context; -import android.telephony.ims.ImsException; - -/** - * Controller class for mock VT status - */ -public class MockVtQueryImsState extends VtQueryImsState { - - private Boolean mIsTtyOnVolteEnabled; - private Boolean mIsEnabledOnPlatform; - private Boolean mIsProvisionedOnDevice; - private Boolean mIsEnabledByUser; - private Boolean mIsServiceStateReady; - - /** - * Constructor - * - * @param context {@link Context} - * @param subId subscription's id - */ - public MockVtQueryImsState(Context context, int subId) { - super(context, subId); - } - - public void setIsTtyOnVolteEnabled(boolean enabled) { - mIsTtyOnVolteEnabled = enabled; - } - - @Override - boolean isTtyOnVolteEnabled(int subId) { - if (mIsTtyOnVolteEnabled != null) { - return mIsTtyOnVolteEnabled; - } - return super.isTtyOnVolteEnabled(subId); - } - - public void setIsEnabledByPlatform(boolean isEnabled) { - mIsEnabledOnPlatform = isEnabled; - } - - @Override - boolean isEnabledByPlatform(int subId) throws InterruptedException, ImsException, - IllegalArgumentException { - if (mIsEnabledOnPlatform != null) { - return mIsEnabledOnPlatform; - } - return super.isEnabledByPlatform(subId); - } - - public void setIsProvisionedOnDevice(boolean isProvisioned) { - mIsProvisionedOnDevice = isProvisioned; - } - - @Override - boolean isProvisionedOnDevice(int subId) { - if (mIsProvisionedOnDevice != null) { - return mIsProvisionedOnDevice; - } - return super.isProvisionedOnDevice(subId); - } - - public void setServiceStateReady(boolean isReady) { - mIsServiceStateReady = isReady; - } - - @Override - boolean isServiceStateReady(int subId) throws InterruptedException, ImsException, - IllegalArgumentException { - if (mIsServiceStateReady != null) { - return mIsServiceStateReady; - } - return super.isServiceStateReady(subId); - } - - public void setIsEnabledByUser(boolean enabled) { - mIsEnabledByUser = enabled; - } - - @Override - boolean isEnabledByUser(int subId) { - if (mIsEnabledByUser != null) { - return mIsEnabledByUser; - } - return super.isEnabledByUser(subId); - } -} diff --git a/tests/robotests/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.java deleted file mode 100644 index da8958dff2e..00000000000 --- a/tests/robotests/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2018 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.settings.network.telephony; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; - -import android.content.Context; -import android.os.PersistableBundle; -import android.telephony.CarrierConfigManager; -import android.telephony.TelephonyManager; -import android.telephony.ims.ProvisioningManager; - -import androidx.preference.PreferenceScreen; -import androidx.preference.SwitchPreference; - -import com.android.settings.network.CarrierConfigCache; -import com.android.settings.network.ims.MockVolteQueryImsState; -import com.android.settings.network.ims.MockVtQueryImsState; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -@RunWith(RobolectricTestRunner.class) -public class VideoCallingPreferenceControllerTest { - private static final int SUB_ID = 2; - - @Mock - private TelephonyManager mTelephonyManager; - @Mock - private ProvisioningManager mProvisioningManager; - @Mock - private CarrierConfigCache mCarrierConfigCache; - @Mock - private PreferenceScreen mPreferenceScreen; - - private MockVtQueryImsState mQueryImsState; - private MockVolteQueryImsState mQueryVoLteState; - - private VideoCallingPreferenceController mController; - private PersistableBundle mCarrierConfig; - private SwitchPreference mPreference; - private Context mContext; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mContext = spy(RuntimeEnvironment.application); - doReturn(mTelephonyManager).when(mContext).getSystemService(TelephonyManager.class); - CarrierConfigCache.setTestInstance(mContext, mCarrierConfigCache); - doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID); - - mCarrierConfig = new PersistableBundle(); - mCarrierConfig.putBoolean( - CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, true); - doReturn(mCarrierConfig).when(mCarrierConfigCache).getConfigForSubId(SUB_ID); - - mQueryImsState = new MockVtQueryImsState(mContext, SUB_ID); - mQueryImsState.setIsEnabledByUser(true); - - mQueryVoLteState = new MockVolteQueryImsState(mContext, SUB_ID); - mQueryVoLteState.setIsEnabledByUser(true); - - mPreference = new SwitchPreference(mContext); - mController = spy(new VideoCallingPreferenceController(mContext, "wifi_calling")); - mController.init( - SUB_ID, new CallingPreferenceCategoryController(mContext, "calling_category")); - doReturn(mQueryImsState).when(mController).queryImsState(anyInt()); - doReturn(mQueryVoLteState).when(mController).queryVoLteState(anyInt()); - doReturn(true).when(mController).isImsSupported(); - mPreference.setKey(mController.getPreferenceKey()); - - mQueryImsState.setIsEnabledByPlatform(true); - mQueryImsState.setIsProvisionedOnDevice(true); - mQueryImsState.setServiceStateReady(true); - doReturn(true).when(mTelephonyManager).isDataEnabled(); - - mController.mCallState = TelephonyManager.CALL_STATE_IDLE; - } - - @Test - public void isVideoCallEnabled_allFlagsOn_returnTrue() { - assertThat(mController.isVideoCallEnabled(SUB_ID)).isTrue(); - } - - @Test - public void isVideoCallEnabled_disabledByPlatform_returnFalse() { - mQueryImsState.setIsProvisionedOnDevice(false); - mQueryImsState.setIsEnabledByPlatform(false); - - assertThat(mController.isVideoCallEnabled(SUB_ID)).isFalse(); - } - - @Test - public void isVideoCallEnabled_dataDisabled_returnFalse() { - mCarrierConfig.putBoolean( - CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, false); - doReturn(false).when(mTelephonyManager).isDataEnabled(); - - assertThat(mController.isVideoCallEnabled(SUB_ID)).isFalse(); - } - - @Test - public void updateState_4gLteOff_disabled() { - mQueryImsState.setIsEnabledByUser(false); - mQueryVoLteState.setIsEnabledByUser(false); - - mController.updateState(mPreference); - - assertThat(mPreference.isEnabled()).isFalse(); - assertThat(mPreference.isChecked()).isFalse(); - } - - @Test - public void updateState_4gLteOnWithoutCall_checked() { - mQueryImsState.setIsEnabledByUser(true); - mQueryVoLteState.setIsEnabledByUser(true); - mQueryImsState.setIsTtyOnVolteEnabled(true); - mController.mCallState = TelephonyManager.CALL_STATE_IDLE; - - mController.updateState(mPreference); - - assertThat(mPreference.isEnabled()).isTrue(); - assertThat(mPreference.isChecked()).isTrue(); - } - - - @Test - public void displayPreference_notAvailable_setPreferenceInvisible() { - mQueryImsState.setIsEnabledByPlatform(false); - - mController.displayPreference(mPreferenceScreen); - - assertThat(mPreferenceScreen.isVisible()).isFalse(); - } - -} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.kt new file mode 100644 index 00000000000..4babfaae301 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.kt @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2024 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.settings.network.telephony + +import android.content.Context +import android.telephony.TelephonyManager +import androidx.lifecycle.testing.TestLifecycleOwner +import androidx.preference.PreferenceManager +import androidx.preference.SwitchPreferenceCompat +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.network.ims.VolteQueryImsState +import com.android.settings.network.ims.VtQueryImsState +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub + +@RunWith(AndroidJUnit4::class) +class VideoCallingPreferenceControllerTest { + + private val mockVtQueryImsState = mock {} + + private var mockQueryVoLteState = mock {} + + private val context: Context = ApplicationProvider.getApplicationContext() + + private val mockCallStateRepository = mock {} + + private var controller = + spy( + VideoCallingPreferenceController( + context = context, + key = TEST_KEY, + callStateRepository = mockCallStateRepository, + ) + ) { + on { queryImsState(SUB_ID) } doReturn mockVtQueryImsState + on { queryVoLteState(SUB_ID) } doReturn mockQueryVoLteState + } + + private val preference = SwitchPreferenceCompat(context).apply { key = TEST_KEY } + private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context) + + @Before + fun setUp() { + controller.init(SUB_ID, CallingPreferenceCategoryController(context, "calling_category")) + preferenceScreen.addPreference(preference) + controller.displayPreference(preferenceScreen) + } + + @Test + fun updateState_4gLteOff_disabledAndUnchecked() { + mockQueryVoLteState.stub { on { isEnabledByUser } doReturn false } + + controller.updateState(preference) + + assertThat(preference.isEnabled).isFalse() + assertThat(preference.isChecked).isFalse() + } + + @Test + fun updateState_4gLteOnWithoutCall_enabledAndChecked() = runBlocking { + mockVtQueryImsState.stub { + on { isEnabledByUser } doReturn true + on { isAllowUserControl } doReturn true + } + mockQueryVoLteState.stub { on { isEnabledByUser } doReturn true } + mockCallStateRepository.stub { + on { callStateFlow(SUB_ID) } doReturn flowOf(TelephonyManager.CALL_STATE_IDLE) + } + + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + controller.updateState(preference) + + assertThat(preference.isEnabled).isTrue() + assertThat(preference.isChecked).isTrue() + } + + @Test + fun updateState_4gLteOnWithCall_disabledAndChecked() = runBlocking { + mockVtQueryImsState.stub { + on { isEnabledByUser } doReturn true + on { isAllowUserControl } doReturn true + } + mockQueryVoLteState.stub { on { isEnabledByUser } doReturn true } + mockCallStateRepository.stub { + on { callStateFlow(SUB_ID) } doReturn flowOf(TelephonyManager.CALL_STATE_RINGING) + } + + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + controller.updateState(preference) + + assertThat(preference.isEnabled).isFalse() + assertThat(preference.isChecked).isTrue() + } + + private companion object { + const val TEST_KEY = "test_key" + const val SUB_ID = 10 + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/VideoCallingRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/VideoCallingRepositoryTest.kt new file mode 100644 index 00000000000..063e1918a89 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/VideoCallingRepositoryTest.kt @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2024 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.settings.network.telephony + +import android.content.Context +import android.telephony.AccessNetworkConstants +import android.telephony.CarrierConfigManager +import android.telephony.SubscriptionManager +import android.telephony.ims.feature.MmTelFeature +import android.telephony.ims.stub.ImsRegistrationImplBase +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.network.telephony.ims.ImsFeatureRepository +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub + +@RunWith(AndroidJUnit4::class) +class VideoCallingRepositoryTest { + + private val context: Context = ApplicationProvider.getApplicationContext() + + private val mockMobileDataRepository = mock() + private val mockImsFeatureRepository = mock() + + private val repository = + VideoCallingRepository( + context = context, + mobileDataRepository = mockMobileDataRepository, + imsFeatureRepositoryFactory = { mockImsFeatureRepository }, + ) + + @Before + fun setUp() { + CarrierConfigRepository.resetForTest() + } + + @Test + fun isVideoCallReadyFlow_invalidSubId() = runBlocking { + val isVideoCallReady = + repository + .isVideoCallReadyFlow(subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID) + .firstWithTimeoutOrNull() + + assertThat(isVideoCallReady).isFalse() + } + + @Test + fun isVideoCallReadyFlow_ignoreDataEnabledChangedAndIsReady_returnTrue() = runBlocking { + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, + value = true, + ) + mockImsFeatureRepository.stub { + on { + isReadyFlow( + capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO, + tech = ImsRegistrationImplBase.REGISTRATION_TECH_LTE, + transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + ) + } doReturn flowOf(true) + } + + val isVideoCallReady = repository.isVideoCallReadyFlow(SUB_ID).firstWithTimeoutOrNull() + + assertThat(isVideoCallReady).isTrue() + } + + @Test + fun isVideoCallReadyFlow_ignoreDataEnabledChangedAndNotReady_returnFalse() = runBlocking { + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, + value = true, + ) + mockImsFeatureRepository.stub { + on { + isReadyFlow( + capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO, + tech = ImsRegistrationImplBase.REGISTRATION_TECH_LTE, + transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + ) + } doReturn flowOf(false) + } + + val isVideoCallReady = repository.isVideoCallReadyFlow(SUB_ID).firstWithTimeoutOrNull() + + assertThat(isVideoCallReady).isFalse() + } + + @Test + fun isVideoCallReadyFlow_mobileDataEnabledAndIsReady_returnTrue() = runBlocking { + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, + value = false, + ) + mockMobileDataRepository.stub { + on { isMobileDataEnabledFlow(SUB_ID) } doReturn flowOf(true) + } + mockImsFeatureRepository.stub { + on { + isReadyFlow( + capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO, + tech = ImsRegistrationImplBase.REGISTRATION_TECH_LTE, + transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + ) + } doReturn flowOf(true) + } + + val isVideoCallReady = repository.isVideoCallReadyFlow(SUB_ID).firstWithTimeoutOrNull() + + assertThat(isVideoCallReady).isTrue() + } + + @Test + fun isVideoCallReadyFlow_ignoreDataEnabledChangedAndIsReady_returnFalse() = runBlocking { + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, + value = false, + ) + mockMobileDataRepository.stub { + on { isMobileDataEnabledFlow(SUB_ID) } doReturn flowOf(false) + } + mockImsFeatureRepository.stub { + on { + isReadyFlow( + capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO, + tech = ImsRegistrationImplBase.REGISTRATION_TECH_LTE, + transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + ) + } doReturn flowOf(true) + } + + val isVideoCallReady = repository.isVideoCallReadyFlow(SUB_ID).firstWithTimeoutOrNull() + + assertThat(isVideoCallReady).isFalse() + } + + private companion object { + const val SUB_ID = 10 + } +} diff --git a/tests/unit/src/com/android/settings/network/ims/MockVtQueryImsState.java b/tests/unit/src/com/android/settings/network/ims/MockVtQueryImsState.java deleted file mode 100644 index 0949f1c0262..00000000000 --- a/tests/unit/src/com/android/settings/network/ims/MockVtQueryImsState.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2020 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.settings.network.ims; - -import android.content.Context; -import android.telephony.ims.ImsException; - -/** - * Controller class for mock VT status - */ -public class MockVtQueryImsState extends VtQueryImsState { - - private Boolean mIsTtyOnVolteEnabled; - private Boolean mIsEnabledOnPlatform; - private Boolean mIsProvisionedOnDevice; - private Boolean mIsEnabledByUser; - private Boolean mIsServiceStateReady; - - /** - * Constructor - * - * @param context {@link Context} - * @param subId subscription's id - */ - public MockVtQueryImsState(Context context, int subId) { - super(context, subId); - } - - public void setIsTtyOnVolteEnabled(boolean enabled) { - mIsTtyOnVolteEnabled = enabled; - } - - @Override - boolean isTtyOnVolteEnabled(int subId) { - if (mIsTtyOnVolteEnabled != null) { - return mIsTtyOnVolteEnabled; - } - return super.isTtyOnVolteEnabled(subId); - } - - public void setIsEnabledByPlatform(boolean isEnabled) { - mIsEnabledOnPlatform = isEnabled; - } - - @Override - boolean isEnabledByPlatform(int subId) throws InterruptedException, ImsException, - IllegalArgumentException { - if (mIsEnabledOnPlatform != null) { - return mIsEnabledOnPlatform; - } - return super.isEnabledByPlatform(subId); - } - - public void setIsProvisionedOnDevice(boolean isProvisioned) { - mIsProvisionedOnDevice = isProvisioned; - } - - @Override - boolean isProvisionedOnDevice(int subId) { - if (mIsProvisionedOnDevice != null) { - return mIsProvisionedOnDevice; - } - return super.isProvisionedOnDevice(subId); - } - - public void setServiceStateReady(boolean isReady) { - mIsServiceStateReady = isReady; - } - - @Override - boolean isServiceStateReady(int subId) throws InterruptedException, ImsException, - IllegalArgumentException { - if (mIsServiceStateReady != null) { - return mIsServiceStateReady; - } - return super.isServiceStateReady(subId); - } - - public void setIsEnabledByUser(boolean enabled) { - mIsEnabledByUser = enabled; - } - - @Override - boolean isEnabledByUser(int subId) { - if (mIsEnabledByUser != null) { - return mIsEnabledByUser; - } - return super.isEnabledByUser(subId); - } -}