Snap for 12348750 from 1c756ec809 to 24Q4-release

Change-Id: I8dec62d80cea41f1b27722e8b5523456cacfb93b
This commit is contained in:
Android Build Coastguard Worker
2024-09-10 23:22:39 +00:00
20 changed files with 789 additions and 296 deletions

View File

@@ -111,7 +111,10 @@ android_library {
"keyboard_flags_lib", "keyboard_flags_lib",
], ],
plugins: ["androidx.room_room-compiler-plugin"], plugins: [
"SettingsLibMetadata-processor",
"androidx.room_room-compiler-plugin",
],
errorprone: { errorprone: {
extra_check_modules: ["//external/nullaway:nullaway_plugin"], extra_check_modules: ["//external/nullaway:nullaway_plugin"],

View File

@@ -16,6 +16,8 @@
package com.android.settings; package com.android.settings;
import static com.android.settingslib.flags.Flags.settingsCatalyst;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@@ -42,13 +44,20 @@ import com.android.settings.overlay.FeatureFactoryImpl;
import com.android.settings.spa.SettingsSpaEnvironment; import com.android.settings.spa.SettingsSpaEnvironment;
import com.android.settingslib.applications.AppIconCacheManager; import com.android.settingslib.applications.AppIconCacheManager;
import com.android.settingslib.datastore.BackupRestoreStorageManager; 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.android.settingslib.spa.framework.common.SpaEnvironmentFactory;
import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupcompat.util.WizardManagerHelper;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.List;
/** Settings application which sets up activity embedding rules for the large screen device. */ /** 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 { public class SettingsApplication extends Application {
private WeakReference<SettingsHomepageActivity> mHomeActivity = new WeakReference<>(null); private WeakReference<SettingsHomepageActivity> mHomeActivity = new WeakReference<>(null);
@@ -64,6 +73,11 @@ public class SettingsApplication extends Application {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
if (settingsCatalyst()) {
PreferenceScreenRegistry.INSTANCE.setPreferenceScreensSupplier(
this::getPreferenceScreens);
}
BackupRestoreStorageManager.getInstance(this) BackupRestoreStorageManager.getInstance(this)
.add( .add(
new BatterySettingsStorage(this), new BatterySettingsStorage(this),
@@ -90,6 +104,13 @@ public class SettingsApplication extends Application {
registerActivityLifecycleCallbacks(new DeveloperOptionsActivityLifecycle()); registerActivityLifecycleCallbacks(new DeveloperOptionsActivityLifecycle());
} }
/** Returns the screens using metadata. */
protected List<PreferenceScreenMetadata> getPreferenceScreens() {
// PreferenceScreenCollector is generated by annotation processor from classes annotated
// with @ProvidePreferenceScreen
return PreferenceScreenCollector.get(this);
}
@Override @Override
public void onTerminate() { public void onTerminate() {
BackupRestoreStorageManager.getInstance(this).removeAll(); BackupRestoreStorageManager.getInstance(this).removeAll();

View File

@@ -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;
}
}

View File

@@ -25,7 +25,6 @@ import android.bluetooth.BluetoothLeBroadcast;
import android.bluetooth.BluetoothLeBroadcastAssistant; import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; 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 // FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST is always true in
// prod. We can turn off the flag for debug purpose. // prod. We can turn off the flag for debug purpose.
if (FeatureFlagUtils.isEnabled( if (FeatureFlagUtils.isEnabled(
mContext, mContext,
FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST) FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST)
&& mAssistant.getAllConnectedDevices().isEmpty()) { && hasEmptyConnectedSink()) {
// Pop up dialog to ask users to connect at least one lea buds before audio sharing. // Pop up dialog to ask users to connect at least one lea buds before audio sharing.
AudioSharingUtils.postOnMainThread( AudioSharingUtils.postOnMainThread(
mContext, mContext,
@@ -435,8 +434,12 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
} }
@Override @Override
public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { public void onActiveDeviceChanged(@Nullable CachedBluetoothDevice activeDevice,
if (activeDevice != null && bluetoothProfile == BluetoothProfile.LE_AUDIO) { int bluetoothProfile) {
if (activeDevice != null) {
Log.d(TAG, "onActiveDeviceChanged: device = "
+ activeDevice.getDevice().getAnonymizedAddress()
+ ", profile = " + bluetoothProfile);
updateSwitch(); updateSwitch();
} }
} }
@@ -536,13 +539,19 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
boolean isBroadcasting = BluetoothUtils.isBroadcasting(mBtManager); boolean isBroadcasting = BluetoothUtils.isBroadcasting(mBtManager);
boolean hasActiveDevice = boolean hasActiveDevice =
AudioSharingUtils.hasActiveConnectedLeadDevice(mBtManager); AudioSharingUtils.hasActiveConnectedLeadDevice(mBtManager);
boolean hasEmptyConnectedDevice = hasEmptyConnectedSink();
boolean isStateReady = boolean isStateReady =
isBluetoothOn() isBluetoothOn()
&& AudioSharingUtils.isAudioSharingProfileReady( && 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 // Disable toggle till device gets active after
// broadcast ends. // broadcast ends.
&& (isBroadcasting || hasActiveDevice); || hasActiveDevice);
AudioSharingUtils.postOnMainThread( AudioSharingUtils.postOnMainThread(
mContext, mContext,
() -> { () -> {
@@ -566,6 +575,10 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled(); return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
} }
private boolean hasEmptyConnectedSink() {
return mAssistant != null && mAssistant.getAllConnectedDevices().isEmpty();
}
private void handleOnBroadcastReady() { private void handleOnBroadcastReady() {
Pair<Integer, Object>[] eventData = Pair<Integer, Object>[] eventData =
AudioSharingUtils.buildAudioSharingDialogEventData( AudioSharingUtils.buildAudioSharingDialogEventData(

View File

@@ -215,15 +215,12 @@ public class AudioSharingUtils {
@Nullable LocalBluetoothManager localBtManager) { @Nullable LocalBluetoothManager localBtManager) {
CachedBluetoothDeviceManager deviceManager = CachedBluetoothDeviceManager deviceManager =
localBtManager == null ? null : localBtManager.getCachedDeviceManager(); localBtManager == null ? null : localBtManager.getCachedDeviceManager();
Map<Integer, List<BluetoothDevice>> groupedConnectedDevices = if (deviceManager == null) {
fetchConnectedDevicesByGroupId(localBtManager); Log.d(TAG, "hasActiveConnectedLeadDevice return false due to null device manager.");
for (List<BluetoothDevice> devices : groupedConnectedDevices.values()) { return false;
CachedBluetoothDevice leadDevice = getLeadDevice(deviceManager, devices);
if (isActiveLeAudioDevice(leadDevice)) {
return true;
}
} }
return false; return deviceManager.getCachedDevicesCopy().stream().anyMatch(
BluetoothUtils::isActiveMediaDevice);
} }
/** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */ /** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */

View File

@@ -15,6 +15,8 @@
*/ */
package com.android.settings.dashboard; package com.android.settings.dashboard;
import static com.android.settingslib.flags.Flags.settingsCatalyst;
import android.app.Activity; import android.app.Activity;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.ContentResolver; 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.core.lifecycle.Lifecycle;
import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.Tile;
import com.android.settingslib.metadata.PreferenceScreenRegistry;
import com.android.settingslib.search.Indexable; import com.android.settingslib.search.Indexable;
import java.util.ArrayList; import java.util.ArrayList;
@@ -97,30 +100,35 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
R.array.config_suppress_injected_tile_keys)); R.array.config_suppress_injected_tile_keys));
mDashboardFeatureProvider = mDashboardFeatureProvider =
FeatureFactory.getFeatureFactory().getDashboardFeatureProvider(); FeatureFactory.getFeatureFactory().getDashboardFeatureProvider();
// Load preference controllers from code
final List<AbstractPreferenceController> controllersFromCode =
createPreferenceControllers(context);
// Load preference controllers from xml definition
final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
.getPreferenceControllersFromXml(context, getPreferenceScreenResId());
// Filter xml-based controllers in case a similar controller is created from code already.
final List<BasePreferenceController> uniqueControllerFromXml =
PreferenceControllerListHelper.filterControllers(
controllersFromXml, controllersFromCode);
// Add unique controllers to list. if (!usePreferenceScreenMetadata() || PreferenceScreenRegistry.INSTANCE.get(
if (controllersFromCode != null) { getPreferenceScreenBindingKey(context)) == null) {
mControllers.addAll(controllersFromCode); // Load preference controllers from code
} final List<AbstractPreferenceController> controllersFromCode =
mControllers.addAll(uniqueControllerFromXml); createPreferenceControllers(context);
// Load preference controllers from xml definition
final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
.getPreferenceControllersFromXml(context, getPreferenceScreenResId());
// Filter xml-based controllers in case a similar controller is created from code
// already.
final List<BasePreferenceController> uniqueControllerFromXml =
PreferenceControllerListHelper.filterControllers(
controllersFromXml, controllersFromCode);
// And wire up with lifecycle. // Add unique controllers to list.
final Lifecycle lifecycle = getSettingsLifecycle(); if (controllersFromCode != null) {
uniqueControllerFromXml.forEach(controller -> { mControllers.addAll(controllersFromCode);
if (controller instanceof LifecycleObserver) {
lifecycle.addObserver((LifecycleObserver) controller);
} }
}); 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. // Set metrics category for BasePreferenceController.
final int metricCategory = getMetricsCategory(); 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 @Override
protected abstract int getPreferenceScreenResId(); protected abstract int getPreferenceScreenResId();
@@ -364,12 +377,32 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
if (resId <= 0) { if (resId <= 0) {
return; return;
} }
addPreferencesFromResource(resId); PreferenceScreen screen;
final PreferenceScreen screen = getPreferenceScreen(); if (usePreferenceScreenMetadata()) {
screen = createPreferenceScreen();
setPreferenceScreen(screen);
requireActivity().setTitle(screen.getTitle());
} else {
addPreferencesFromResource(resId);
screen = getPreferenceScreen();
}
screen.setOnExpandButtonClickListener(this); screen.setOnExpandButtonClickListener(this);
displayResourceTilesToScreen(screen); 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)} * Perform {@link AbstractPreferenceController#displayPreference(PreferenceScreen)}
* on all {@link AbstractPreferenceController}s. * on all {@link AbstractPreferenceController}s.

View File

@@ -17,6 +17,7 @@
package com.android.settings.network.telephony package com.android.settings.network.telephony
import android.content.Context import android.content.Context
import android.os.Build
import android.os.PersistableBundle import android.os.PersistableBundle
import android.telephony.CarrierConfigManager import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
@@ -35,7 +36,8 @@ class CarrierConfigRepository(private val context: Context) {
private enum class KeyType { private enum class KeyType {
BOOLEAN, BOOLEAN,
INT, INT,
STRING INT_ARRAY,
STRING,
} }
interface CarrierConfigAccessor { interface CarrierConfigAccessor {
@@ -43,17 +45,20 @@ class CarrierConfigRepository(private val context: Context) {
fun getInt(key: String): Int fun getInt(key: String): Int
fun getIntArray(key: String): IntArray?
fun getString(key: String): String? fun getString(key: String): String?
} }
private class Accessor(private val cache: ConfigCache) : CarrierConfigAccessor { private class Accessor(private val cache: ConfigCache) : CarrierConfigAccessor {
private val keysToRetrieve = mutableMapOf<String, KeyType>() private val keysToRetrieve = mutableMapOf<String, KeyType>()
private var isKeysToRetrieveFrozen = false
override fun getBoolean(key: String): Boolean { override fun getBoolean(key: String): Boolean {
checkBooleanKey(key) checkBooleanKey(key)
val value = cache[key] val value = cache[key]
return if (value == null) { return if (value == null) {
keysToRetrieve += key to KeyType.BOOLEAN addKeyToRetrieve(key, KeyType.BOOLEAN)
DefaultConfig.getBoolean(key) DefaultConfig.getBoolean(key)
} else { } else {
check(value is BooleanConfigValue) { "Boolean value type wrong" } 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" } check(key.endsWith("_int")) { "Int key should ends with _int" }
val value = cache[key] val value = cache[key]
return if (value == null) { return if (value == null) {
keysToRetrieve += key to KeyType.INT addKeyToRetrieve(key, KeyType.INT)
DefaultConfig.getInt(key) DefaultConfig.getInt(key)
} else { } else {
check(value is IntConfigValue) { "Int value type wrong" } 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? { override fun getString(key: String): String? {
check(key.endsWith("_string")) { "String key should ends with _string" } check(key.endsWith("_string")) { "String key should ends with _string" }
val value = cache[key] val value = cache[key]
return if (value == null) { return if (value == null) {
keysToRetrieve += key to KeyType.STRING addKeyToRetrieve(key, KeyType.STRING)
DefaultConfig.getString(key) DefaultConfig.getString(key)
} else { } else {
check(value is StringConfigValue) { "String value type wrong" } check(value is StringConfigValue) { "String value type wrong" }
@@ -85,20 +102,35 @@ class CarrierConfigRepository(private val context: Context) {
} }
} }
fun getKeysToRetrieve(): Map<String, KeyType> = 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<String, KeyType> {
isKeysToRetrieveFrozen = true
return keysToRetrieve
}
} }
/** /**
* Gets the configuration values for the given [subId]. * Gets the configuration values for the given [subId].
* *
* Configuration values could be accessed in [block]. Note: [block] could be called multiple * 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 <T> transformConfig(subId: Int, block: CarrierConfigAccessor.() -> T): T { fun <T> transformConfig(subId: Int, block: CarrierConfigAccessor.() -> T): T {
val perSubCache = getPerSubCache(subId) val perSubCache = getPerSubCache(subId)
val accessor = Accessor(perSubCache) val accessor = Accessor(perSubCache)
val result = accessor.block() 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 all keys found in the first pass, no need to collect again
if (keysToRetrieve.isEmpty()) return result 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]. */ /** Gets the configuration int for the given [subId] and [key]. */
fun getInt(subId: Int, key: String): Int = transformConfig(subId) { getInt(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]. */ /** Gets the configuration string for the given [subId] and [key]. */
fun getString(subId: Int, key: String): String? = transformConfig(subId) { getString(key) } fun getString(subId: Int, key: String): String? = transformConfig(subId) { getString(key) }
@@ -122,6 +158,7 @@ class CarrierConfigRepository(private val context: Context) {
when (type) { when (type) {
KeyType.BOOLEAN -> this[key] = BooleanConfigValue(config.getBoolean(key)) KeyType.BOOLEAN -> this[key] = BooleanConfigValue(config.getBoolean(key))
KeyType.INT -> this[key] = IntConfigValue(config.getInt(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)) 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 @VisibleForTesting
fun setBooleanForTest(subId: Int, key: String, value: Boolean) { fun setBooleanForTest(subId: Int, key: String, value: Boolean) {
checkBooleanKey(key) checkBooleanKey(key)
@@ -207,6 +248,12 @@ class CarrierConfigRepository(private val context: Context) {
getPerSubCache(subId)[key] = IntConfigValue(value) getPerSubCache(subId)[key] = IntConfigValue(value)
} }
@VisibleForTesting
fun setIntArrayForTest(subId: Int, key: String, value: IntArray?) {
checkIntArrayKey(key)
getPerSubCache(subId)[key] = IntArrayConfigValue(value)
}
@VisibleForTesting @VisibleForTesting
fun setStringForTest(subId: Int, key: String, value: String?) { fun setStringForTest(subId: Int, key: String, value: String?) {
check(key.endsWith("_string")) { "String key should ends with _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 data class IntConfigValue(val value: Int) : ConfigValue
private class IntArrayConfigValue(val value: IntArray?) : ConfigValue
private data class StringConfigValue(val value: String?) : ConfigValue private data class StringConfigValue(val value: String?) : ConfigValue
private typealias ConfigCache = ConcurrentHashMap<String, ConfigValue> private typealias ConfigCache = ConcurrentHashMap<String, ConfigValue>

View File

@@ -34,7 +34,6 @@ import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference; import androidx.preference.TwoStatePreference;
import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.ArrayUtils;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.network.ims.VolteQueryImsState; import com.android.settings.network.ims.VolteQueryImsState;
import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -56,8 +55,6 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc
Preference mPreference; Preference mPreference;
private PhoneCallStateTelephonyCallback mTelephonyCallback; private PhoneCallStateTelephonyCallback mTelephonyCallback;
private boolean mShow5gLimitedDialog; private boolean mShow5gLimitedDialog;
boolean mIsNrEnabledFromCarrierConfig;
private boolean mHas5gCapability;
private Integer mCallState; private Integer mCallState;
private final List<On4gLteUpdateListener> m4gLteListeners; private final List<On4gLteUpdateListener> m4gLteListeners;
@@ -94,9 +91,6 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc
mShow5gLimitedDialog = carrierConfig.getBoolean( mShow5gLimitedDialog = carrierConfig.getBoolean(
CarrierConfigManager.KEY_VOLTE_5G_LIMITED_ALERT_DIALOG_BOOL); 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; return this;
} }
@@ -247,10 +241,6 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc
} }
mTelephonyManager.registerTelephonyCallback( mTelephonyManager.registerTelephonyCallback(
mContext.getMainExecutor(), mTelephonyCallback); mContext.getMainExecutor(), mTelephonyCallback);
final long supportedRadioBitmask = mTelephonyManager.getSupportedRadioAccessFamily();
mHas5gCapability =
(supportedRadioBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_NR) > 0;
} }
public void unregister() { public void unregister() {
@@ -269,8 +259,7 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc
} }
private boolean isDialogNeeded() { private boolean isDialogNeeded() {
Log.d(TAG, "Has5gCapability:" + mHas5gCapability); return mShow5gLimitedDialog && new NrRepository(mContext).isNrAvailable(mSubId);
return mShow5gLimitedDialog && mHas5gCapability && mIsNrEnabledFromCarrierConfig;
} }
private void show5gLimitedDialog(ImsMmTelManager imsMmTelManager) { private void show5gLimitedDialog(ImsMmTelManager imsMmTelManager) {

View File

@@ -25,31 +25,28 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R 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.MobileNetworkSettingsSearchItem
import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchResult
import com.android.settings.spa.preference.ComposePreferenceController import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /** Preference controller for "Voice over NR". */
* Preference controller for "Voice over NR". class NrAdvancedCallingPreferenceController
*/ @JvmOverloads
class NrAdvancedCallingPreferenceController @JvmOverloads constructor( constructor(
context: Context, context: Context,
key: String, key: String,
private val callStateRepository : CallStateRepository = CallStateRepository(context), private val voNrRepository: VoNrRepository = VoNrRepository(context),
private val callStateRepository: CallStateRepository = CallStateRepository(context),
) : ComposePreferenceController(context, key) { ) : ComposePreferenceController(context, key) {
private var subId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID private var subId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
private var repository: VoNrRepository? = null
private val searchItem = NrAdvancedCallingSearchItem(context) private val searchItem = NrAdvancedCallingSearchItem(context)
/** Initial this PreferenceController. */ /** Initial this PreferenceController. */
@JvmOverloads fun init(subId: Int) {
fun init(subId: Int, repository: VoNrRepository = VoNrRepository(mContext, subId)) {
this.subId = subId this.subId = subId
this.repository = repository
} }
override fun getAvailabilityStatus() = override fun getAvailabilityStatus() =
@@ -58,30 +55,32 @@ class NrAdvancedCallingPreferenceController @JvmOverloads constructor(
@Composable @Composable
override fun Content() { override fun Content() {
val summary = stringResource(R.string.nr_advanced_calling_summary) val summary = stringResource(R.string.nr_advanced_calling_summary)
val isInCall by remember { callStateRepository.isInCallFlow() } val isInCall by
.collectAsStateWithLifecycle(initialValue = false) remember { callStateRepository.isInCallFlow() }
val isEnabled by remember { .collectAsStateWithLifecycle(initialValue = false)
repository?.isVoNrEnabledFlow() ?: flowOf(false) val isVoNrEnabled by
}.collectAsStateWithLifecycle(initialValue = false) remember { voNrRepository.isVoNrEnabledFlow(subId) }
.collectAsStateWithLifecycle(initialValue = false)
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
SwitchPreference(object : SwitchPreferenceModel { SwitchPreference(
override val title = stringResource(R.string.nr_advanced_calling_title) object : SwitchPreferenceModel {
override val summary = { summary } override val title = stringResource(R.string.nr_advanced_calling_title)
override val changeable = { !isInCall } override val summary = { summary }
override val checked = { isEnabled } override val changeable = { !isInCall }
override val onCheckedChange: (Boolean) -> Unit = { newChecked -> override val checked = { isVoNrEnabled }
coroutineScope.launch { override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
repository?.setVoNrEnabled(newChecked) coroutineScope.launch { voNrRepository.setVoNrEnabled(subId, newChecked) }
} }
} }
}) )
} }
companion object { companion object {
class NrAdvancedCallingSearchItem(private val context: Context) : class NrAdvancedCallingSearchItem(private val context: Context) :
MobileNetworkSettingsSearchItem { 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? { override fun getSearchResult(subId: Int): MobileNetworkSettingsSearchResult? {
if (!isAvailable(subId)) return null if (!isAvailable(subId)) return null

View File

@@ -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"
}
}

View File

@@ -19,7 +19,6 @@ package com.android.settings.network.telephony
import android.content.Context import android.content.Context
import android.telephony.CarrierConfigManager import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.util.Log import android.util.Log
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -29,43 +28,43 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class VoNrRepository(private val context: Context, private val subId: Int) { class VoNrRepository(
private val telephonyManager = context.telephonyManager(subId) private val context: Context,
private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!! private val nrRepository: NrRepository = NrRepository(context),
) {
private val carrierConfigRepository = CarrierConfigRepository(context)
fun isVoNrAvailable(): Boolean { fun isVoNrAvailable(subId: Int): Boolean {
if (!SubscriptionManager.isValidSubscriptionId(subId) || !has5gCapability()) return false if (!nrRepository.isNrAvailable(subId)) return false
val carrierConfig = carrierConfigManager.safeGetConfig( data class Config(val isVoNrEnabled: Boolean, val isVoNrSettingVisibility: Boolean)
keys = listOf( val carrierConfig =
CarrierConfigManager.KEY_VONR_ENABLED_BOOL, carrierConfigRepository.transformConfig(subId) {
CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, Config(
CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, isVoNrEnabled = getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL),
), isVoNrSettingVisibility =
subId = subId, getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL),
) )
return carrierConfig.getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL) && }
carrierConfig.getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL) && return carrierConfig.isVoNrEnabled && carrierConfig.isVoNrSettingVisibility
(carrierConfig.getIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY)
?.isNotEmpty() ?: false)
} }
private fun has5gCapability() = fun isVoNrEnabledFlow(subId: Int): Flow<Boolean> {
((telephonyManager.supportedRadioAccessFamily and val telephonyManager = context.telephonyManager(subId)
TelephonyManager.NETWORK_TYPE_BITMASK_NR) > 0) return context
.also { Log.d(TAG, "[$subId] has5gCapability: $it") } .subscriptionsChangedFlow()
.map { telephonyManager.isVoNrEnabled }
fun isVoNrEnabledFlow(): Flow<Boolean> = context.subscriptionsChangedFlow() .conflate()
.map { telephonyManager.isVoNrEnabled } .onEach { Log.d(TAG, "[$subId] isVoNrEnabled: $it") }
.conflate() .flowOn(Dispatchers.Default)
.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")
} }
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 companion object {
private const val TAG = "VoNrRepository" private const val TAG = "VoNrRepository"
} }

View File

@@ -26,6 +26,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo; import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.UserManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.webkit.UserPackage; import android.webkit.UserPackage;
@@ -149,17 +150,20 @@ public class WebViewAppPicker extends DefaultAppPickerFragment {
@VisibleForTesting @VisibleForTesting
String getDisabledReason(WebViewUpdateServiceWrapper webviewUpdateServiceWrapper, String getDisabledReason(WebViewUpdateServiceWrapper webviewUpdateServiceWrapper,
Context context, String packageName) { Context context, String packageName) {
UserManager userManager = context.getSystemService(UserManager.class);
List<UserPackage> userPackages = List<UserPackage> userPackages =
webviewUpdateServiceWrapper.getPackageInfosAllUsers(context, packageName); webviewUpdateServiceWrapper.getPackageInfosAllUsers(context, packageName);
for (UserPackage userPackage : userPackages) { for (UserPackage userPackage : userPackages) {
if (!userPackage.isInstalledPackage()) { if (!userPackage.isInstalledPackage()) {
// Package uninstalled/hidden // Package uninstalled/hidden
return context.getString( 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()) { } else if (!userPackage.isEnabledPackage()) {
// Package disabled // Package disabled
return context.getString( 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; return null;

View File

@@ -21,33 +21,30 @@ import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder; import androidx.preference.PreferenceViewHolder;
import com.android.settings.R; import com.android.settings.R;
import com.android.settingslib.widget.TwoTargetPreference;
/** A preference with tick icon. */ /** A preference with tick icon. */
public class TickButtonPreference extends Preference { public class TickButtonPreference extends TwoTargetPreference {
private ImageView mCheckIcon; private ImageView mCheckIcon;
private boolean mIsSelected = false; private boolean mIsSelected = false;
public TickButtonPreference(Context context) { public TickButtonPreference(Context context) {
super(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 @Override
public void onBindViewHolder(PreferenceViewHolder holder) { public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(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); mCheckIcon = (ImageView) holder.findViewById(R.id.check_icon);
setSelected(mIsSelected); setSelected(mIsSelected);
} }
@@ -64,4 +61,10 @@ public class TickButtonPreference extends Preference {
public boolean isSelected() { public boolean isSelected() {
return mIsSelected; return mIsSelected;
} }
@Override
protected int getSecondTargetResId() {
return R.layout.preference_check_icon;
}
} }

View File

@@ -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();
}
}

View File

@@ -111,10 +111,10 @@ import java.util.concurrent.Executor;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config( @Config(
shadows = { shadows = {
ShadowBluetoothAdapter.class, ShadowBluetoothAdapter.class,
ShadowBluetoothUtils.class, ShadowBluetoothUtils.class,
ShadowThreadUtils.class, ShadowThreadUtils.class,
ShadowAlertDialogCompat.class ShadowAlertDialogCompat.class
}) })
public class AudioSharingSwitchBarControllerTest { public class AudioSharingSwitchBarControllerTest {
private static final String TEST_DEVICE_NAME1 = "test1"; 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 String TEST_DEVICE_ANONYMIZED_ADDR2 = "XX:XX:02";
private static final int TEST_DEVICE_GROUP_ID1 = 1; private static final int TEST_DEVICE_GROUP_ID1 = 1;
private static final int TEST_DEVICE_GROUP_ID2 = 2; private static final int TEST_DEVICE_GROUP_ID2 = 2;
private static final Correspondence<Fragment, String> TAG_EQUALS = private static final Correspondence<Fragment, String> CLAZZNAME_EQUALS =
Correspondence.from( Correspondence.from(
(Fragment fragment, String tag) -> (Fragment fragment, String clazzName) ->
fragment instanceof DialogFragment fragment instanceof DialogFragment
&& ((DialogFragment) fragment).getTag() != null && ((DialogFragment) fragment).getClass().getName() != null
&& ((DialogFragment) fragment).getTag().equals(tag), && ((DialogFragment) fragment).getClass().getName().equals(
clazzName),
"is equal to"); "is equal to");
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -343,6 +344,18 @@ public class AudioSharingSwitchBarControllerTest {
assertThat(mSwitchBar.isEnabled()).isTrue(); 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 @Test
public void onStop_flagOff_doNothing() { public void onStop_flagOff_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
@@ -398,15 +411,21 @@ public class AudioSharingSwitchBarControllerTest {
} }
@Test @Test
public void onCheckedChangedToChecked_noConnectedLeaDevices_flagOn_notStartAudioSharing() { public void onCheckedChangedToChecked_noConnectedLeaDevices_flagOn_showDialog() {
FeatureFlagUtils.setEnabled( FeatureFlagUtils.setEnabled(
mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true); mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
when(mBtnView.isEnabled()).thenReturn(true); when(mBtnView.isEnabled()).thenReturn(true);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of()); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of());
doNothing().when(mBroadcast).startPrivateBroadcast(); doNothing().when(mBroadcast).startPrivateBroadcast();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true); mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle();
assertThat(mSwitchBar.isChecked()).isFalse(); assertThat(mSwitchBar.isChecked()).isFalse();
verify(mBroadcast, never()).startPrivateBroadcast(); verify(mBroadcast, never()).startPrivateBroadcast();
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments)
.comparingElementsUsing(CLAZZNAME_EQUALS)
.containsExactly(AudioSharingConfirmDialogFragment.class.getName());
} }
@Test @Test
@@ -526,8 +545,8 @@ public class AudioSharingSwitchBarControllerTest {
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments(); List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments) assertThat(childFragments)
.comparingElementsUsing(TAG_EQUALS) .comparingElementsUsing(CLAZZNAME_EQUALS)
.containsExactly(AudioSharingDialogFragment.tag()); .containsExactly(AudioSharingDialogFragment.class.getName());
AudioSharingDialogFragment fragment = AudioSharingDialogFragment fragment =
(AudioSharingDialogFragment) Iterables.getOnlyElement(childFragments); (AudioSharingDialogFragment) Iterables.getOnlyElement(childFragments);
@@ -613,6 +632,8 @@ public class AudioSharingSwitchBarControllerTest {
mSwitchBar.setChecked(false); mSwitchBar.setChecked(false);
when(mBroadcast.isEnabled(any())).thenReturn(false); when(mBroadcast.isEnabled(any())).thenReturn(false);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1, mDevice2)); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1, mDevice2));
when(mDeviceManager.getCachedDevicesCopy()).thenReturn(
ImmutableList.of(mCachedDevice1, mCachedDevice2));
mController.mBroadcastCallback.onBroadcastStartFailed(/* reason= */ 1); mController.mBroadcastCallback.onBroadcastStartFailed(/* reason= */ 1);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
assertThat(mSwitchBar.isChecked()).isFalse(); assertThat(mSwitchBar.isChecked()).isFalse();
@@ -706,12 +727,30 @@ public class AudioSharingSwitchBarControllerTest {
mSwitchBar.setEnabled(false); mSwitchBar.setEnabled(false);
when(mBroadcast.isEnabled(null)).thenReturn(false); when(mBroadcast.isEnabled(null)).thenReturn(false);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
when(mDeviceManager.getCachedDevicesCopy()).thenReturn(
ImmutableList.of(mCachedDevice2, mCachedDevice1));
mController.onActiveDeviceChanged(mCachedDevice2, BluetoothProfile.LE_AUDIO); mController.onActiveDeviceChanged(mCachedDevice2, BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
assertThat(mSwitchBar.isChecked()).isFalse(); assertThat(mSwitchBar.isChecked()).isFalse();
verify(mSwitchBar).setEnabled(true); 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 @Test
public void onActiveDeviceChanged_nullActiveDevice_doNothing() { public void onActiveDeviceChanged_nullActiveDevice_doNothing() {
mController.onActiveDeviceChanged(/* activeDevice= */ null, BluetoothProfile.LE_AUDIO); mController.onActiveDeviceChanged(/* activeDevice= */ null, BluetoothProfile.LE_AUDIO);
@@ -720,14 +759,6 @@ public class AudioSharingSwitchBarControllerTest {
verify(mSwitchBar, never()).setChecked(anyBoolean()); 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 @Test
public void testAccessibilityDelegate() { public void testAccessibilityDelegate() {
View view = new View(mContext); View view = new View(mContext);

View File

@@ -35,8 +35,8 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.UserInfo;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.os.UserHandle;
import android.webkit.UserPackage; import android.webkit.UserPackage;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
@@ -71,15 +71,17 @@ import java.util.Collections;
}) })
public class WebViewAppPickerTest { public class WebViewAppPickerTest {
private final static String PACKAGE_NAME = "com.example.test"; private static final String PACKAGE_NAME = "com.example.test";
private final static String PACKAGE_VERSION = "1.0.0"; 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 @Mock
private FragmentActivity mActivity; private FragmentActivity mActivity;
private Context mContext; private Context mContext;
private UserInfo mFirstUser; private UserHandle mFirstUser;
private UserInfo mSecondUser; private UserHandle mSecondUser;
private ShadowPackageManager mPackageManager; private ShadowPackageManager mPackageManager;
private WebViewAppPicker mPicker; private WebViewAppPicker mPicker;
private WebViewUpdateServiceWrapper mWvusWrapper; private WebViewUpdateServiceWrapper mWvusWrapper;
@@ -105,8 +107,8 @@ public class WebViewAppPickerTest {
mPackageManager.addPackage(packageInfo); mPackageManager.addPackage(packageInfo);
mPackageManager.setUnbadgedApplicationIcon(PACKAGE_NAME, new ColorDrawable()); mPackageManager.setUnbadgedApplicationIcon(PACKAGE_NAME, new ColorDrawable());
mFirstUser = new UserInfo(0, "FIRST_USER", 0); mFirstUser = mUserManager.addUser(0, FIRST_USER_NAME, 0);
mSecondUser = new UserInfo(0, "SECOND_USER", 0); mSecondUser = mUserManager.addUser(1, SECOND_USER_NAME, 0);
mPicker = new WebViewAppPicker(); mPicker = new WebViewAppPicker();
mPicker = spy(mPicker); mPicker = spy(mPicker);
doNothing().when(mPicker).updateCandidates(); doNothing().when(mPicker).updateCandidates();
@@ -238,13 +240,13 @@ public class WebViewAppPickerTest {
UserPackage packageForFirstUser = mock(UserPackage.class); UserPackage packageForFirstUser = mock(UserPackage.class);
when(packageForFirstUser.isEnabledPackage()).thenReturn(false); when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
when(packageForFirstUser.isInstalledPackage()).thenReturn(true); when(packageForFirstUser.isInstalledPackage()).thenReturn(true);
when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser); when(packageForFirstUser.getUser()).thenReturn(mFirstUser);
WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class); WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME))) when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME)))
.thenReturn(Collections.singletonList(packageForFirstUser)); .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)) assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME))
.isEqualTo(expectedReason); .isEqualTo(expectedReason);
} }
@@ -254,13 +256,13 @@ public class WebViewAppPickerTest {
UserPackage packageForFirstUser = mock(UserPackage.class); UserPackage packageForFirstUser = mock(UserPackage.class);
when(packageForFirstUser.isEnabledPackage()).thenReturn(true); when(packageForFirstUser.isEnabledPackage()).thenReturn(true);
when(packageForFirstUser.isInstalledPackage()).thenReturn(false); when(packageForFirstUser.isInstalledPackage()).thenReturn(false);
when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser); when(packageForFirstUser.getUser()).thenReturn(mFirstUser);
WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class); WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME))) when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME)))
.thenReturn(Collections.singletonList(packageForFirstUser)); .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)) assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME))
.isEqualTo(expectedReason); .isEqualTo(expectedReason);
} }
@@ -270,18 +272,18 @@ public class WebViewAppPickerTest {
UserPackage packageForFirstUser = mock(UserPackage.class); UserPackage packageForFirstUser = mock(UserPackage.class);
when(packageForFirstUser.isEnabledPackage()).thenReturn(false); when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
when(packageForFirstUser.isInstalledPackage()).thenReturn(true); when(packageForFirstUser.isInstalledPackage()).thenReturn(true);
when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser); when(packageForFirstUser.getUser()).thenReturn(mFirstUser);
UserPackage packageForSecondUser = mock(UserPackage.class); UserPackage packageForSecondUser = mock(UserPackage.class);
when(packageForSecondUser.isEnabledPackage()).thenReturn(true); when(packageForSecondUser.isEnabledPackage()).thenReturn(true);
when(packageForSecondUser.isInstalledPackage()).thenReturn(false); when(packageForSecondUser.isInstalledPackage()).thenReturn(false);
when(packageForSecondUser.getUserInfo()).thenReturn(mSecondUser); when(packageForSecondUser.getUser()).thenReturn(mSecondUser);
WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class); WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME))) when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME)))
.thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser)); .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)) assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME))
.isEqualTo(expectedReason); .isEqualTo(expectedReason);
} }
@@ -295,18 +297,18 @@ public class WebViewAppPickerTest {
UserPackage packageForFirstUser = mock(UserPackage.class); UserPackage packageForFirstUser = mock(UserPackage.class);
when(packageForFirstUser.isEnabledPackage()).thenReturn(false); when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
when(packageForFirstUser.isInstalledPackage()).thenReturn(false); when(packageForFirstUser.isInstalledPackage()).thenReturn(false);
when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser); when(packageForFirstUser.getUser()).thenReturn(mFirstUser);
UserPackage packageForSecondUser = mock(UserPackage.class); UserPackage packageForSecondUser = mock(UserPackage.class);
when(packageForSecondUser.isEnabledPackage()).thenReturn(true); when(packageForSecondUser.isEnabledPackage()).thenReturn(true);
when(packageForSecondUser.isInstalledPackage()).thenReturn(true); when(packageForSecondUser.isInstalledPackage()).thenReturn(true);
when(packageForSecondUser.getUserInfo()).thenReturn(mSecondUser); when(packageForSecondUser.getUser()).thenReturn(mSecondUser);
WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class); WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME))) when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME)))
.thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser)); .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)) assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME))
.isEqualTo(expectedReason); .isEqualTo(expectedReason);
} }

