diff --git a/Android.bp b/Android.bp index 25fc8f56519..8b903ba91be 100644 --- a/Android.bp +++ b/Android.bp @@ -111,7 +111,10 @@ android_library { "keyboard_flags_lib", ], - plugins: ["androidx.room_room-compiler-plugin"], + plugins: [ + "SettingsLibMetadata-processor", + "androidx.room_room-compiler-plugin", + ], errorprone: { extra_check_modules: ["//external/nullaway:nullaway_plugin"], diff --git a/src/com/android/settings/SettingsApplication.java b/src/com/android/settings/SettingsApplication.java index d208fdf09e1..7e008e43667 100644 --- a/src/com/android/settings/SettingsApplication.java +++ b/src/com/android/settings/SettingsApplication.java @@ -16,6 +16,8 @@ package com.android.settings; +import static com.android.settingslib.flags.Flags.settingsCatalyst; + import android.app.Application; import android.content.Context; import android.content.pm.PackageManager; @@ -42,13 +44,20 @@ import com.android.settings.overlay.FeatureFactoryImpl; import com.android.settings.spa.SettingsSpaEnvironment; import com.android.settingslib.applications.AppIconCacheManager; import com.android.settingslib.datastore.BackupRestoreStorageManager; +import com.android.settingslib.metadata.PreferenceScreenMetadata; +import com.android.settingslib.metadata.PreferenceScreenRegistry; +import com.android.settingslib.metadata.ProvidePreferenceScreenOptions; import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory; import com.google.android.setupcompat.util.WizardManagerHelper; import java.lang.ref.WeakReference; +import java.util.List; /** Settings application which sets up activity embedding rules for the large screen device. */ +@ProvidePreferenceScreenOptions( + codegenCollector = "com.android.settings/PreferenceScreenCollector/get" +) public class SettingsApplication extends Application { private WeakReference mHomeActivity = new WeakReference<>(null); @@ -64,6 +73,11 @@ public class SettingsApplication extends Application { public void onCreate() { super.onCreate(); + if (settingsCatalyst()) { + PreferenceScreenRegistry.INSTANCE.setPreferenceScreensSupplier( + this::getPreferenceScreens); + } + BackupRestoreStorageManager.getInstance(this) .add( new BatterySettingsStorage(this), @@ -90,6 +104,13 @@ public class SettingsApplication extends Application { registerActivityLifecycleCallbacks(new DeveloperOptionsActivityLifecycle()); } + /** Returns the screens using metadata. */ + protected List getPreferenceScreens() { + // PreferenceScreenCollector is generated by annotation processor from classes annotated + // with @ProvidePreferenceScreen + return PreferenceScreenCollector.get(this); + } + @Override public void onTerminate() { BackupRestoreStorageManager.getInstance(this).removeAll(); diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingRetryDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingRetryDialogFragment.java new file mode 100644 index 00000000000..822e0537149 --- /dev/null +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingRetryDialogFragment.java @@ -0,0 +1,81 @@ +/* + * 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.connecteddevice.audiosharing; + +import android.app.Dialog; +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settingslib.bluetooth.BluetoothUtils; + +public class AudioSharingRetryDialogFragment extends InstrumentedDialogFragment { + private static final String TAG = "AudioSharingRetryDialog"; + + @Override + public int getMetricsCategory() { + // TODO: add metrics + return 0; + } + + /** + * Display the {@link AudioSharingRetryDialogFragment} dialog. + * + * @param host The Fragment this dialog will be hosted. + */ + public static void show(@Nullable Fragment host) { + if (host == null || !BluetoothUtils.isAudioSharingEnabled()) return; + final FragmentManager manager; + try { + manager = host.getChildFragmentManager(); + } catch (IllegalStateException e) { + Log.d(TAG, "Fail to show dialog: " + e.getMessage()); + return; + } + AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG); + if (dialog != null) { + Log.d(TAG, "Dialog is showing, return."); + return; + } + Log.d(TAG, "Show up the retry dialog."); + AudioSharingRetryDialogFragment dialogFrag = new AudioSharingRetryDialogFragment(); + dialogFrag.show(manager, TAG); + } + + @Override + @NonNull + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + // TODO: put strings to res till they are finalized + AlertDialog dialog = + AudioSharingDialogFactory.newBuilder(getActivity()) + .setTitle("Couldn't share audio") + .setTitleIcon(com.android.settings.R.drawable.ic_warning_24dp) + .setIsCustomBodyEnabled(true) + .setCustomMessage("Something went wrong. Please try again.") + .setPositiveButton(com.android.settings.R.string.okay, (d, w) -> { + }) + .build(); + dialog.setCanceledOnTouchOutside(true); + return dialog; + } +} diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java index 6501084ab7e..2040694c935 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java @@ -25,7 +25,6 @@ import android.bluetooth.BluetoothLeBroadcast; import android.bluetooth.BluetoothLeBroadcastAssistant; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; -import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -377,9 +376,9 @@ public class AudioSharingSwitchBarController extends BasePreferenceController // FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST is always true in // prod. We can turn off the flag for debug purpose. if (FeatureFlagUtils.isEnabled( - mContext, - FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST) - && mAssistant.getAllConnectedDevices().isEmpty()) { + mContext, + FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST) + && hasEmptyConnectedSink()) { // Pop up dialog to ask users to connect at least one lea buds before audio sharing. AudioSharingUtils.postOnMainThread( mContext, @@ -435,8 +434,12 @@ public class AudioSharingSwitchBarController extends BasePreferenceController } @Override - public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { - if (activeDevice != null && bluetoothProfile == BluetoothProfile.LE_AUDIO) { + public void onActiveDeviceChanged(@Nullable CachedBluetoothDevice activeDevice, + int bluetoothProfile) { + if (activeDevice != null) { + Log.d(TAG, "onActiveDeviceChanged: device = " + + activeDevice.getDevice().getAnonymizedAddress() + + ", profile = " + bluetoothProfile); updateSwitch(); } } @@ -536,13 +539,19 @@ public class AudioSharingSwitchBarController extends BasePreferenceController boolean isBroadcasting = BluetoothUtils.isBroadcasting(mBtManager); boolean hasActiveDevice = AudioSharingUtils.hasActiveConnectedLeadDevice(mBtManager); + boolean hasEmptyConnectedDevice = hasEmptyConnectedSink(); boolean isStateReady = isBluetoothOn() && AudioSharingUtils.isAudioSharingProfileReady( - mProfileManager) + mProfileManager) + && (isBroadcasting + // Always enable toggle when no connected sink. We have + // dialog to guide users to connect compatible devices + // for audio sharing. + || hasEmptyConnectedDevice // Disable toggle till device gets active after // broadcast ends. - && (isBroadcasting || hasActiveDevice); + || hasActiveDevice); AudioSharingUtils.postOnMainThread( mContext, () -> { @@ -566,6 +575,10 @@ public class AudioSharingSwitchBarController extends BasePreferenceController return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled(); } + private boolean hasEmptyConnectedSink() { + return mAssistant != null && mAssistant.getAllConnectedDevices().isEmpty(); + } + private void handleOnBroadcastReady() { Pair[] eventData = AudioSharingUtils.buildAudioSharingDialogEventData( diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java index 43256bcdee4..a662809fb78 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java @@ -215,15 +215,12 @@ public class AudioSharingUtils { @Nullable LocalBluetoothManager localBtManager) { CachedBluetoothDeviceManager deviceManager = localBtManager == null ? null : localBtManager.getCachedDeviceManager(); - Map> groupedConnectedDevices = - fetchConnectedDevicesByGroupId(localBtManager); - for (List devices : groupedConnectedDevices.values()) { - CachedBluetoothDevice leadDevice = getLeadDevice(deviceManager, devices); - if (isActiveLeAudioDevice(leadDevice)) { - return true; - } + if (deviceManager == null) { + Log.d(TAG, "hasActiveConnectedLeadDevice return false due to null device manager."); + return false; } - return false; + return deviceManager.getCachedDevicesCopy().stream().anyMatch( + BluetoothUtils::isActiveMediaDevice); } /** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */ diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index 0808da1b4a8..09dd1dd480d 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -15,6 +15,8 @@ */ package com.android.settings.dashboard; +import static com.android.settingslib.flags.Flags.settingsCatalyst; + import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.ContentResolver; @@ -53,6 +55,7 @@ import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.Tile; +import com.android.settingslib.metadata.PreferenceScreenRegistry; import com.android.settingslib.search.Indexable; import java.util.ArrayList; @@ -97,30 +100,35 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment R.array.config_suppress_injected_tile_keys)); mDashboardFeatureProvider = FeatureFactory.getFeatureFactory().getDashboardFeatureProvider(); - // Load preference controllers from code - final List controllersFromCode = - createPreferenceControllers(context); - // Load preference controllers from xml definition - final List controllersFromXml = PreferenceControllerListHelper - .getPreferenceControllersFromXml(context, getPreferenceScreenResId()); - // Filter xml-based controllers in case a similar controller is created from code already. - final List uniqueControllerFromXml = - PreferenceControllerListHelper.filterControllers( - controllersFromXml, controllersFromCode); - // Add unique controllers to list. - if (controllersFromCode != null) { - mControllers.addAll(controllersFromCode); - } - mControllers.addAll(uniqueControllerFromXml); + if (!usePreferenceScreenMetadata() || PreferenceScreenRegistry.INSTANCE.get( + getPreferenceScreenBindingKey(context)) == null) { + // Load preference controllers from code + final List controllersFromCode = + createPreferenceControllers(context); + // Load preference controllers from xml definition + final List controllersFromXml = PreferenceControllerListHelper + .getPreferenceControllersFromXml(context, getPreferenceScreenResId()); + // Filter xml-based controllers in case a similar controller is created from code + // already. + final List uniqueControllerFromXml = + PreferenceControllerListHelper.filterControllers( + controllersFromXml, controllersFromCode); - // And wire up with lifecycle. - final Lifecycle lifecycle = getSettingsLifecycle(); - uniqueControllerFromXml.forEach(controller -> { - if (controller instanceof LifecycleObserver) { - lifecycle.addObserver((LifecycleObserver) controller); + // Add unique controllers to list. + if (controllersFromCode != null) { + mControllers.addAll(controllersFromCode); } - }); + mControllers.addAll(uniqueControllerFromXml); + + // And wire up with lifecycle. + final Lifecycle lifecycle = getSettingsLifecycle(); + uniqueControllerFromXml.forEach(controller -> { + if (controller instanceof LifecycleObserver) { + lifecycle.addObserver((LifecycleObserver) controller); + } + }); + } // Set metrics category for BasePreferenceController. final int metricCategory = getMetricsCategory(); @@ -272,6 +280,11 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment } } + @Override + protected final int getPreferenceScreenResId(@NonNull Context context) { + return getPreferenceScreenResId(); + } + @Override protected abstract int getPreferenceScreenResId(); @@ -364,12 +377,32 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment if (resId <= 0) { return; } - addPreferencesFromResource(resId); - final PreferenceScreen screen = getPreferenceScreen(); + PreferenceScreen screen; + if (usePreferenceScreenMetadata()) { + screen = createPreferenceScreen(); + setPreferenceScreen(screen); + requireActivity().setTitle(screen.getTitle()); + } else { + addPreferencesFromResource(resId); + screen = getPreferenceScreen(); + } screen.setOnExpandButtonClickListener(this); displayResourceTilesToScreen(screen); } + @Override + protected final boolean usePreferenceScreenMetadata() { + return settingsCatalyst() && enableCatalyst(); + } + + /** + * Returns if settings catalyst should be enabled (e.g. check trunk stable flag) on current + * screen. + */ + protected boolean enableCatalyst() { + return false; + } + /** * Perform {@link AbstractPreferenceController#displayPreference(PreferenceScreen)} * on all {@link AbstractPreferenceController}s. diff --git a/src/com/android/settings/network/telephony/CarrierConfigRepository.kt b/src/com/android/settings/network/telephony/CarrierConfigRepository.kt index 88525402e34..99683a8671a 100644 --- a/src/com/android/settings/network/telephony/CarrierConfigRepository.kt +++ b/src/com/android/settings/network/telephony/CarrierConfigRepository.kt @@ -17,6 +17,7 @@ package com.android.settings.network.telephony import android.content.Context +import android.os.Build import android.os.PersistableBundle import android.telephony.CarrierConfigManager import android.telephony.SubscriptionManager @@ -35,7 +36,8 @@ class CarrierConfigRepository(private val context: Context) { private enum class KeyType { BOOLEAN, INT, - STRING + INT_ARRAY, + STRING, } interface CarrierConfigAccessor { @@ -43,17 +45,20 @@ class CarrierConfigRepository(private val context: Context) { fun getInt(key: String): Int + fun getIntArray(key: String): IntArray? + fun getString(key: String): String? } private class Accessor(private val cache: ConfigCache) : CarrierConfigAccessor { private val keysToRetrieve = mutableMapOf() + private var isKeysToRetrieveFrozen = false override fun getBoolean(key: String): Boolean { checkBooleanKey(key) val value = cache[key] return if (value == null) { - keysToRetrieve += key to KeyType.BOOLEAN + addKeyToRetrieve(key, KeyType.BOOLEAN) DefaultConfig.getBoolean(key) } else { check(value is BooleanConfigValue) { "Boolean value type wrong" } @@ -65,7 +70,7 @@ class CarrierConfigRepository(private val context: Context) { check(key.endsWith("_int")) { "Int key should ends with _int" } val value = cache[key] return if (value == null) { - keysToRetrieve += key to KeyType.INT + addKeyToRetrieve(key, KeyType.INT) DefaultConfig.getInt(key) } else { check(value is IntConfigValue) { "Int value type wrong" } @@ -73,11 +78,23 @@ class CarrierConfigRepository(private val context: Context) { } } + override fun getIntArray(key: String): IntArray? { + checkIntArrayKey(key) + val value = cache[key] + return if (value == null) { + addKeyToRetrieve(key, KeyType.INT_ARRAY) + DefaultConfig.getIntArray(key) + } else { + check(value is IntArrayConfigValue) { "Int array value type wrong" } + value.value + } + } + override fun getString(key: String): String? { check(key.endsWith("_string")) { "String key should ends with _string" } val value = cache[key] return if (value == null) { - keysToRetrieve += key to KeyType.STRING + addKeyToRetrieve(key, KeyType.STRING) DefaultConfig.getString(key) } else { check(value is StringConfigValue) { "String value type wrong" } @@ -85,20 +102,35 @@ class CarrierConfigRepository(private val context: Context) { } } - fun getKeysToRetrieve(): Map = keysToRetrieve + private fun addKeyToRetrieve(key: String, type: KeyType) { + if (keysToRetrieve.put(key, type) == null && Build.IS_DEBUGGABLE) { + check(!isKeysToRetrieveFrozen) { "implement error for key $key" } + } + } + + /** + * Gets the keys to retrieve. + * + * After this function is called, the keys to retrieve is frozen. + */ + fun getAndFrozeKeysToRetrieve(): Map { + isKeysToRetrieveFrozen = true + return keysToRetrieve + } } /** * Gets the configuration values for the given [subId]. * * Configuration values could be accessed in [block]. Note: [block] could be called multiple - * times, so it should be pure function without side effort. + * times, so it should be pure function without side effort. Please also make sure every key is + * retrieved every time, for example, we need avoid expression shortcut. */ fun transformConfig(subId: Int, block: CarrierConfigAccessor.() -> T): T { val perSubCache = getPerSubCache(subId) val accessor = Accessor(perSubCache) val result = accessor.block() - val keysToRetrieve = accessor.getKeysToRetrieve() + val keysToRetrieve = accessor.getAndFrozeKeysToRetrieve() // If all keys found in the first pass, no need to collect again if (keysToRetrieve.isEmpty()) return result @@ -113,6 +145,10 @@ class CarrierConfigRepository(private val context: Context) { /** Gets the configuration int for the given [subId] and [key]. */ fun getInt(subId: Int, key: String): Int = transformConfig(subId) { getInt(key) } + /** Gets the configuration int array for the given [subId] and [key]. */ + fun getIntArray(subId: Int, key: String): IntArray? = + transformConfig(subId) { getIntArray(key) } + /** Gets the configuration string for the given [subId] and [key]. */ fun getString(subId: Int, key: String): String? = transformConfig(subId) { getString(key) } @@ -122,6 +158,7 @@ class CarrierConfigRepository(private val context: Context) { when (type) { KeyType.BOOLEAN -> this[key] = BooleanConfigValue(config.getBoolean(key)) KeyType.INT -> this[key] = IntConfigValue(config.getInt(key)) + KeyType.INT_ARRAY -> this[key] = IntArrayConfigValue(config.getIntArray(key)) KeyType.STRING -> this[key] = StringConfigValue(config.getString(key)) } } @@ -195,6 +232,10 @@ class CarrierConfigRepository(private val context: Context) { } } + private fun checkIntArrayKey(key: String) { + check(key.endsWith("_int_array")) { "Int array key should ends with _int_array" } + } + @VisibleForTesting fun setBooleanForTest(subId: Int, key: String, value: Boolean) { checkBooleanKey(key) @@ -207,6 +248,12 @@ class CarrierConfigRepository(private val context: Context) { getPerSubCache(subId)[key] = IntConfigValue(value) } + @VisibleForTesting + fun setIntArrayForTest(subId: Int, key: String, value: IntArray?) { + checkIntArrayKey(key) + getPerSubCache(subId)[key] = IntArrayConfigValue(value) + } + @VisibleForTesting fun setStringForTest(subId: Int, key: String, value: String?) { check(key.endsWith("_string")) { "String key should ends with _string" } @@ -221,6 +268,8 @@ private data class BooleanConfigValue(val value: Boolean) : ConfigValue private data class IntConfigValue(val value: Int) : ConfigValue +private class IntArrayConfigValue(val value: IntArray?) : ConfigValue + private data class StringConfigValue(val value: String?) : ConfigValue private typealias ConfigCache = ConcurrentHashMap diff --git a/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java b/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java index 1a71e5aad45..d1988c4a3b7 100644 --- a/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java +++ b/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java @@ -34,7 +34,6 @@ import androidx.preference.PreferenceScreen; import androidx.preference.TwoStatePreference; import com.android.internal.telephony.flags.Flags; -import com.android.internal.telephony.util.ArrayUtils; import com.android.settings.R; import com.android.settings.network.ims.VolteQueryImsState; import com.android.settingslib.core.lifecycle.LifecycleObserver; @@ -56,8 +55,6 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc Preference mPreference; private PhoneCallStateTelephonyCallback mTelephonyCallback; private boolean mShow5gLimitedDialog; - boolean mIsNrEnabledFromCarrierConfig; - private boolean mHas5gCapability; private Integer mCallState; private final List m4gLteListeners; @@ -94,9 +91,6 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc mShow5gLimitedDialog = carrierConfig.getBoolean( CarrierConfigManager.KEY_VOLTE_5G_LIMITED_ALERT_DIALOG_BOOL); - int[] nrAvailabilities = carrierConfig.getIntArray( - CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY); - mIsNrEnabledFromCarrierConfig = !ArrayUtils.isEmpty(nrAvailabilities); return this; } @@ -247,10 +241,6 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc } mTelephonyManager.registerTelephonyCallback( mContext.getMainExecutor(), mTelephonyCallback); - - final long supportedRadioBitmask = mTelephonyManager.getSupportedRadioAccessFamily(); - mHas5gCapability = - (supportedRadioBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_NR) > 0; } public void unregister() { @@ -269,8 +259,7 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc } private boolean isDialogNeeded() { - Log.d(TAG, "Has5gCapability:" + mHas5gCapability); - return mShow5gLimitedDialog && mHas5gCapability && mIsNrEnabledFromCarrierConfig; + return mShow5gLimitedDialog && new NrRepository(mContext).isNrAvailable(mSubId); } private void show5gLimitedDialog(ImsMmTelManager imsMmTelManager) { diff --git a/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.kt b/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.kt index 0d8766e4df0..b79e818c20e 100644 --- a/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.kt +++ b/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.kt @@ -25,31 +25,28 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settings.R -import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchResult import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchItem +import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchResult import com.android.settings.spa.preference.ComposePreferenceController import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch -/** - * Preference controller for "Voice over NR". - */ -class NrAdvancedCallingPreferenceController @JvmOverloads constructor( +/** Preference controller for "Voice over NR". */ +class NrAdvancedCallingPreferenceController +@JvmOverloads +constructor( context: Context, key: String, - private val callStateRepository : CallStateRepository = CallStateRepository(context), + private val voNrRepository: VoNrRepository = VoNrRepository(context), + private val callStateRepository: CallStateRepository = CallStateRepository(context), ) : ComposePreferenceController(context, key) { private var subId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID - private var repository: VoNrRepository? = null private val searchItem = NrAdvancedCallingSearchItem(context) /** Initial this PreferenceController. */ - @JvmOverloads - fun init(subId: Int, repository: VoNrRepository = VoNrRepository(mContext, subId)) { + fun init(subId: Int) { this.subId = subId - this.repository = repository } override fun getAvailabilityStatus() = @@ -58,30 +55,32 @@ class NrAdvancedCallingPreferenceController @JvmOverloads constructor( @Composable override fun Content() { val summary = stringResource(R.string.nr_advanced_calling_summary) - val isInCall by remember { callStateRepository.isInCallFlow() } - .collectAsStateWithLifecycle(initialValue = false) - val isEnabled by remember { - repository?.isVoNrEnabledFlow() ?: flowOf(false) - }.collectAsStateWithLifecycle(initialValue = false) + val isInCall by + remember { callStateRepository.isInCallFlow() } + .collectAsStateWithLifecycle(initialValue = false) + val isVoNrEnabled by + remember { voNrRepository.isVoNrEnabledFlow(subId) } + .collectAsStateWithLifecycle(initialValue = false) val coroutineScope = rememberCoroutineScope() - SwitchPreference(object : SwitchPreferenceModel { - override val title = stringResource(R.string.nr_advanced_calling_title) - override val summary = { summary } - override val changeable = { !isInCall } - override val checked = { isEnabled } - override val onCheckedChange: (Boolean) -> Unit = { newChecked -> - coroutineScope.launch { - repository?.setVoNrEnabled(newChecked) + SwitchPreference( + object : SwitchPreferenceModel { + override val title = stringResource(R.string.nr_advanced_calling_title) + override val summary = { summary } + override val changeable = { !isInCall } + override val checked = { isVoNrEnabled } + override val onCheckedChange: (Boolean) -> Unit = { newChecked -> + coroutineScope.launch { voNrRepository.setVoNrEnabled(subId, newChecked) } } } - }) + ) } companion object { class NrAdvancedCallingSearchItem(private val context: Context) : MobileNetworkSettingsSearchItem { + private val voNrRepository = VoNrRepository(context) - fun isAvailable(subId: Int): Boolean = VoNrRepository(context, subId).isVoNrAvailable() + fun isAvailable(subId: Int): Boolean = voNrRepository.isVoNrAvailable(subId) override fun getSearchResult(subId: Int): MobileNetworkSettingsSearchResult? { if (!isAvailable(subId)) return null diff --git a/src/com/android/settings/network/telephony/NrRepository.kt b/src/com/android/settings/network/telephony/NrRepository.kt new file mode 100644 index 00000000000..e6247fd2655 --- /dev/null +++ b/src/com/android/settings/network/telephony/NrRepository.kt @@ -0,0 +1,50 @@ +/* + * 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.CarrierConfigManager +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.util.Log + +class NrRepository(private val context: Context) { + private val carrierConfigRepository = CarrierConfigRepository(context) + + fun isNrAvailable(subId: Int): Boolean { + if (!SubscriptionManager.isValidSubscriptionId(subId) || !has5gCapability(subId)) { + return false + } + val carrierNrAvailabilities = + carrierConfigRepository.getIntArray( + subId = subId, + key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, + ) + return carrierNrAvailabilities?.isNotEmpty() ?: false + } + + private fun has5gCapability(subId: Int): Boolean { + val telephonyManager = context.telephonyManager(subId) + return (telephonyManager.supportedRadioAccessFamily and + TelephonyManager.NETWORK_TYPE_BITMASK_NR > 0) + .also { Log.d(TAG, "[$subId] has5gCapability: $it") } + } + + private companion object { + private const val TAG = "NrRepository" + } +} diff --git a/src/com/android/settings/network/telephony/VoNrRepository.kt b/src/com/android/settings/network/telephony/VoNrRepository.kt index 35af2844609..635c57223be 100644 --- a/src/com/android/settings/network/telephony/VoNrRepository.kt +++ b/src/com/android/settings/network/telephony/VoNrRepository.kt @@ -19,7 +19,6 @@ package com.android.settings.network.telephony import android.content.Context import android.telephony.CarrierConfigManager import android.telephony.SubscriptionManager -import android.telephony.TelephonyManager import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -29,43 +28,43 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext -class VoNrRepository(private val context: Context, private val subId: Int) { - private val telephonyManager = context.telephonyManager(subId) - private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!! +class VoNrRepository( + private val context: Context, + private val nrRepository: NrRepository = NrRepository(context), +) { + private val carrierConfigRepository = CarrierConfigRepository(context) - fun isVoNrAvailable(): Boolean { - if (!SubscriptionManager.isValidSubscriptionId(subId) || !has5gCapability()) return false - val carrierConfig = carrierConfigManager.safeGetConfig( - keys = listOf( - CarrierConfigManager.KEY_VONR_ENABLED_BOOL, - CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, - CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, - ), - subId = subId, - ) - return carrierConfig.getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL) && - carrierConfig.getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL) && - (carrierConfig.getIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY) - ?.isNotEmpty() ?: false) + fun isVoNrAvailable(subId: Int): Boolean { + if (!nrRepository.isNrAvailable(subId)) return false + data class Config(val isVoNrEnabled: Boolean, val isVoNrSettingVisibility: Boolean) + val carrierConfig = + carrierConfigRepository.transformConfig(subId) { + Config( + isVoNrEnabled = getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL), + isVoNrSettingVisibility = + getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL), + ) + } + return carrierConfig.isVoNrEnabled && carrierConfig.isVoNrSettingVisibility } - private fun has5gCapability() = - ((telephonyManager.supportedRadioAccessFamily and - TelephonyManager.NETWORK_TYPE_BITMASK_NR) > 0) - .also { Log.d(TAG, "[$subId] has5gCapability: $it") } - - fun isVoNrEnabledFlow(): Flow = context.subscriptionsChangedFlow() - .map { telephonyManager.isVoNrEnabled } - .conflate() - .onEach { Log.d(TAG, "[$subId] isVoNrEnabled: $it") } - .flowOn(Dispatchers.Default) - - suspend fun setVoNrEnabled(enabled: Boolean) = withContext(Dispatchers.Default) { - if (!SubscriptionManager.isValidSubscriptionId(subId)) return@withContext - val result = telephonyManager.setVoNrEnabled(enabled) - Log.d(TAG, "[$subId] setVoNrEnabled: $enabled, result: $result") + fun isVoNrEnabledFlow(subId: Int): Flow { + val telephonyManager = context.telephonyManager(subId) + return context + .subscriptionsChangedFlow() + .map { telephonyManager.isVoNrEnabled } + .conflate() + .onEach { Log.d(TAG, "[$subId] isVoNrEnabled: $it") } + .flowOn(Dispatchers.Default) } + suspend fun setVoNrEnabled(subId: Int, enabled: Boolean) = + withContext(Dispatchers.Default) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) return@withContext + val result = context.telephonyManager(subId).setVoNrEnabled(enabled) + Log.d(TAG, "[$subId] setVoNrEnabled: $enabled, result: $result") + } + private companion object { private const val TAG = "VoNrRepository" } diff --git a/src/com/android/settings/webview/WebViewAppPicker.java b/src/com/android/settings/webview/WebViewAppPicker.java index b1dfd1454f9..7fb5ebca8ce 100644 --- a/src/com/android/settings/webview/WebViewAppPicker.java +++ b/src/com/android/settings/webview/WebViewAppPicker.java @@ -26,6 +26,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; +import android.os.UserManager; import android.text.TextUtils; import android.webkit.UserPackage; @@ -149,17 +150,20 @@ public class WebViewAppPicker extends DefaultAppPickerFragment { @VisibleForTesting String getDisabledReason(WebViewUpdateServiceWrapper webviewUpdateServiceWrapper, Context context, String packageName) { + UserManager userManager = context.getSystemService(UserManager.class); List userPackages = webviewUpdateServiceWrapper.getPackageInfosAllUsers(context, packageName); for (UserPackage userPackage : userPackages) { if (!userPackage.isInstalledPackage()) { // Package uninstalled/hidden return context.getString( - R.string.webview_uninstalled_for_user, userPackage.getUserInfo().name); + R.string.webview_uninstalled_for_user, + userManager.getUserInfo(userPackage.getUser().getIdentifier()).name); } else if (!userPackage.isEnabledPackage()) { // Package disabled return context.getString( - R.string.webview_disabled_for_user, userPackage.getUserInfo().name); + R.string.webview_disabled_for_user, + userManager.getUserInfo(userPackage.getUser().getIdentifier()).name); } } return null; diff --git a/src/com/android/settings/widget/TickButtonPreference.java b/src/com/android/settings/widget/TickButtonPreference.java index b9b9b19b7d8..4778f8c8ee0 100644 --- a/src/com/android/settings/widget/TickButtonPreference.java +++ b/src/com/android/settings/widget/TickButtonPreference.java @@ -21,33 +21,30 @@ import android.util.AttributeSet; import android.view.View; import android.widget.ImageView; -import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import com.android.settings.R; +import com.android.settingslib.widget.TwoTargetPreference; /** A preference with tick icon. */ -public class TickButtonPreference extends Preference { +public class TickButtonPreference extends TwoTargetPreference { private ImageView mCheckIcon; private boolean mIsSelected = false; public TickButtonPreference(Context context) { super(context); - init(context, null); - } - - public TickButtonPreference(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs); - } - - private void init(Context context, AttributeSet attrs) { - setWidgetLayoutResource(R.layout.preference_check_icon); } @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); + View divider = + holder.findViewById( + com.android.settingslib.widget.preference.twotarget.R.id + .two_target_divider); + if (divider != null) { + divider.setVisibility(View.GONE); + } mCheckIcon = (ImageView) holder.findViewById(R.id.check_icon); setSelected(mIsSelected); } @@ -64,4 +61,10 @@ public class TickButtonPreference extends Preference { public boolean isSelected() { return mIsSelected; } + + @Override + protected int getSecondTargetResId() { + return R.layout.preference_check_icon; + } + } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingRetryDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingRetryDialogFragmentTest.java new file mode 100644 index 00000000000..4b0524b848a --- /dev/null +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingRetryDialogFragmentTest.java @@ -0,0 +1,132 @@ +/* + * 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.connecteddevice.audiosharing; + +import static com.google.common.truth.Truth.assertThat; + +import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothStatusCodes; +import android.platform.test.flag.junit.SetFlagsRule; +import android.view.View; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; + +import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; +import com.android.settingslib.flags.Flags; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.androidx.fragment.FragmentController; + +@RunWith(RobolectricTestRunner.class) +@Config( + shadows = { + ShadowAlertDialogCompat.class, + ShadowBluetoothAdapter.class, + }) +public class AudioSharingRetryDialogFragmentTest { + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private Fragment mParent; + private AudioSharingRetryDialogFragment mFragment; + + @Before + public void setUp() { + ShadowAlertDialogCompat.reset(); + ShadowBluetoothAdapter shadowBluetoothAdapter = + Shadow.extract(BluetoothAdapter.getDefaultAdapter()); + shadowBluetoothAdapter.setEnabled(true); + shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + mFragment = new AudioSharingRetryDialogFragment(); + mParent = new Fragment(); + FragmentController.setupFragment( + mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null); + } + + @After + public void tearDown() { + ShadowAlertDialogCompat.reset(); + } + + @Test + public void getMetricsCategory_correctValue() { + // TODO: update metrics id + assertThat(mFragment.getMetricsCategory()) + .isEqualTo(0); + } + + @Test + public void onCreateDialog_flagOff_dialogNotExist() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + AudioSharingRetryDialogFragment.show(mParent); + shadowMainLooper().idle(); + AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNull(); + } + + @Test + public void onCreateDialog_unattachedFragment_dialogNotExist() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + AudioSharingRetryDialogFragment.show(new Fragment()); + shadowMainLooper().idle(); + AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNull(); + } + + @Test + public void onCreateDialog_flagOn_showDialog() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + AudioSharingRetryDialogFragment.show(mParent); + shadowMainLooper().idle(); + AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNotNull(); + assertThat(dialog.isShowing()).isTrue(); + } + + @Test + public void onCreateDialog_clickOk_dialogDismiss() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + AudioSharingRetryDialogFragment.show(mParent); + shadowMainLooper().idle(); + AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNotNull(); + View btnView = dialog.findViewById(android.R.id.button1); + assertThat(btnView).isNotNull(); + btnView.performClick(); + shadowMainLooper().idle(); + assertThat(dialog.isShowing()).isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java index d1533c9703b..354c5c7f08e 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java @@ -111,10 +111,10 @@ import java.util.concurrent.Executor; @RunWith(RobolectricTestRunner.class) @Config( shadows = { - ShadowBluetoothAdapter.class, - ShadowBluetoothUtils.class, - ShadowThreadUtils.class, - ShadowAlertDialogCompat.class + ShadowBluetoothAdapter.class, + ShadowBluetoothUtils.class, + ShadowThreadUtils.class, + ShadowAlertDialogCompat.class }) public class AudioSharingSwitchBarControllerTest { private static final String TEST_DEVICE_NAME1 = "test1"; @@ -123,12 +123,13 @@ public class AudioSharingSwitchBarControllerTest { private static final String TEST_DEVICE_ANONYMIZED_ADDR2 = "XX:XX:02"; private static final int TEST_DEVICE_GROUP_ID1 = 1; private static final int TEST_DEVICE_GROUP_ID2 = 2; - private static final Correspondence TAG_EQUALS = + private static final Correspondence CLAZZNAME_EQUALS = Correspondence.from( - (Fragment fragment, String tag) -> + (Fragment fragment, String clazzName) -> fragment instanceof DialogFragment - && ((DialogFragment) fragment).getTag() != null - && ((DialogFragment) fragment).getTag().equals(tag), + && ((DialogFragment) fragment).getClass().getName() != null + && ((DialogFragment) fragment).getClass().getName().equals( + clazzName), "is equal to"); @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -343,6 +344,18 @@ public class AudioSharingSwitchBarControllerTest { assertThat(mSwitchBar.isEnabled()).isTrue(); } + @Test + public void onStart_flagOn_updateSwitch() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + when(mBroadcast.isEnabled(null)).thenReturn(false); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of()); + mController.onStart(mLifecycleOwner); + shadowOf(Looper.getMainLooper()).idle(); + + assertThat(mSwitchBar.isChecked()).isFalse(); + assertThat(mSwitchBar.isEnabled()).isTrue(); + } + @Test public void onStop_flagOff_doNothing() { mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); @@ -398,15 +411,21 @@ public class AudioSharingSwitchBarControllerTest { } @Test - public void onCheckedChangedToChecked_noConnectedLeaDevices_flagOn_notStartAudioSharing() { + public void onCheckedChangedToChecked_noConnectedLeaDevices_flagOn_showDialog() { FeatureFlagUtils.setEnabled( mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true); when(mBtnView.isEnabled()).thenReturn(true); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of()); doNothing().when(mBroadcast).startPrivateBroadcast(); mController.onCheckedChanged(mBtnView, /* isChecked= */ true); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(mSwitchBar.isChecked()).isFalse(); verify(mBroadcast, never()).startPrivateBroadcast(); + List childFragments = mParentFragment.getChildFragmentManager().getFragments(); + assertThat(childFragments) + .comparingElementsUsing(CLAZZNAME_EQUALS) + .containsExactly(AudioSharingConfirmDialogFragment.class.getName()); } @Test @@ -526,8 +545,8 @@ public class AudioSharingSwitchBarControllerTest { List childFragments = mParentFragment.getChildFragmentManager().getFragments(); assertThat(childFragments) - .comparingElementsUsing(TAG_EQUALS) - .containsExactly(AudioSharingDialogFragment.tag()); + .comparingElementsUsing(CLAZZNAME_EQUALS) + .containsExactly(AudioSharingDialogFragment.class.getName()); AudioSharingDialogFragment fragment = (AudioSharingDialogFragment) Iterables.getOnlyElement(childFragments); @@ -613,6 +632,8 @@ public class AudioSharingSwitchBarControllerTest { mSwitchBar.setChecked(false); when(mBroadcast.isEnabled(any())).thenReturn(false); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1, mDevice2)); + when(mDeviceManager.getCachedDevicesCopy()).thenReturn( + ImmutableList.of(mCachedDevice1, mCachedDevice2)); mController.mBroadcastCallback.onBroadcastStartFailed(/* reason= */ 1); shadowOf(Looper.getMainLooper()).idle(); assertThat(mSwitchBar.isChecked()).isFalse(); @@ -706,12 +727,30 @@ public class AudioSharingSwitchBarControllerTest { mSwitchBar.setEnabled(false); when(mBroadcast.isEnabled(null)).thenReturn(false); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); + when(mDeviceManager.getCachedDevicesCopy()).thenReturn( + ImmutableList.of(mCachedDevice2, mCachedDevice1)); mController.onActiveDeviceChanged(mCachedDevice2, BluetoothProfile.LE_AUDIO); shadowOf(Looper.getMainLooper()).idle(); assertThat(mSwitchBar.isChecked()).isFalse(); verify(mSwitchBar).setEnabled(true); } + @Test + public void onActiveDeviceChanged_a2dpProfile_updateSwitch() { + mSwitchBar.setChecked(true); + mSwitchBar.setEnabled(false); + when(mBroadcast.isEnabled(null)).thenReturn(false); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1, mDevice2)); + when(mCachedDevice2.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(false); + when(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(true); + when(mDeviceManager.getCachedDevicesCopy()).thenReturn( + ImmutableList.of(mCachedDevice1, mCachedDevice2)); + mController.onActiveDeviceChanged(mCachedDevice2, BluetoothProfile.A2DP); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(mSwitchBar.isChecked()).isFalse(); + verify(mSwitchBar).setEnabled(true); + } + @Test public void onActiveDeviceChanged_nullActiveDevice_doNothing() { mController.onActiveDeviceChanged(/* activeDevice= */ null, BluetoothProfile.LE_AUDIO); @@ -720,14 +759,6 @@ public class AudioSharingSwitchBarControllerTest { verify(mSwitchBar, never()).setChecked(anyBoolean()); } - @Test - public void onActiveDeviceChanged_notLeaProfile_doNothing() { - mController.onActiveDeviceChanged(mCachedDevice2, BluetoothProfile.HEADSET); - shadowOf(Looper.getMainLooper()).idle(); - verify(mSwitchBar, never()).setEnabled(anyBoolean()); - verify(mSwitchBar, never()).setChecked(anyBoolean()); - } - @Test public void testAccessibilityDelegate() { View view = new View(mContext); diff --git a/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java index d35b608d761..9b5f4520520 100644 --- a/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java +++ b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java @@ -35,8 +35,8 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.UserInfo; import android.graphics.drawable.ColorDrawable; +import android.os.UserHandle; import android.webkit.UserPackage; import androidx.fragment.app.FragmentActivity; @@ -71,15 +71,17 @@ import java.util.Collections; }) public class WebViewAppPickerTest { - private final static String PACKAGE_NAME = "com.example.test"; - private final static String PACKAGE_VERSION = "1.0.0"; + private static final String PACKAGE_NAME = "com.example.test"; + private static final String PACKAGE_VERSION = "1.0.0"; + private static final String FIRST_USER_NAME = "FIRST_USER"; + private static final String SECOND_USER_NAME = "SECOND_USER"; @Mock private FragmentActivity mActivity; private Context mContext; - private UserInfo mFirstUser; - private UserInfo mSecondUser; + private UserHandle mFirstUser; + private UserHandle mSecondUser; private ShadowPackageManager mPackageManager; private WebViewAppPicker mPicker; private WebViewUpdateServiceWrapper mWvusWrapper; @@ -105,8 +107,8 @@ public class WebViewAppPickerTest { mPackageManager.addPackage(packageInfo); mPackageManager.setUnbadgedApplicationIcon(PACKAGE_NAME, new ColorDrawable()); - mFirstUser = new UserInfo(0, "FIRST_USER", 0); - mSecondUser = new UserInfo(0, "SECOND_USER", 0); + mFirstUser = mUserManager.addUser(0, FIRST_USER_NAME, 0); + mSecondUser = mUserManager.addUser(1, SECOND_USER_NAME, 0); mPicker = new WebViewAppPicker(); mPicker = spy(mPicker); doNothing().when(mPicker).updateCandidates(); @@ -238,13 +240,13 @@ public class WebViewAppPickerTest { UserPackage packageForFirstUser = mock(UserPackage.class); when(packageForFirstUser.isEnabledPackage()).thenReturn(false); when(packageForFirstUser.isInstalledPackage()).thenReturn(true); - when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser); + when(packageForFirstUser.getUser()).thenReturn(mFirstUser); WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class); when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME))) .thenReturn(Collections.singletonList(packageForFirstUser)); - final String expectedReason = String.format("(disabled for user %s)", mFirstUser.name); + final String expectedReason = String.format("(disabled for user %s)", FIRST_USER_NAME); assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME)) .isEqualTo(expectedReason); } @@ -254,13 +256,13 @@ public class WebViewAppPickerTest { UserPackage packageForFirstUser = mock(UserPackage.class); when(packageForFirstUser.isEnabledPackage()).thenReturn(true); when(packageForFirstUser.isInstalledPackage()).thenReturn(false); - when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser); + when(packageForFirstUser.getUser()).thenReturn(mFirstUser); WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class); when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME))) .thenReturn(Collections.singletonList(packageForFirstUser)); - final String expectedReason = String.format("(uninstalled for user %s)", mFirstUser.name); + final String expectedReason = String.format("(uninstalled for user %s)", FIRST_USER_NAME); assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME)) .isEqualTo(expectedReason); } @@ -270,18 +272,18 @@ public class WebViewAppPickerTest { UserPackage packageForFirstUser = mock(UserPackage.class); when(packageForFirstUser.isEnabledPackage()).thenReturn(false); when(packageForFirstUser.isInstalledPackage()).thenReturn(true); - when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser); + when(packageForFirstUser.getUser()).thenReturn(mFirstUser); UserPackage packageForSecondUser = mock(UserPackage.class); when(packageForSecondUser.isEnabledPackage()).thenReturn(true); when(packageForSecondUser.isInstalledPackage()).thenReturn(false); - when(packageForSecondUser.getUserInfo()).thenReturn(mSecondUser); + when(packageForSecondUser.getUser()).thenReturn(mSecondUser); WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class); when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME))) .thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser)); - final String expectedReason = String.format("(disabled for user %s)", mFirstUser.name); + final String expectedReason = String.format("(disabled for user %s)", FIRST_USER_NAME); assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME)) .isEqualTo(expectedReason); } @@ -295,18 +297,18 @@ public class WebViewAppPickerTest { UserPackage packageForFirstUser = mock(UserPackage.class); when(packageForFirstUser.isEnabledPackage()).thenReturn(false); when(packageForFirstUser.isInstalledPackage()).thenReturn(false); - when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser); + when(packageForFirstUser.getUser()).thenReturn(mFirstUser); UserPackage packageForSecondUser = mock(UserPackage.class); when(packageForSecondUser.isEnabledPackage()).thenReturn(true); when(packageForSecondUser.isInstalledPackage()).thenReturn(true); - when(packageForSecondUser.getUserInfo()).thenReturn(mSecondUser); + when(packageForSecondUser.getUser()).thenReturn(mSecondUser); WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class); when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME))) .thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser)); - final String expectedReason = String.format("(uninstalled for user %s)", mFirstUser.name); + final String expectedReason = String.format("(uninstalled for user %s)", FIRST_USER_NAME); assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME)) .isEqualTo(expectedReason); } diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt index 8c54751da35..12bbcaf49e5 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt @@ -77,6 +77,19 @@ class CarrierConfigRepositoryTest { assertThat(value).isEqualTo(99) } + @Test + fun getIntArray_returnValue() { + val key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY + mockCarrierConfigManager.stub { + on { getConfigForSubId(any(), eq(key)) } doReturn + persistableBundleOf(key to intArrayOf(99)) + } + + val value = repository.getIntArray(SUB_ID, key)!!.toList() + + assertThat(value).containsExactly(99) + } + @Test fun getString_returnValue() { val key = CarrierConfigManager.KEY_CARRIER_NAME_STRING @@ -104,7 +117,8 @@ class CarrierConfigRepositoryTest { assertThat(carrierName) .isEqualTo( CarrierConfigManager.getDefaultConfig() - .getInt(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT)) + .getInt(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT) + ) } @Test diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.kt index 418a00b057f..c8fc977b500 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.kt @@ -41,91 +41,77 @@ import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class NrAdvancedCallingPreferenceControllerTest { - @get:Rule - val composeTestRule = createComposeRule() + @get:Rule val composeTestRule = createComposeRule() private val context: Context = spy(ApplicationProvider.getApplicationContext()) {} - private val callStateRepository = mock { - on { isInCallFlow() } doReturn flowOf(false) - } + private val callStateRepository = + mock { on { isInCallFlow() } doReturn flowOf(false) } - private val voNrRepository = mock() + private val voNrRepository = + mock { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(true) } - private val controller = NrAdvancedCallingPreferenceController( - context = context, - key = TEST_KEY, - callStateRepository = callStateRepository, - ).apply { init(SUB_ID, voNrRepository) } + private val controller = + NrAdvancedCallingPreferenceController( + context = context, + key = TEST_KEY, + voNrRepository = voNrRepository, + callStateRepository = callStateRepository, + ) + .apply { init(SUB_ID) } @Test fun isChecked_voNrEnabled_on() { - voNrRepository.stub { - on { isVoNrEnabledFlow() } doReturn flowOf(true) - } + voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(true) } - composeTestRule.setContent { - controller.Content() - } + composeTestRule.setContent { controller.Content() } - composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) + composeTestRule + .onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) .assertIsOn() } @Test fun isChecked_voNrDisabled_off() { - voNrRepository.stub { - on { isVoNrEnabledFlow() } doReturn flowOf(false) - } + voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(false) } - composeTestRule.setContent { - controller.Content() - } + composeTestRule.setContent { controller.Content() } - composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) + composeTestRule + .onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) .assertIsOff() } @Test - fun isEnabled_notInCall_enabled() { - callStateRepository.stub { - on { isInCallFlow() } doReturn flowOf(false) - } + fun isChangeable_notInCall_changeable() { + callStateRepository.stub { on { isInCallFlow() } doReturn flowOf(false) } - composeTestRule.setContent { - controller.Content() - } + composeTestRule.setContent { controller.Content() } - composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) + composeTestRule + .onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) .assertIsEnabled() } @Test - fun isEnabled_inCall_notEnabled() { - callStateRepository.stub { - on { isInCallFlow() } doReturn flowOf(true) - } + fun isChangeable_inCall_notChangeable() { + callStateRepository.stub { on { isInCallFlow() } doReturn flowOf(true) } - composeTestRule.setContent { - controller.Content() - } + composeTestRule.setContent { controller.Content() } - composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) + composeTestRule + .onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) .assertIsNotEnabled() } @Test fun onClick_setVoNrEnabled(): Unit = runBlocking { - voNrRepository.stub { - on { isVoNrEnabledFlow() } doReturn flowOf(false) - } + voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(false) } - composeTestRule.setContent { - controller.Content() - } + composeTestRule.setContent { controller.Content() } composeTestRule.onRoot().performClick() - verify(voNrRepository).setVoNrEnabled(true) + verify(voNrRepository).setVoNrEnabled(SUB_ID, true) } private companion object { diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/NrRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/NrRepositoryTest.kt new file mode 100644 index 00000000000..2df2a0fae59 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/NrRepositoryTest.kt @@ -0,0 +1,109 @@ +/* + * 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.CarrierConfigManager +import android.telephony.TelephonyManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +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 NrRepositoryTest { + private val mockTelephonyManager = + mock { + on { createForSubscriptionId(SUB_ID) } doReturn mock + on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR + } + + private val context: Context = + spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + } + + private val repository = NrRepository(context) + + @Before + fun setUp() { + CarrierConfigRepository.resetForTest() + } + + @Test + fun isNrAvailable_deviceNoNr_returnFalse() { + mockTelephonyManager.stub { + on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_LTE + } + + val available = repository.isNrAvailable(SUB_ID) + + assertThat(available).isFalse() + } + + @Test + fun isNrAvailable_carrierConfigNrIsEmpty_returnFalse() { + CarrierConfigRepository.setIntArrayForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, + value = intArrayOf(), + ) + + val available = repository.isNrAvailable(SUB_ID) + + assertThat(available).isFalse() + } + + @Test + fun isNrAvailable_carrierConfigNrIsNull_returnFalse() { + CarrierConfigRepository.setIntArrayForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, + value = null, + ) + + val available = repository.isNrAvailable(SUB_ID) + + assertThat(available).isFalse() + } + + @Test + fun isNrAvailable_allEnabled_returnTrue() { + mockTelephonyManager.stub { + on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR + } + CarrierConfigRepository.setIntArrayForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, + value = intArrayOf(1, 2), + ) + + val available = repository.isNrAvailable(SUB_ID) + + assertThat(available).isTrue() + } + + private companion object { + const val SUB_ID = 10 + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt index 9c20afe5e85..90d0aa56d77 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt @@ -19,17 +19,15 @@ package com.android.settings.network.telephony import android.content.Context import android.telephony.CarrierConfigManager import android.telephony.TelephonyManager -import androidx.core.os.persistableBundleOf import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.runBlocking +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.anyVararg import org.mockito.kotlin.doReturn -import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.stub @@ -38,127 +36,107 @@ import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class VoNrRepositoryTest { - private val mockTelephonyManager = mock { - on { createForSubscriptionId(SUB_ID) } doReturn mock - on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR + private val mockTelephonyManager = + mock { on { createForSubscriptionId(SUB_ID) } doReturn mock } + + private val context: Context = + spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + } + + private val mockNrRepository = mock { on { isNrAvailable(SUB_ID) } doReturn true } + + private val repository = VoNrRepository(context, mockNrRepository) + + @Before + fun setUp() { + CarrierConfigRepository.resetForTest() + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_VONR_ENABLED_BOOL, + value = true, + ) + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, + value = true, + ) } - private val carrierConfig = persistableBundleOf( - CarrierConfigManager.KEY_VONR_ENABLED_BOOL to true, - CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL to true, - CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY to intArrayOf(1, 2), - ) - - private val mockCarrierConfigManager = mock { - on { getConfigForSubId(eq(SUB_ID), anyVararg()) } doReturn carrierConfig - } - - private val context: Context = spy(ApplicationProvider.getApplicationContext()) { - on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager - on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager - } - - private val repository = VoNrRepository(context, SUB_ID) - @Test fun isVoNrAvailable_visibleDisable_returnFalse() { - carrierConfig.apply { - putBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, false) - } + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, + value = false, + ) - val available = repository.isVoNrAvailable() + val available = repository.isVoNrAvailable(SUB_ID) assertThat(available).isFalse() } @Test fun isVoNrAvailable_voNrDisabled_returnFalse() { - carrierConfig.apply { - putBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL, false) - } + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_VONR_ENABLED_BOOL, + value = false, + ) - val available = repository.isVoNrAvailable() + val available = repository.isVoNrAvailable(SUB_ID) assertThat(available).isFalse() } @Test fun isVoNrAvailable_allEnabled_returnTrue() { - mockTelephonyManager.stub { - on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR - } - carrierConfig.apply { - putBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL, true) - putBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, true) - putIntArray( - CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, - intArrayOf(1, 2), - ) - } + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_VONR_ENABLED_BOOL, + value = true, + ) + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, + value = true, + ) - val available = repository.isVoNrAvailable() + val available = repository.isVoNrAvailable(SUB_ID) assertThat(available).isTrue() } @Test - fun isVoNrAvailable_deviceNoNr_returnFalse() { - mockTelephonyManager.stub { - on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_LTE - } + fun isVoNrAvailable_noNr_returnFalse() { + mockNrRepository.stub { on { isNrAvailable(SUB_ID) } doReturn false } - val available = repository.isVoNrAvailable() - - assertThat(available).isFalse() - } - - @Test - fun isVoNrAvailable_carrierNoNr_returnFalse() { - carrierConfig.apply { - putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, intArrayOf()) - } - - val available = repository.isVoNrAvailable() - - assertThat(available).isFalse() - } - - @Test - fun isVoNrAvailable_carrierConfigNrIsNull_returnFalse() { - carrierConfig.apply { - putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, null) - } - - val available = repository.isVoNrAvailable() + val available = repository.isVoNrAvailable(SUB_ID) assertThat(available).isFalse() } @Test fun isVoNrEnabledFlow_voNrDisabled() = runBlocking { - mockTelephonyManager.stub { - on { isVoNrEnabled } doReturn false - } + mockTelephonyManager.stub { on { isVoNrEnabled } doReturn false } - val isVoNrEnabled = repository.isVoNrEnabledFlow().firstWithTimeoutOrNull() + val isVoNrEnabled = repository.isVoNrEnabledFlow(SUB_ID).firstWithTimeoutOrNull() assertThat(isVoNrEnabled).isFalse() } @Test fun isVoNrEnabledFlow_voNrEnabled() = runBlocking { - mockTelephonyManager.stub { - on { isVoNrEnabled } doReturn true - } + mockTelephonyManager.stub { on { isVoNrEnabled } doReturn true } - val isVoNrEnabled = repository.isVoNrEnabledFlow().firstWithTimeoutOrNull() + val isVoNrEnabled = repository.isVoNrEnabledFlow(SUB_ID).firstWithTimeoutOrNull() assertThat(isVoNrEnabled).isTrue() } @Test fun setVoNrEnabled(): Unit = runBlocking { - repository.setVoNrEnabled(true) + repository.setVoNrEnabled(SUB_ID, true) verify(mockTelephonyManager).setVoNrEnabled(true) }