From 355144675a41ed8e531c02d8c275c91ce7f18efa Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Fri, 29 Dec 2023 13:26:09 +0800 Subject: [PATCH] Fix ANR in WifiCallingPreferenceController.getAvailabilityStatus Move the following to background thread to avoid block main thread, - MobileNetworkUtils.isWifiCallingEnabled(mContext, mSubId, null) - MobileNetworkUtils.buildPhoneAccountConfigureIntent() - getSummaryForWfcMode() - Call State Since WifiCallingPreferenceController no longer calculate availability in getAvailabilityStatus(), also update the CallingPreferenceCategoryController accordingly. Also introduce ImsMmTelRepository for split business logic for easy testing. Fix: 292401934 Test: manual - on Mobile Settings Test: unit test Change-Id: If92e2c8f6e137e40b83e578294c03c1b917eef8e --- .../CallingPreferenceCategoryController.java | 31 --- .../CallingPreferenceCategoryController.kt | 48 ++++ .../telephony/MobileNetworkSettings.java | 11 +- .../VideoCallingPreferenceController.java | 10 +- .../WifiCallingPreferenceController.java | 230 ------------------ .../WifiCallingPreferenceController.kt | 140 +++++++++++ .../telephony/ims/ImsMmTelRepository.kt | 57 +++++ .../VideoCallingPreferenceControllerTest.java | 3 +- ...CallingPreferenceCategoryControllerTest.kt | 80 ++++++ .../WifiCallingPreferenceControllerTest.kt | 140 +++++++++++ .../telephony/ims/ImsMmTelRepositoryTest.kt | 115 +++++++++ .../WifiCallingPreferenceControllerTest.java | 227 ----------------- 12 files changed, 597 insertions(+), 495 deletions(-) delete mode 100644 src/com/android/settings/network/telephony/CallingPreferenceCategoryController.java create mode 100644 src/com/android/settings/network/telephony/CallingPreferenceCategoryController.kt delete mode 100644 src/com/android/settings/network/telephony/WifiCallingPreferenceController.java create mode 100644 src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt create mode 100644 src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt create mode 100644 tests/spa_unit/src/com/android/settings/network/telephony/CallingPreferenceCategoryControllerTest.kt create mode 100644 tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt create mode 100644 tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt delete mode 100644 tests/unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.java diff --git a/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.java b/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.java deleted file mode 100644 index f8364158d58..00000000000 --- a/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2019 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 com.android.settings.widget.PreferenceCategoryController; - -/** - * Preference controller for "Calling" category - */ -public class CallingPreferenceCategoryController extends PreferenceCategoryController { - - public CallingPreferenceCategoryController(Context context, String key) { - super(context, key); - } -} diff --git a/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.kt b/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.kt new file mode 100644 index 00000000000..5356a417235 --- /dev/null +++ b/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 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 androidx.preference.Preference +import androidx.preference.PreferenceScreen +import com.android.settings.core.BasePreferenceController + +/** + * Preference controller for "Calling" category + */ +class CallingPreferenceCategoryController(context: Context, key: String) : + BasePreferenceController(context, key) { + + private val visibleChildren = mutableSetOf() + private var preference: Preference? = null + + override fun getAvailabilityStatus() = AVAILABLE + + override fun displayPreference(screen: PreferenceScreen) { + // Not call super here, to avoid preference.isVisible changed unexpectedly + preference = screen.findPreference(preferenceKey) + } + + fun updateChildVisible(key: String, isVisible: Boolean) { + if (isVisible) { + visibleChildren.add(key) + } else { + visibleChildren.remove(key) + } + preference?.isVisible = visibleChildren.isNotEmpty() + } +} diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index 0812ccc83cd..16b04aa356d 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -269,8 +269,10 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme use(Enable2gPreferenceController.class).init(mSubId); use(CarrierWifiTogglePreferenceController.class).init(getLifecycle(), mSubId); - final WifiCallingPreferenceController wifiCallingPreferenceController = - use(WifiCallingPreferenceController.class).init(mSubId); + final CallingPreferenceCategoryController callingPreferenceCategoryController = + use(CallingPreferenceCategoryController.class); + use(WifiCallingPreferenceController.class) + .init(mSubId, callingPreferenceCategoryController); final OpenNetworkSelectPagePreferenceController openNetworkSelectPagePreferenceController = use(OpenNetworkSelectPagePreferenceController.class).init(mSubId); @@ -286,9 +288,8 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme mCdmaSubscriptionPreferenceController.init(getPreferenceManager(), mSubId); final VideoCallingPreferenceController videoCallingPreferenceController = - use(VideoCallingPreferenceController.class).init(mSubId); - use(CallingPreferenceCategoryController.class).setChildren( - Arrays.asList(wifiCallingPreferenceController, videoCallingPreferenceController)); + use(VideoCallingPreferenceController.class) + .init(mSubId, callingPreferenceCategoryController); use(Enhanced4gLtePreferenceController.class).init(mSubId) .addListener(videoCallingPreferenceController); use(Enhanced4gCallingPreferenceController.class).init(mSubId) diff --git a/src/com/android/settings/network/telephony/VideoCallingPreferenceController.java b/src/com/android/settings/network/telephony/VideoCallingPreferenceController.java index 1519bf01940..5810510a8b1 100644 --- a/src/com/android/settings/network/telephony/VideoCallingPreferenceController.java +++ b/src/com/android/settings/network/telephony/VideoCallingPreferenceController.java @@ -54,6 +54,7 @@ public class VideoCallingPreferenceController extends TelephonyTogglePreferenceC @VisibleForTesting Integer mCallState; private MobileDataEnabledListener mDataContentObserver; + private CallingPreferenceCategoryController mCallingPreferenceCategoryController; public VideoCallingPreferenceController(Context context, String key) { super(context, key); @@ -97,6 +98,8 @@ public class VideoCallingPreferenceController extends TelephonyTogglePreferenceC 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(); @@ -136,8 +139,13 @@ public class VideoCallingPreferenceController extends TelephonyTogglePreferenceC PackageManager.FEATURE_TELEPHONY_IMS); } - public VideoCallingPreferenceController init(int subId) { + /** + * Init instance of VideoCallingPreferenceController. + */ + public VideoCallingPreferenceController init( + int subId, CallingPreferenceCategoryController callingPreferenceCategoryController) { mSubId = subId; + mCallingPreferenceCategoryController = callingPreferenceCategoryController; return this; } diff --git a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.java b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.java deleted file mode 100644 index 5503e95854d..00000000000 --- a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.java +++ /dev/null @@ -1,230 +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.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.PersistableBundle; -import android.provider.Settings; -import android.telecom.PhoneAccountHandle; -import android.telecom.TelecomManager; -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 com.android.settings.R; -import com.android.settings.network.ims.WifiCallingQueryImsState; -import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnStart; -import com.android.settingslib.core.lifecycle.events.OnStop; - -import java.util.List; - -/** - * Preference controller for "Wifi Calling" - */ -//TODO: Remove the class once Provider Model is always enabled in the future. -public class WifiCallingPreferenceController extends TelephonyBasePreferenceController implements - LifecycleObserver, OnStart, OnStop { - - private static final String TAG = "WifiCallingPreference"; - - @VisibleForTesting - Integer mCallState; - @VisibleForTesting - CarrierConfigManager mCarrierConfigManager; - private ImsMmTelManager mImsMmTelManager; - @VisibleForTesting - PhoneAccountHandle mSimCallManager; - private PhoneTelephonyCallback mTelephonyCallback; - private Preference mPreference; - private boolean mHasException; - - public WifiCallingPreferenceController(Context context, String key) { - super(context, key); - mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class); - mTelephonyCallback = new PhoneTelephonyCallback(); - } - - @Override - public int getAvailabilityStatus(int subId) { - return SubscriptionManager.isValidSubscriptionId(subId) - && MobileNetworkUtils.isWifiCallingEnabled(mContext, subId, null) - ? AVAILABLE - : UNSUPPORTED_ON_DEVICE; - } - - @Override - public void onStart() { - mTelephonyCallback.register(mContext, mSubId); - } - - @Override - public void onStop() { - mTelephonyCallback.unregister(); - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mPreference = screen.findPreference(getPreferenceKey()); - final Intent intent = mPreference.getIntent(); - if (intent != null) { - intent.putExtra(Settings.EXTRA_SUB_ID, mSubId); - } - } - - @Override - public void updateState(Preference preference) { - super.updateState(preference); - if ((mCallState == null) || (preference == null)) { - Log.d(TAG, "Skip update under mCallState=" + mCallState); - return; - } - mHasException = false; - CharSequence summaryText = null; - if (mSimCallManager != null) { - final Intent intent = MobileNetworkUtils.buildPhoneAccountConfigureIntent(mContext, - mSimCallManager); - if (intent == null) { - // Do nothing in this case since preference is invisible - return; - } - final PackageManager pm = mContext.getPackageManager(); - final List resolutions = pm.queryIntentActivities(intent, 0); - preference.setTitle(resolutions.get(0).loadLabel(pm)); - preference.setIntent(intent); - } else { - final String title = SubscriptionManager.getResourcesForSubId(mContext, mSubId) - .getString(R.string.wifi_calling_settings_title); - preference.setTitle(title); - summaryText = getResourceIdForWfcMode(mSubId); - } - preference.setSummary(summaryText); - preference.setEnabled(mCallState == TelephonyManager.CALL_STATE_IDLE && !mHasException); - } - - private CharSequence getResourceIdForWfcMode(int subId) { - int resId = com.android.internal.R.string.wifi_calling_off_summary; - if (queryImsState(subId).isEnabledByUser()) { - boolean useWfcHomeModeForRoaming = false; - if (mCarrierConfigManager != null) { - final PersistableBundle carrierConfig = - mCarrierConfigManager.getConfigForSubId(subId); - if (carrierConfig != null) { - useWfcHomeModeForRoaming = carrierConfig.getBoolean( - CarrierConfigManager - .KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL); - } - } - final boolean isRoaming = getTelephonyManager(mContext, subId) - .isNetworkRoaming(); - int wfcMode = ImsMmTelManager.WIFI_MODE_UNKNOWN; - try { - wfcMode = (isRoaming && !useWfcHomeModeForRoaming) - ? mImsMmTelManager.getVoWiFiRoamingModeSetting() : - mImsMmTelManager.getVoWiFiModeSetting(); - } catch (IllegalArgumentException e) { - mHasException = true; - Log.e(TAG, "getResourceIdForWfcMode: Exception", e); - } - - switch (wfcMode) { - case ImsMmTelManager.WIFI_MODE_WIFI_ONLY: - resId = com.android.internal.R.string.wfc_mode_wifi_only_summary; - break; - case ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED: - resId = com.android.internal.R.string - .wfc_mode_cellular_preferred_summary; - break; - case ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED: - resId = com.android.internal.R.string.wfc_mode_wifi_preferred_summary; - break; - default: - break; - } - } - return SubscriptionManager.getResourcesForSubId(mContext, subId).getText(resId); - } - - public WifiCallingPreferenceController init(int subId) { - mSubId = subId; - mImsMmTelManager = getImsMmTelManager(mSubId); - mSimCallManager = mContext.getSystemService(TelecomManager.class) - .getSimCallManagerForSubscription(mSubId); - - return this; - } - - @VisibleForTesting - WifiCallingQueryImsState queryImsState(int subId) { - return new WifiCallingQueryImsState(mContext, subId); - } - - protected ImsMmTelManager getImsMmTelManager(int subId) { - if (!SubscriptionManager.isValidSubscriptionId(subId)) { - return null; - } - return ImsMmTelManager.createForSubscriptionId(subId); - } - - @VisibleForTesting - TelephonyManager getTelephonyManager(Context context, int subId) { - final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); - if (!SubscriptionManager.isValidSubscriptionId(subId)) { - return telephonyMgr; - } - final TelephonyManager subscriptionTelephonyMgr = - telephonyMgr.createForSubscriptionId(subId); - return (subscriptionTelephonyMgr == null) ? telephonyMgr : subscriptionTelephonyMgr; - } - - - 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 = getTelephonyManager(context, subId); - // assign current call state so that it helps to show correct preference state even - // before first onCallStateChanged() by initial registration. - mCallState = mTelephonyManager.getCallStateForSubscription(); - mTelephonyManager.registerTelephonyCallback(context.getMainExecutor(), this); - } - - public void unregister() { - mCallState = null; - mTelephonyManager.unregisterTelephonyCallback(this); - } - } -} diff --git a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt new file mode 100644 index 00000000000..e7b83189f86 --- /dev/null +++ b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2023 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.provider.Settings +import android.telecom.TelecomManager +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.telephony.ims.ImsMmTelManager +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.preference.Preference +import androidx.preference.PreferenceScreen +import com.android.settings.R +import com.android.settings.network.telephony.ims.ImsMmTelRepository +import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl +import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * Preference controller for "Wifi Calling". + * + * TODO: Remove the class once Provider Model is always enabled in the future. + */ +open class WifiCallingPreferenceController @JvmOverloads constructor( + context: Context, + key: String, + private val callStateFlowFactory: (subId: Int) -> Flow = context::callStateFlow, + private val imsMmTelRepositoryFactory: (subId: Int) -> ImsMmTelRepository = { subId -> + ImsMmTelRepositoryImpl(context, subId) + }, +) : TelephonyBasePreferenceController(context, key) { + + private lateinit var preference: Preference + private lateinit var callingPreferenceCategoryController: CallingPreferenceCategoryController + + private val resourcesForSub by lazy { + SubscriptionManager.getResourcesForSubId(mContext, mSubId) + } + + fun init( + subId: Int, + callingPreferenceCategoryController: CallingPreferenceCategoryController, + ): WifiCallingPreferenceController { + mSubId = subId + this.callingPreferenceCategoryController = callingPreferenceCategoryController + return this + } + + /** + * Note: Visibility also controlled by [onViewCreated]. + */ + override fun getAvailabilityStatus(subId: Int) = + if (SubscriptionManager.isValidSubscriptionId(subId)) AVAILABLE + else CONDITIONALLY_UNAVAILABLE + + override fun displayPreference(screen: PreferenceScreen) { + // Not call super here, to avoid preference.isVisible changed unexpectedly + preference = screen.findPreference(preferenceKey)!! + preference.intent?.putExtra(Settings.EXTRA_SUB_ID, mSubId) + } + + override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + val isVisible = withContext(Dispatchers.Default) { + MobileNetworkUtils.isWifiCallingEnabled(mContext, mSubId, null) + } + preference.isVisible = isVisible + callingPreferenceCategoryController.updateChildVisible(preferenceKey, isVisible) + } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + update() + } + } + + callStateFlowFactory(mSubId).collectLatestWithLifecycle(viewLifecycleOwner) { + preference.isEnabled = (it == TelephonyManager.CALL_STATE_IDLE) + } + } + + private suspend fun update() { + val simCallManager = mContext.getSystemService(TelecomManager::class.java) + ?.getSimCallManagerForSubscription(mSubId) + if (simCallManager != null) { + val intent = withContext(Dispatchers.Default) { + MobileNetworkUtils.buildPhoneAccountConfigureIntent(mContext, simCallManager) + } ?: return // Do nothing in this case since preference is invisible + val title = withContext(Dispatchers.Default) { + mContext.packageManager.resolveActivity(intent, 0) + ?.loadLabel(mContext.packageManager) + } ?: return + preference.intent = intent + preference.title = title + preference.summary = null + } else { + preference.title = resourcesForSub.getString(R.string.wifi_calling_settings_title) + preference.summary = withContext(Dispatchers.Default) { getSummaryForWfcMode() } + } + } + + private fun getSummaryForWfcMode(): String { + val resId = when (imsMmTelRepositoryFactory(mSubId).getWiFiCallingMode()) { + ImsMmTelManager.WIFI_MODE_WIFI_ONLY -> + com.android.internal.R.string.wfc_mode_wifi_only_summary + + ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED -> + com.android.internal.R.string.wfc_mode_cellular_preferred_summary + + ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED -> + com.android.internal.R.string.wfc_mode_wifi_preferred_summary + + else -> com.android.internal.R.string.wifi_calling_off_summary + } + return resourcesForSub.getString(resId) + } +} diff --git a/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt new file mode 100644 index 00000000000..44f09d13e4d --- /dev/null +++ b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 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.ims + +import android.content.Context +import android.telephony.CarrierConfigManager +import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL +import android.telephony.TelephonyManager +import android.telephony.ims.ImsManager +import android.telephony.ims.ImsMmTelManager +import android.telephony.ims.ImsMmTelManager.WiFiCallingMode + +interface ImsMmTelRepository { + @WiFiCallingMode + fun getWiFiCallingMode(): Int +} + +class ImsMmTelRepositoryImpl( + context: Context, + private val subId: Int, + private val imsMmTelManager: ImsMmTelManager = ImsManager(context).getImsMmTelManager(subId), +) : ImsMmTelRepository { + + private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!! + .createForSubscriptionId(subId) + + private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!! + + @WiFiCallingMode + override fun getWiFiCallingMode(): Int = when { + !imsMmTelManager.isVoWiFiSettingEnabled -> ImsMmTelManager.WIFI_MODE_UNKNOWN + + telephonyManager.isNetworkRoaming && !useWfcHomeModeForRoaming() -> + imsMmTelManager.getVoWiFiRoamingModeSetting() + + else -> imsMmTelManager.getVoWiFiModeSetting() + } + + private fun useWfcHomeModeForRoaming(): Boolean = + carrierConfigManager + .getConfigForSubId(subId, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL) + .getBoolean(KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL) +} diff --git a/tests/robotests/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.java index 9b44faa9295..da8958dff2e 100644 --- a/tests/robotests/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.java @@ -86,7 +86,8 @@ public class VideoCallingPreferenceControllerTest { mPreference = new SwitchPreference(mContext); mController = spy(new VideoCallingPreferenceController(mContext, "wifi_calling")); - mController.init(SUB_ID); + 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(); diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/CallingPreferenceCategoryControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/CallingPreferenceCategoryControllerTest.kt new file mode 100644 index 00000000000..81d17d2ea3b --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/CallingPreferenceCategoryControllerTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 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 androidx.preference.PreferenceManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.spa.preference.ComposePreference +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class CallingPreferenceCategoryControllerTest { + + private val context: Context = ApplicationProvider.getApplicationContext() + + private val preference = ComposePreference(context).apply { key = TEST_KEY } + private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context) + + private val controller = CallingPreferenceCategoryController(context, TEST_KEY) + + @Before + fun setUp() { + preferenceScreen.addPreference(preference) + controller.displayPreference(preferenceScreen) + } + + @Test + fun updateChildVisible_singleChildVisible_categoryVisible() { + controller.updateChildVisible(CHILD_A_KEY, true) + + assertThat(preference.isVisible).isTrue() + } + + @Test + fun updateChildVisible_singleChildNotVisible_categoryNotVisible() { + controller.updateChildVisible(CHILD_A_KEY, false) + + assertThat(preference.isVisible).isFalse() + } + + @Test + fun updateChildVisible_oneChildVisible_categoryVisible() { + controller.updateChildVisible(CHILD_A_KEY, true) + controller.updateChildVisible(CHILD_B_KEY, false) + + assertThat(preference.isVisible).isTrue() + } + + @Test + fun updateChildVisible_nonChildNotVisible_categoryNotVisible() { + controller.updateChildVisible(CHILD_A_KEY, false) + controller.updateChildVisible(CHILD_B_KEY, false) + + assertThat(preference.isVisible).isFalse() + } + + private companion object { + const val TEST_KEY = "test_key" + const val CHILD_A_KEY = "a" + const val CHILD_B_KEY = "b" + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt new file mode 100644 index 00000000000..fc53049ee2a --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2023 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.ComponentName +import android.content.Context +import android.content.Intent +import android.provider.Settings +import android.telecom.PhoneAccountHandle +import android.telecom.TelecomManager +import android.telephony.TelephonyManager +import android.telephony.ims.ImsMmTelManager +import androidx.lifecycle.testing.TestLifecycleOwner +import androidx.preference.Preference +import androidx.preference.PreferenceManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.network.telephony.ims.ImsMmTelRepository +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 WifiCallingPreferenceControllerTest { + private val mockTelecomManager = mock() + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(TelecomManager::class.java) } doReturn mockTelecomManager + } + + private val preferenceIntent = Intent() + + private val preference = Preference(context).apply { + key = TEST_KEY + intent = preferenceIntent + } + private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context) + + private var callState = TelephonyManager.CALL_STATE_IDLE + + private object FakeImsMmTelRepository : ImsMmTelRepository { + var wiFiMode = ImsMmTelManager.WIFI_MODE_UNKNOWN + override fun getWiFiCallingMode() = wiFiMode + } + + private val callingPreferenceCategoryController = + CallingPreferenceCategoryController(context, "calling_category") + + private val controller = WifiCallingPreferenceController( + context = context, + key = TEST_KEY, + callStateFlowFactory = { flowOf(callState) }, + imsMmTelRepositoryFactory = { FakeImsMmTelRepository }, + ).init(subId = SUB_ID, callingPreferenceCategoryController) + + @Before + fun setUp() { + preferenceScreen.addPreference(preference) + controller.displayPreference(preferenceScreen) + } + + @Test + fun summary_noSimCallManager_setCorrectSummary() = runBlocking { + mockTelecomManager.stub { + on { getSimCallManagerForSubscription(SUB_ID) } doReturn null + } + FakeImsMmTelRepository.wiFiMode = ImsMmTelManager.WIFI_MODE_WIFI_ONLY + + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + + assertThat(preference.summary) + .isEqualTo(context.getString(com.android.internal.R.string.wfc_mode_wifi_only_summary)) + } + + @Test + fun summary_hasSimCallManager_summaryIsNull() = runBlocking { + mockTelecomManager.stub { + on { getSimCallManagerForSubscription(SUB_ID) } doReturn + PhoneAccountHandle(ComponentName("", ""), "") + } + + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + + assertThat(preference.summary).isNull() + } + + @Test + fun isEnabled_callIdle_enabled() = runBlocking { + callState = TelephonyManager.CALL_STATE_IDLE + + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + + assertThat(preference.isEnabled).isTrue() + } + + @Test + fun isEnabled_notCallIdle_disabled() = runBlocking { + callState = TelephonyManager.CALL_STATE_RINGING + + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + + assertThat(preference.isEnabled).isFalse() + } + + @Test + fun displayPreference_setsSubscriptionIdOnIntent() = runBlocking { + assertThat(preference.intent!!.getIntExtra(Settings.EXTRA_SUB_ID, 0)).isEqualTo(SUB_ID) + } + + private companion object { + const val TEST_KEY = "test_key" + const val SUB_ID = 2 + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt new file mode 100644 index 00000000000..eba44eddcfa --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2023 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.ims + +import android.content.Context +import android.telephony.CarrierConfigManager +import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL +import android.telephony.TelephonyManager +import android.telephony.ims.ImsMmTelManager +import androidx.core.os.persistableBundleOf +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +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 ImsMmTelRepositoryTest { + private val mockTelephonyManager = mock { + on { createForSubscriptionId(SUB_ID) } doReturn mock + } + + private val mockCarrierConfigManager = mock() + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager + } + + private val mockImsMmTelManager = mock { + on { isVoWiFiSettingEnabled } doReturn true + on { getVoWiFiRoamingModeSetting() } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED + on { getVoWiFiModeSetting() } doReturn ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED + } + + private val repository = ImsMmTelRepositoryImpl(context, SUB_ID, mockImsMmTelManager) + + @Test + fun getWiFiCallingMode_voWiFiSettingNotEnabled_returnUnknown() { + mockImsMmTelManager.stub { + on { isVoWiFiSettingEnabled } doReturn false + } + + val wiFiCallingMode = repository.getWiFiCallingMode() + + assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_UNKNOWN) + } + + @Test + fun getWiFiCallingMode_roamingAndNotUseWfcHomeModeForRoaming_returnRoamingSetting() { + mockTelephonyManager.stub { + on { isNetworkRoaming } doReturn true + } + mockUseWfcHomeModeForRoaming(false) + + val wiFiCallingMode = repository.getWiFiCallingMode() + + assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiRoamingModeSetting()) + } + + @Test + fun getWiFiCallingMode_roamingAndUseWfcHomeModeForRoaming_returnHomeSetting() { + mockTelephonyManager.stub { + on { isNetworkRoaming } doReturn true + } + mockUseWfcHomeModeForRoaming(true) + + val wiFiCallingMode = repository.getWiFiCallingMode() + + assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiModeSetting()) + } + + @Test + fun getWiFiCallingMode_notRoaming_returnHomeSetting() { + mockTelephonyManager.stub { + on { isNetworkRoaming } doReturn false + } + + val wiFiCallingMode = repository.getWiFiCallingMode() + + assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiModeSetting()) + } + + private fun mockUseWfcHomeModeForRoaming(config: Boolean) { + mockCarrierConfigManager.stub { + on { + getConfigForSubId(SUB_ID, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL) + } doReturn persistableBundleOf( + KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL to config, + ) + } + } + + private companion object { + const val SUB_ID = 1 + } +} diff --git a/tests/unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.java deleted file mode 100644 index 58275169f85..00000000000 --- a/tests/unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * 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.settings.network.telephony; - -import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.Intent; -import android.os.PersistableBundle; -import android.provider.Settings; -import android.telecom.PhoneAccountHandle; -import android.telephony.CarrierConfigManager; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.telephony.ims.ImsMmTelManager; - -import androidx.preference.Preference; -import androidx.preference.PreferenceManager; -import androidx.preference.PreferenceScreen; -import androidx.test.annotation.UiThreadTest; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.android.internal.R; -import com.android.settings.core.BasePreferenceController; -import com.android.settings.network.ims.MockWifiCallingQueryImsState; -import com.android.settings.network.ims.WifiCallingQueryImsState; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@RunWith(AndroidJUnit4.class) -public class WifiCallingPreferenceControllerTest { - private static final int SUB_ID = 2; - @Mock - private SubscriptionManager mSubscriptionManager; - @Mock - private CarrierConfigManager mCarrierConfigManager; - @Mock - private TelephonyManager mTelephonyManager; - @Mock - private ImsMmTelManager mImsMmTelManager; - - private PreferenceScreen mScreen; - private PreferenceManager mPreferenceManager; - - private MockWifiCallingQueryImsState mQueryImsState; - - private TestWifiCallingPreferenceController mController; - private Preference mPreference; - private Context mContext; - private PersistableBundle mCarrierConfig; - - @Before - @UiThreadTest - public void setUp() { - MockitoAnnotations.initMocks(this); - - mContext = spy(ApplicationProvider.getApplicationContext()); - when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); - - mQueryImsState = new MockWifiCallingQueryImsState(mContext, SUB_ID); - mQueryImsState.setIsEnabledByUser(true); - mQueryImsState.setIsProvisionedOnDevice(true); - - mController = new TestWifiCallingPreferenceController(mContext, "wifi_calling"); - mController.mCarrierConfigManager = mCarrierConfigManager; - mController.init(SUB_ID); - mController.mCallState = TelephonyManager.CALL_STATE_IDLE; - mCarrierConfig = new PersistableBundle(); - when(mCarrierConfigManager.getConfigForSubId(SUB_ID)).thenReturn(mCarrierConfig); - - mPreferenceManager = new PreferenceManager(mContext); - mScreen = mPreferenceManager.createPreferenceScreen(mContext); - mPreference = new Preference(mContext); - mPreference.setKey(mController.getPreferenceKey()); - mScreen.addPreference(mPreference); - } - - @Test - @UiThreadTest - public void updateState_noSimCallManager_setCorrectSummary() { - mController.mSimCallManager = null; - mQueryImsState.setIsEnabledByUser(true); - when(mImsMmTelManager.getVoWiFiRoamingModeSetting()).thenReturn( - ImsMmTelManager.WIFI_MODE_WIFI_ONLY); - when(mImsMmTelManager.getVoWiFiModeSetting()).thenReturn( - ImsMmTelManager.WIFI_MODE_WIFI_ONLY); - - mController.updateState(mPreference); - - assertThat(mPreference.getSummary()).isEqualTo( - mContext.getString(com.android.internal.R.string.wfc_mode_wifi_only_summary)); - } - - @Test - @UiThreadTest - public void updateState_notCallIdle_disable() { - mController.mCallState = TelephonyManager.CALL_STATE_RINGING; - - mController.updateState(mPreference); - - assertThat(mPreference.isEnabled()).isFalse(); - } - - @Test - @UiThreadTest - public void updateState_invalidPhoneAccountHandle_shouldNotCrash() { - mController.mSimCallManager = new PhoneAccountHandle(null /* invalid */, ""); - - //Should not crash - mController.updateState(mPreference); - } - - @Test - @UiThreadTest - public void updateState_wfcNonRoamingByConfig() { - assertNull(mController.mSimCallManager); - mCarrierConfig.putBoolean( - CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL, true); - mController.init(SUB_ID); - - when(mImsMmTelManager.getVoWiFiRoamingModeSetting()).thenReturn( - ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED); - when(mImsMmTelManager.getVoWiFiModeSetting()).thenReturn( - ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED); - mQueryImsState.setIsEnabledByUser(true); - when(mTelephonyManager.isNetworkRoaming()).thenReturn(true); - - mController.updateState(mPreference); - assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getString(R.string.wfc_mode_cellular_preferred_summary)); - } - - @Test - @UiThreadTest - public void updateState_wfcRoamingByConfig() { - assertNull(mController.mSimCallManager); - // useWfcHomeModeForRoaming is false by default. In order to check wfc in roaming mode. We - // need the device roaming, and not using home mode in roaming network. - when(mImsMmTelManager.getVoWiFiRoamingModeSetting()).thenReturn( - ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED); - when(mImsMmTelManager.getVoWiFiModeSetting()).thenReturn( - ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED); - mQueryImsState.setIsEnabledByUser(true); - when(mTelephonyManager.isNetworkRoaming()).thenReturn(true); - - mController.updateState(mPreference); - assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getString(R.string.wfc_mode_wifi_preferred_summary)); - } - - @Test - @UiThreadTest - public void displayPreference_notAvailable_setPreferenceInvisible() { - mController.init(SubscriptionManager.INVALID_SUBSCRIPTION_ID); - when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(null); - - mController.displayPreference(mScreen); - - assertThat(mController.getPreferenceKey()).isEqualTo("wifi_calling"); - assertThat(mScreen.findPreference(mController.getPreferenceKey()).isVisible()).isFalse(); - } - - @Test - @Ignore - public void displayPreference_available_setsSubscriptionIdOnIntent() { - final Intent intent = new Intent(); - mPreference.setIntent(intent); - mController.displayPreference(mScreen); - assertThat(intent.getIntExtra(Settings.EXTRA_SUB_ID, - SubscriptionManager.INVALID_SUBSCRIPTION_ID)).isEqualTo(SUB_ID); - } - - @Test - @UiThreadTest - public void getAvailabilityStatus_noWiFiCalling_shouldReturnUnsupported() { - mController.init(SubscriptionManager.INVALID_SUBSCRIPTION_ID); - when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(null); - - assertThat(mController.getAvailabilityStatus()).isEqualTo( - BasePreferenceController.UNSUPPORTED_ON_DEVICE); - } - - private class TestWifiCallingPreferenceController extends WifiCallingPreferenceController { - TestWifiCallingPreferenceController(Context context, String preferenceKey) { - super(context, preferenceKey); - } - - @Override - protected ImsMmTelManager getImsMmTelManager(int subId) { - return mImsMmTelManager; - } - - @Override - protected TelephonyManager getTelephonyManager(Context context, int subId) { - return mTelephonyManager; - } - - @Override - protected WifiCallingQueryImsState queryImsState(int subId) { - return mQueryImsState; - } - } -}