View File

@@ -77,6 +77,19 @@ class CarrierConfigRepositoryTest {
assertThat(value).isEqualTo(99) 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 @Test
fun getString_returnValue() { fun getString_returnValue() {
val key = CarrierConfigManager.KEY_CARRIER_NAME_STRING val key = CarrierConfigManager.KEY_CARRIER_NAME_STRING
@@ -104,7 +117,8 @@ class CarrierConfigRepositoryTest {
assertThat(carrierName) assertThat(carrierName)
.isEqualTo( .isEqualTo(
CarrierConfigManager.getDefaultConfig() CarrierConfigManager.getDefaultConfig()
.getInt(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT)) .getInt(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT)
)
} }
@Test @Test

View File

@@ -41,91 +41,77 @@ import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class NrAdvancedCallingPreferenceControllerTest { class NrAdvancedCallingPreferenceControllerTest {
@get:Rule @get:Rule val composeTestRule = createComposeRule()
val composeTestRule = createComposeRule()
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {} private val context: Context = spy(ApplicationProvider.getApplicationContext()) {}
private val callStateRepository = mock<CallStateRepository> { private val callStateRepository =
on { isInCallFlow() } doReturn flowOf(false) mock<CallStateRepository> { on { isInCallFlow() } doReturn flowOf(false) }
}
private val voNrRepository = mock<VoNrRepository>() private val voNrRepository =
mock<VoNrRepository> { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(true) }
private val controller = NrAdvancedCallingPreferenceController( private val controller =
context = context, NrAdvancedCallingPreferenceController(
key = TEST_KEY, context = context,
callStateRepository = callStateRepository, key = TEST_KEY,
).apply { init(SUB_ID, voNrRepository) } voNrRepository = voNrRepository,
callStateRepository = callStateRepository,
)
.apply { init(SUB_ID) }
@Test @Test
fun isChecked_voNrEnabled_on() { fun isChecked_voNrEnabled_on() {
voNrRepository.stub { voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(true) }
on { isVoNrEnabledFlow() } doReturn flowOf(true)
}
composeTestRule.setContent { composeTestRule.setContent { controller.Content() }
controller.Content()
}
composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) composeTestRule
.onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
.assertIsOn() .assertIsOn()
} }
@Test @Test
fun isChecked_voNrDisabled_off() { fun isChecked_voNrDisabled_off() {
voNrRepository.stub { voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(false) }
on { isVoNrEnabledFlow() } doReturn flowOf(false)
}
composeTestRule.setContent { composeTestRule.setContent { controller.Content() }
controller.Content()
}
composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) composeTestRule
.onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
.assertIsOff() .assertIsOff()
} }
@Test @Test
fun isEnabled_notInCall_enabled() { fun isChangeable_notInCall_changeable() {
callStateRepository.stub { callStateRepository.stub { on { isInCallFlow() } doReturn flowOf(false) }
on { isInCallFlow() } doReturn flowOf(false)
}
composeTestRule.setContent { composeTestRule.setContent { controller.Content() }
controller.Content()
}
composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) composeTestRule
.onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
.assertIsEnabled() .assertIsEnabled()
} }
@Test @Test
fun isEnabled_inCall_notEnabled() { fun isChangeable_inCall_notChangeable() {
callStateRepository.stub { callStateRepository.stub { on { isInCallFlow() } doReturn flowOf(true) }
on { isInCallFlow() } doReturn flowOf(true)
}
composeTestRule.setContent { composeTestRule.setContent { controller.Content() }
controller.Content()
}
composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) composeTestRule
.onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
.assertIsNotEnabled() .assertIsNotEnabled()
} }
@Test @Test
fun onClick_setVoNrEnabled(): Unit = runBlocking { fun onClick_setVoNrEnabled(): Unit = runBlocking {
voNrRepository.stub { voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(false) }
on { isVoNrEnabledFlow() } doReturn flowOf(false)
}
composeTestRule.setContent { composeTestRule.setContent { controller.Content() }
controller.Content()
}
composeTestRule.onRoot().performClick() composeTestRule.onRoot().performClick()
verify(voNrRepository).setVoNrEnabled(true) verify(voNrRepository).setVoNrEnabled(SUB_ID, true)
} }
private companion object { private companion object {

View File

@@ -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<TelephonyManager> {
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
}
}

View File

@@ -19,17 +19,15 @@ package com.android.settings.network.telephony
import android.content.Context import android.content.Context
import android.telephony.CarrierConfigManager import android.telephony.CarrierConfigManager
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import androidx.core.os.persistableBundleOf
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.kotlin.anyVararg
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import org.mockito.kotlin.spy import org.mockito.kotlin.spy
import org.mockito.kotlin.stub import org.mockito.kotlin.stub
@@ -38,127 +36,107 @@ import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class VoNrRepositoryTest { class VoNrRepositoryTest {
private val mockTelephonyManager = mock<TelephonyManager> { private val mockTelephonyManager =
on { createForSubscriptionId(SUB_ID) } doReturn mock mock<TelephonyManager> { 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 mockNrRepository = mock<NrRepository> { 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<CarrierConfigManager> {
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 @Test
fun isVoNrAvailable_visibleDisable_returnFalse() { fun isVoNrAvailable_visibleDisable_returnFalse() {
carrierConfig.apply { CarrierConfigRepository.setBooleanForTest(
putBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, false) 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() assertThat(available).isFalse()
} }
@Test @Test
fun isVoNrAvailable_voNrDisabled_returnFalse() { fun isVoNrAvailable_voNrDisabled_returnFalse() {
carrierConfig.apply { CarrierConfigRepository.setBooleanForTest(
putBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL, false) subId = SUB_ID,
} key = CarrierConfigManager.KEY_VONR_ENABLED_BOOL,
value = false,
)
val available = repository.isVoNrAvailable() val available = repository.isVoNrAvailable(SUB_ID)
assertThat(available).isFalse() assertThat(available).isFalse()
} }
@Test @Test
fun isVoNrAvailable_allEnabled_returnTrue() { fun isVoNrAvailable_allEnabled_returnTrue() {
mockTelephonyManager.stub { CarrierConfigRepository.setBooleanForTest(
on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR subId = SUB_ID,
} key = CarrierConfigManager.KEY_VONR_ENABLED_BOOL,
carrierConfig.apply { value = true,
putBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL, true) )
putBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, true) CarrierConfigRepository.setBooleanForTest(
putIntArray( subId = SUB_ID,
CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, key = CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL,
intArrayOf(1, 2), value = true,
) )
}
val available = repository.isVoNrAvailable() val available = repository.isVoNrAvailable(SUB_ID)
assertThat(available).isTrue() assertThat(available).isTrue()
} }
@Test @Test
fun isVoNrAvailable_deviceNoNr_returnFalse() { fun isVoNrAvailable_noNr_returnFalse() {
mockTelephonyManager.stub { mockNrRepository.stub { on { isNrAvailable(SUB_ID) } doReturn false }
on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_LTE
}
val available = repository.isVoNrAvailable() val available = repository.isVoNrAvailable(SUB_ID)
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()
assertThat(available).isFalse() assertThat(available).isFalse()
} }
@Test @Test
fun isVoNrEnabledFlow_voNrDisabled() = runBlocking { fun isVoNrEnabledFlow_voNrDisabled() = runBlocking {
mockTelephonyManager.stub { mockTelephonyManager.stub { on { isVoNrEnabled } doReturn false }
on { isVoNrEnabled } doReturn false
}
val isVoNrEnabled = repository.isVoNrEnabledFlow().firstWithTimeoutOrNull() val isVoNrEnabled = repository.isVoNrEnabledFlow(SUB_ID).firstWithTimeoutOrNull()
assertThat(isVoNrEnabled).isFalse() assertThat(isVoNrEnabled).isFalse()
} }
@Test @Test
fun isVoNrEnabledFlow_voNrEnabled() = runBlocking { fun isVoNrEnabledFlow_voNrEnabled() = runBlocking {
mockTelephonyManager.stub { mockTelephonyManager.stub { on { isVoNrEnabled } doReturn true }
on { isVoNrEnabled } doReturn true
}
val isVoNrEnabled = repository.isVoNrEnabledFlow().firstWithTimeoutOrNull() val isVoNrEnabled = repository.isVoNrEnabledFlow(SUB_ID).firstWithTimeoutOrNull()
assertThat(isVoNrEnabled).isTrue() assertThat(isVoNrEnabled).isTrue()
} }
@Test @Test
fun setVoNrEnabled(): Unit = runBlocking { fun setVoNrEnabled(): Unit = runBlocking {
repository.setVoNrEnabled(true) repository.setVoNrEnabled(SUB_ID, true)
verify(mockTelephonyManager).setVoNrEnabled(true) verify(mockTelephonyManager).setVoNrEnabled(true)
} }