Merge "[Audiosharing] Add loading state dialog" into main
diff --git a/Android.bp b/Android.bp
index 25fc8f56..8b903ba 100644
--- a/Android.bp
+++ b/Android.bp
@@ -111,7 +111,10 @@
"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/AndroidManifest.xml b/AndroidManifest.xml
index e204dd9..66c3beb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1292,7 +1292,7 @@
<activity
android:name="Settings$ModesSettingsActivity"
- android:label="@string/zen_mode_settings_title"
+ android:label="@string/zen_modes_list_title"
android:icon="@drawable/ic_homepage_notification"
android:exported="true">
<intent-filter android:priority="1"
diff --git a/res/layout/sfps_enroll_enrolling.xml b/res/layout/sfps_enroll_enrolling.xml
index e0f9d1f..bd6e69a 100644
--- a/res/layout/sfps_enroll_enrolling.xml
+++ b/res/layout/sfps_enroll_enrolling.xml
@@ -45,7 +45,7 @@
android:id="@+id/illustration_lottie"
android:layout_width="@dimen/fingerprint_progress_bar_max_size"
android:layout_height="@dimen/fingerprint_progress_bar_max_size"
- android:layout_marginRight="@dimen/sfps_lottie_translate_x"
+ android:layout_marginEnd="@dimen/sfps_lottie_translate_x"
android:layout_marginBottom="@dimen/sfps_lottie_translate_y"
android:scaleType="centerInside"
android:visibility="gone"
@@ -66,7 +66,7 @@
android:id="@+id/fingerprint_progress_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginLeft="@dimen/sfps_progress_bar_translate_x"
+ android:layout_marginStart="@dimen/sfps_progress_bar_translate_x"
android:layout_marginTop="@dimen/sfps_progress_bar_translate_y"
android:layout_gravity="center"
android:minHeight="@dimen/fingerprint_progress_bar_min_size"
diff --git a/res/values-ldrtl/dimens.xml b/res/values-ldrtl/dimens.xml
index cbe7eb5..f11b2f4 100755
--- a/res/values-ldrtl/dimens.xml
+++ b/res/values-ldrtl/dimens.xml
@@ -23,4 +23,8 @@
<dimen name="rotation_90_enroll_padding_start">20dp</dimen>
<dimen name="rotation_90_enroll_padding_end">0dp</dimen>
<dimen name="rotation_90_enroll_margin_end">20dp</dimen>
+
+ <!-- SFPS Fingerprint -->
+ <dimen name="fingerprint_progress_bar_max_size">242dp</dimen>
+ <dimen name="sfps_lottie_translate_x">10dp</dimen>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c63e5f4..c1207cf 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -5190,6 +5190,8 @@
<string name="accessibility_button_low_label">Transparent</string>
<!-- Label on the right side of transparency adjustment slider [CHAR LIMIT=30] -->
<string name="accessibility_button_high_label">Non-transparent</string>
+ <!-- Summary for settings that are disabled in the current button mode [CHAR LIMIT=30] -->
+ <string name="accessibility_button_disabled_button_mode_summary">Unavailable while using navigation bar button mode.</string>
<!-- Title for the accessibility preference to high contrast text. [CHAR LIMIT=35] -->
<string name="accessibility_toggle_high_text_contrast_preference_title">High contrast text</string>
<!-- Summary for the accessibility preference to high contrast text. [CHAR LIMIT=NONE] -->
diff --git a/src/com/android/settings/SettingsApplication.java b/src/com/android/settings/SettingsApplication.java
index d208fdf..7e008e4 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.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<SettingsHomepageActivity> mHomeActivity = new WeakReference<>(null);
@@ -64,6 +73,11 @@
public void onCreate() {
super.onCreate();
+ if (settingsCatalyst()) {
+ PreferenceScreenRegistry.INSTANCE.setPreferenceScreensSupplier(
+ this::getPreferenceScreens);
+ }
+
BackupRestoreStorageManager.getInstance(this)
.add(
new BatterySettingsStorage(this),
@@ -90,6 +104,13 @@
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
public void onTerminate() {
BackupRestoreStorageManager.getInstance(this).removeAll();
diff --git a/src/com/android/settings/accessibility/AccessibilityButtonFragment.java b/src/com/android/settings/accessibility/AccessibilityButtonFragment.java
index 2df11a9..60e4bb2 100644
--- a/src/com/android/settings/accessibility/AccessibilityButtonFragment.java
+++ b/src/com/android/settings/accessibility/AccessibilityButtonFragment.java
@@ -33,8 +33,15 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- final int titleResource = AccessibilityUtil.isGestureNavigateEnabled(getPrefContext())
- ? R.string.accessibility_button_gesture_title : R.string.accessibility_button_title;
+
+ final int titleResource;
+ if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
+ titleResource = R.string.accessibility_button_title;
+ } else {
+ titleResource = AccessibilityUtil.isGestureNavigateEnabled(getPrefContext())
+ ? R.string.accessibility_button_gesture_title
+ : R.string.accessibility_button_title;
+ }
getActivity().setTitle(titleResource);
}
diff --git a/src/com/android/settings/accessibility/AccessibilityButtonGesturePreferenceController.java b/src/com/android/settings/accessibility/AccessibilityButtonGesturePreferenceController.java
index ded8aca..6db28a6 100644
--- a/src/com/android/settings/accessibility/AccessibilityButtonGesturePreferenceController.java
+++ b/src/com/android/settings/accessibility/AccessibilityButtonGesturePreferenceController.java
@@ -50,8 +50,12 @@
@Override
public int getAvailabilityStatus() {
- return AccessibilityUtil.isGestureNavigateEnabled(mContext)
- ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
+ return CONDITIONALLY_UNAVAILABLE;
+ } else {
+ return AccessibilityUtil.isGestureNavigateEnabled(mContext)
+ ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
}
@Override
diff --git a/src/com/android/settings/accessibility/CaptioningMoreOptionsFragment.java b/src/com/android/settings/accessibility/CaptioningMoreOptionsFragment.java
index 2895e75..85b48e9 100644
--- a/src/com/android/settings/accessibility/CaptioningMoreOptionsFragment.java
+++ b/src/com/android/settings/accessibility/CaptioningMoreOptionsFragment.java
@@ -17,6 +17,8 @@
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.provider.Settings;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
@@ -50,5 +52,16 @@
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider(R.xml.captioning_more_options);
+ new BaseSearchIndexProvider(R.xml.captioning_more_options) {
+ @Override
+ protected boolean isPageSearchEnabled(Context context) {
+ if (!Flags.fixA11ySettingsSearch()) {
+ return super.isPageSearchEnabled(context);
+ }
+ // CaptioningMoreOptions is only searchable if captions are enabled, so that we
+ // don't show search results for settings that will cause no change to the user.
+ return Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, 0) == 1;
+ }
+ };
}
diff --git a/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
index 3d36aa4..70882a4 100644
--- a/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
+++ b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
@@ -28,6 +28,7 @@
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
+import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
@@ -65,6 +66,18 @@
}
@Override
+ public CharSequence getSummary() {
+ if (mPreference != null) {
+ return mPreference.isEnabled()
+ ? "%s"
+ : mContext.getString(
+ R.string.accessibility_button_disabled_button_mode_summary);
+ } else {
+ return "%s";
+ }
+ }
+
+ @Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
@@ -101,6 +114,7 @@
private void updateAvailabilityStatus() {
mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext));
+ refreshSummary(mPreference);
}
private int getFloatingMenuFadeValue() {
diff --git a/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java
index 7d4f495..2f036da 100644
--- a/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java
+++ b/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java
@@ -29,6 +29,7 @@
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
+import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
@@ -80,6 +81,18 @@
}
@Override
+ public CharSequence getSummary() {
+ if (mPreference != null) {
+ return mPreference.isEnabled()
+ ? "%s"
+ : mContext.getString(
+ R.string.accessibility_button_disabled_button_mode_summary);
+ } else {
+ return "%s";
+ }
+ }
+
+ @Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
@@ -119,6 +132,7 @@
private void updateAvailabilityStatus() {
mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext));
+ refreshSummary(mPreference);
}
@Size
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
index 4cb540c..e42c654 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
@@ -26,6 +26,7 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
@@ -52,7 +53,9 @@
import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -81,7 +84,9 @@
private static final String LE_AUDIO_TOGGLE_VISIBLE_PROPERTY =
"persist.bluetooth.leaudio.toggle_visible";
- private final AtomicReference<Set<String>> mInvisiblePreferenceKey = new AtomicReference<>();
+ private Set<String> mInvisibleProfiles = Collections.emptySet();
+ private final AtomicReference<Set<String>> mAdditionalInvisibleProfiles =
+ new AtomicReference<>();
private LocalBluetoothManager mManager;
private LocalBluetoothProfileManager mProfileManager;
@@ -95,13 +100,21 @@
@VisibleForTesting
PreferenceCategory mProfilesContainer;
- public BluetoothDetailsProfilesController(Context context, PreferenceFragmentCompat fragment,
- LocalBluetoothManager manager, CachedBluetoothDevice device, Lifecycle lifecycle) {
+ public BluetoothDetailsProfilesController(
+ Context context,
+ PreferenceFragmentCompat fragment,
+ LocalBluetoothManager manager,
+ CachedBluetoothDevice device,
+ Lifecycle lifecycle,
+ @Nullable List<String> invisibleProfiles) {
super(context, fragment, device, lifecycle);
mManager = manager;
mProfileManager = mManager.getProfileManager();
mCachedDevice = device;
mCachedDeviceGroup = Utils.findAllCachedBluetoothDevicesByGroupId(mManager, mCachedDevice);
+ if (invisibleProfiles != null) {
+ mInvisibleProfiles = Set.copyOf(invisibleProfiles);
+ }
}
@Override
@@ -563,7 +576,7 @@
protected void refresh() {
ThreadUtils.postOnBackgroundThread(
() -> {
- mInvisiblePreferenceKey.set(
+ mAdditionalInvisibleProfiles.set(
FeatureFactory.getFeatureFactory()
.getBluetoothFeatureProvider()
.getInvisibleProfilePreferenceKeys(
@@ -604,12 +617,15 @@
mProfilesContainer.addPreference(preference);
}
- Set<String> invisibleKeys = mInvisiblePreferenceKey.get();
- if (invisibleKeys != null) {
- for (int i = 0; i < mProfilesContainer.getPreferenceCount(); ++i) {
- Preference pref = mProfilesContainer.getPreference(i);
- pref.setVisible(pref.isVisible() && !invisibleKeys.contains(pref.getKey()));
- }
+ Set<String> additionalInvisibleProfiles = mAdditionalInvisibleProfiles.get();
+ HashSet<String> combinedInvisibleProfiles = new HashSet<>(mInvisibleProfiles);
+ if (additionalInvisibleProfiles != null) {
+ combinedInvisibleProfiles.addAll(additionalInvisibleProfiles);
+ }
+ Log.i(TAG, "Invisible profiles: " + combinedInvisibleProfiles);
+ for (int i = 0; i < mProfilesContainer.getPreferenceCount(); ++i) {
+ Preference pref = mProfilesContainer.getPreference(i);
+ pref.setVisible(pref.isVisible() && !combinedInvisibleProfiles.contains(pref.getKey()));
}
}
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
index 844a7c0..0e51d17 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -419,12 +419,16 @@
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+ List<String> invisibleProfiles = List.of();
if (Flags.enableBluetoothDeviceDetailsPolish()) {
mFormatter =
FeatureFactory.getFeatureFactory()
.getBluetoothFeatureProvider()
.getDeviceDetailsFragmentFormatter(
requireContext(), this, mBluetoothAdapter, mCachedDevice);
+ invisibleProfiles =
+ mFormatter.getInvisibleBluetoothProfiles(
+ FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE);
}
ArrayList<AbstractPreferenceController> controllers = new ArrayList<>();
@@ -444,7 +448,7 @@
controllers.add(new BluetoothDetailsSpatialAudioController(context, this, mCachedDevice,
lifecycle));
controllers.add(new BluetoothDetailsProfilesController(context, this, mManager,
- mCachedDevice, lifecycle));
+ mCachedDevice, lifecycle, invisibleProfiles));
controllers.add(new BluetoothDetailsMacAddressController(context, this, mCachedDevice,
lifecycle));
controllers.add(new StylusDevicesController(context, mInputDevice, mCachedDevice,
diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
index 8f0bf3e..f2a569d 100644
--- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
+++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
@@ -43,6 +43,7 @@
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import com.android.settings.spa.preference.ComposePreference
import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
import com.android.settingslib.spa.framework.theme.SettingsDimension
@@ -69,6 +70,9 @@
fun getVisiblePreferenceKeys(fragmentType: FragmentTypeModel): List<String>?
/** Updates device details fragment layout. */
+ fun getInvisibleBluetoothProfiles(fragmentType: FragmentTypeModel): List<String>?
+
+ /** Updates device details fragment layout. */
fun updateLayout(fragmentType: FragmentTypeModel)
/** Gets the menu items of the fragment. */
@@ -108,13 +112,22 @@
viewModel
.getItems(fragmentType)
?.filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem>()
- ?.mapNotNull { it.preferenceKey }
+ ?.map { it.preferenceKey }
+ }
+
+ override fun getInvisibleBluetoothProfiles(fragmentType: FragmentTypeModel): List<String>? =
+ runBlocking {
+ viewModel
+ .getItems(fragmentType)
+ ?.filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem.BluetoothProfilesItem>()
+ ?.first()?.invisibleProfiles
}
/** Updates bluetooth device details fragment layout. */
override fun updateLayout(fragmentType: FragmentTypeModel) = runBlocking {
val items = viewModel.getItems(fragmentType) ?: return@runBlocking
val layout = viewModel.getLayout(fragmentType) ?: return@runBlocking
+
val prefKeyToSettingId =
items
.filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem>()
diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt
index f69f023..c0fbd4f 100644
--- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt
+++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt
@@ -22,7 +22,6 @@
import android.graphics.PorterDuff
import android.os.Bundle
import android.view.Menu
-import android.view.MenuInflater
import android.view.MenuItem
import androidx.lifecycle.lifecycleScope
import com.android.settings.R
@@ -63,8 +62,10 @@
item.icon?.setColorFilter(
resources.getColor(
com.android.settingslib.widget.theme.R.color
- .settingslib_materialColorOnSurface),
- PorterDuff.Mode.SRC_ATOP)
+ .settingslib_materialColorOnSurface
+ ),
+ PorterDuff.Mode.SRC_ATOP,
+ )
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
}
}
@@ -116,14 +117,27 @@
}
formatter =
featureFactory.bluetoothFeatureProvider.getDeviceDetailsFragmentFormatter(
- requireContext(), this, bluetoothManager.adapter, cachedDevice)
+ requireContext(),
+ this,
+ bluetoothManager.adapter,
+ cachedDevice,
+ )
helpItem =
formatter
.getMenuItem(FragmentTypeModel.DeviceDetailsMoreSettingsFragment)
.stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), initialValue = null)
return listOf(
BluetoothDetailsProfilesController(
- context, this, localBluetoothManager, cachedDevice, settingsLifecycle))
+ context,
+ this,
+ localBluetoothManager,
+ cachedDevice,
+ settingsLifecycle,
+ formatter.getInvisibleBluetoothProfiles(
+ FragmentTypeModel.DeviceDetailsMoreSettingsFragment
+ ),
+ )
+ )
}
override fun getLogTag(): String = TAG
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java
new file mode 100644
index 0000000..5de615e
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java
@@ -0,0 +1,113 @@
+/*
+ * 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.content.DialogInterface;
+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;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+public class AudioSharingIncompatibleDialogFragment extends InstrumentedDialogFragment {
+ private static final String TAG = "AudioSharingIncompatDlg";
+
+ private static final String BUNDLE_KEY_DEVICE_NAME = "bundle_key_device_name";
+
+ // The host creates an instance of this dialog fragment must implement this interface to receive
+ // event callbacks.
+ public interface DialogEventListener {
+ /**
+ * Called when the dialog is dismissed.
+ */
+ void onDialogDismissed();
+ }
+
+ @Nullable
+ private static DialogEventListener sListener;
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO: add metrics
+ return 0;
+ }
+
+ /**
+ * Display the {@link AudioSharingIncompatibleDialogFragment} dialog.
+ *
+ * @param host The Fragment this dialog will be hosted.
+ */
+ public static void show(@Nullable Fragment host, @NonNull CachedBluetoothDevice cachedDevice,
+ @NonNull DialogEventListener listener) {
+ 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;
+ }
+ sListener = listener;
+ AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
+ if (dialog != null) {
+ Log.d(TAG, "Dialog is showing, return.");
+ return;
+ }
+ Log.d(TAG, "Show up the incompatible device dialog.");
+ final Bundle bundle = new Bundle();
+ bundle.putString(BUNDLE_KEY_DEVICE_NAME, cachedDevice.getName());
+ AudioSharingIncompatibleDialogFragment dialogFrag =
+ new AudioSharingIncompatibleDialogFragment();
+ dialogFrag.setArguments(bundle);
+ dialogFrag.show(manager, TAG);
+ }
+
+ @Override
+ @NonNull
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ Bundle arguments = requireArguments();
+ String deviceName = arguments.getString(BUNDLE_KEY_DEVICE_NAME);
+ // TODO: move strings to res once they are finalized
+ AlertDialog dialog =
+ AudioSharingDialogFactory.newBuilder(getActivity())
+ .setTitle("Can't share audio with " + deviceName)
+ .setTitleIcon(com.android.settings.R.drawable.ic_warning_24dp)
+ .setIsCustomBodyEnabled(true)
+ .setCustomMessage(
+ "Audio sharing only works with headphones that support LE Audio.")
+ .setPositiveButton(com.android.settings.R.string.okay, (d, w) -> {})
+ .build();
+ dialog.setCanceledOnTouchOutside(true);
+ return dialog;
+ }
+
+ @Override
+ public void onDismiss(@NonNull DialogInterface dialog) {
+ super.onDismiss(dialog);
+ if (sListener != null) {
+ sListener.onDialogDismissed();
+ }
+ }
+}
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 0000000..822e053
--- /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 6501084..2040694 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.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 @@
// 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 @@
}
@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 @@
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 @@
return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
}
+ private boolean hasEmptyConnectedSink() {
+ return mAssistant != null && mAssistant.getAllConnectedDevices().isEmpty();
+ }
+
private void handleOnBroadcastReady() {
Pair<Integer, Object>[] eventData =
AudioSharingUtils.buildAudioSharingDialogEventData(
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
index 43256bc..a662809 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
@@ -215,15 +215,12 @@
@Nullable LocalBluetoothManager localBtManager) {
CachedBluetoothDeviceManager deviceManager =
localBtManager == null ? null : localBtManager.getCachedDeviceManager();
- Map<Integer, List<BluetoothDevice>> groupedConnectedDevices =
- fetchConnectedDevicesByGroupId(localBtManager);
- for (List<BluetoothDevice> 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 0808da1..09dd1dd 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.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 @@
R.array.config_suppress_injected_tile_keys));
mDashboardFeatureProvider =
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 (controllersFromCode != null) {
- mControllers.addAll(controllersFromCode);
- }
- mControllers.addAll(uniqueControllerFromXml);
+ if (!usePreferenceScreenMetadata() || PreferenceScreenRegistry.INSTANCE.get(
+ getPreferenceScreenBindingKey(context)) == null) {
+ // 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);
- // 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();
@@ -273,6 +281,11 @@
}
@Override
+ protected final int getPreferenceScreenResId(@NonNull Context context) {
+ return getPreferenceScreenResId();
+ }
+
+ @Override
protected abstract int getPreferenceScreenResId();
@Override
@@ -364,12 +377,32 @@
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 8852540..99683a8 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 @@
private enum class KeyType {
BOOLEAN,
INT,
- STRING
+ INT_ARRAY,
+ STRING,
}
interface CarrierConfigAccessor {
@@ -43,17 +45,20 @@
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<String, KeyType>()
+ 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 @@
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 @@
}
}
+ 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 @@
}
}
- 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].
*
* 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 {
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 @@
/** 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 @@
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 @@
}
}
+ 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)
@@ -208,6 +249,12 @@
}
@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" }
getPerSubCache(subId)[key] = StringConfigValue(value)
@@ -221,6 +268,8 @@
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<String, ConfigValue>
diff --git a/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java b/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java
index 1a71e5a..d1988c4 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.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 @@
Preference mPreference;
private PhoneCallStateTelephonyCallback mTelephonyCallback;
private boolean mShow5gLimitedDialog;
- boolean mIsNrEnabledFromCarrierConfig;
- private boolean mHas5gCapability;
private Integer mCallState;
private final List<On4gLteUpdateListener> m4gLteListeners;
@@ -94,9 +91,6 @@
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 @@
}
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 @@
}
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 0d8766e..b79e818 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.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 @@
@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 0000000..e6247fd
--- /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 35af284..635c572 100644
--- a/src/com/android/settings/network/telephony/VoNrRepository.kt
+++ b/src/com/android/settings/network/telephony/VoNrRepository.kt
@@ -19,7 +19,6 @@
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.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<Boolean> = 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<Boolean> {
+ 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/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
index d7d1531..e987ebe 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
@@ -293,6 +293,7 @@
new ChooseLockSettingsHelper.Builder(this);
launchedCDC = builder.setHeader(mTitle)
.setDescription(mDetails)
+ .setAlternateButton(alternateButton)
.setExternal(true)
.setUserId(LockPatternUtils.USER_REPAIR_MODE)
.show();
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
index 0d57b57..677d18e 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
@@ -197,8 +197,8 @@
mCancelButton = view.findViewById(R.id.cancelButton);
boolean showCancelButton = mRemoteValidation || getActivity().getIntent().getBooleanExtra(
SHOW_CANCEL_BUTTON, false);
- boolean hasAlternateButton = (mFrp || mRemoteValidation) && !TextUtils.isEmpty(
- mAlternateButtonText);
+ boolean hasAlternateButton = (mFrp || mRemoteValidation || mRepairMode)
+ && !TextUtils.isEmpty(mAlternateButtonText);
mCancelButton.setVisibility(showCancelButton || hasAlternateButton
? View.VISIBLE : View.GONE);
if (hasAlternateButton) {
diff --git a/src/com/android/settings/users/MultiUserSwitchBarController.java b/src/com/android/settings/users/MultiUserSwitchBarController.java
index 07c03d7..1d64141 100644
--- a/src/com/android/settings/users/MultiUserSwitchBarController.java
+++ b/src/com/android/settings/users/MultiUserSwitchBarController.java
@@ -53,6 +53,12 @@
mSwitchBar = switchBar;
mListener = listener;
mUserCapabilities = UserCapabilities.create(context);
+ updateState();
+ mSwitchBar.setListener(this);
+ }
+
+ void updateState() {
+ mUserCapabilities.updateAddUserCapabilities(mContext);
mSwitchBar.setChecked(mUserCapabilities.mUserSwitcherEnabled);
if (Flags.fixDisablingOfMuToggleWhenRestrictionApplied()) {
@@ -74,7 +80,6 @@
mSwitchBar.setEnabled(mUserCapabilities.mIsMain);
}
}
- mSwitchBar.setListener(this);
}
@Override
@@ -92,7 +97,7 @@
Log.d(TAG, "Toggling multi-user feature enabled state to: " + isChecked);
final boolean success = Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.USER_SWITCHER_ENABLED, isChecked ? 1 : 0);
- if (success && mListener != null) {
+ if (success && mListener != null && !Flags.newMultiuserSettingsUx()) {
mListener.onMultiUserSwitchChanged(isChecked);
}
return success;
diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java
index c387d9e..a0137df 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -419,6 +419,7 @@
mTimeoutToDockUserPreferenceController.getPreferenceKey()));
mRemoveGuestOnExitPreferenceController.updateState(screen.findPreference(
mRemoveGuestOnExitPreferenceController.getPreferenceKey()));
+ mSwitchBarController.updateState();
if (mShouldUpdateUserList) {
updateUI();
}
diff --git a/src/com/android/settings/webview/WebViewAppPicker.java b/src/com/android/settings/webview/WebViewAppPicker.java
index b1dfd14..7fb5ebc 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.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 @@
@VisibleForTesting
String getDisabledReason(WebViewUpdateServiceWrapper webviewUpdateServiceWrapper,
Context context, String packageName) {
+ UserManager userManager = context.getSystemService(UserManager.class);
List<UserPackage> 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 b9b9b19..4778f8c 100644
--- a/src/com/android/settings/widget/TickButtonPreference.java
+++ b/src/com/android/settings/widget/TickButtonPreference.java
@@ -21,33 +21,30 @@
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 boolean isSelected() {
return mIsSelected;
}
+
+ @Override
+ protected int getSecondTargetResId() {
+ return R.layout.preference_check_icon;
+ }
+
}
diff --git a/tests/Enable16KbTests/Android.bp b/tests/Enable16KbTests/Android.bp
index fa05d33..7e1d32c 100644
--- a/tests/Enable16KbTests/Android.bp
+++ b/tests/Enable16KbTests/Android.bp
@@ -34,8 +34,8 @@
platform_apis: true,
certificate: "platform",
libs: [
- "android.test.runner",
- "android.test.base",
+ "android.test.runner.stubs.system",
+ "android.test.base.stubs.system",
],
}
diff --git a/tests/anomaly-tester/Android.bp b/tests/anomaly-tester/Android.bp
index d437a6c..63af4ee 100644
--- a/tests/anomaly-tester/Android.bp
+++ b/tests/anomaly-tester/Android.bp
@@ -13,7 +13,7 @@
certificate: "platform",
- libs: ["android.test.runner"],
+ libs: ["android.test.runner.stubs.system"],
static_libs: [
"androidx.test.rules",
diff --git a/tests/perftests/Android.bp b/tests/perftests/Android.bp
index 7e3a8aa..b35bd4a 100644
--- a/tests/perftests/Android.bp
+++ b/tests/perftests/Android.bp
@@ -14,7 +14,7 @@
certificate: "platform",
libs: [
- "android.test.runner",
+ "android.test.runner.stubs.system",
],
static_libs: [
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
index d6210e7..3fd4306 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -76,7 +76,7 @@
],
libs: [
- "android.test.mock",
+ "android.test.mock.impl",
"ims-common",
],
diff --git a/tests/robotests/src/com/android/settings/accessibility/CaptioningMoreOptionsFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/CaptioningMoreOptionsFragmentTest.java
index 6969472..5486808 100644
--- a/tests/robotests/src/com/android/settings/accessibility/CaptioningMoreOptionsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/CaptioningMoreOptionsFragmentTest.java
@@ -20,6 +20,9 @@
import android.app.settings.SettingsEnums;
import android.content.Context;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
import androidx.test.core.app.ApplicationProvider;
@@ -27,15 +30,23 @@
import com.android.settings.testutils.XmlTestUtils;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
/** Tests for {@link CaptioningMoreOptionsFragment}. */
@RunWith(RobolectricTestRunner.class)
public class CaptioningMoreOptionsFragmentTest {
+ // Language/locale preference key, from captioning_more_options.xml
+ private static final String CAPTIONING_LOCALE_KEY = "captioning_locale";
+
+ @Rule
+ public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
private CaptioningMoreOptionsFragment mFragment;
@@ -65,11 +76,40 @@
@Test
public void getNonIndexableKeys_existInXmlLayout() {
final List<String> niks = CaptioningMoreOptionsFragment.SEARCH_INDEX_DATA_PROVIDER
- .getNonIndexableKeys(mContext);
+ .getNonIndexableKeys(mContext)
+ .stream().filter(Objects::nonNull).collect(Collectors.toList());
final List<String> keys =
- XmlTestUtils.getKeysFromPreferenceXml(mContext,
- R.xml.captioning_more_options);
+ XmlTestUtils.getKeysFromPreferenceXml(mContext, R.xml.captioning_more_options);
assertThat(keys).containsAtLeastElementsIn(niks);
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH)
+ public void getNonIndexableKeys_captioningEnabled_localeIsSearchable() {
+ setCaptioningEnabled(true);
+
+ final List<String> niks = CaptioningMoreOptionsFragment.SEARCH_INDEX_DATA_PROVIDER
+ .getNonIndexableKeys(mContext);
+
+ // Not in NonIndexableKeys == searchable
+ assertThat(niks).doesNotContain(CAPTIONING_LOCALE_KEY);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH)
+ public void getNonIndexableKeys_captioningDisabled_localeIsNotSearchable() {
+ setCaptioningEnabled(false);
+
+ final List<String> niks = CaptioningMoreOptionsFragment.SEARCH_INDEX_DATA_PROVIDER
+ .getNonIndexableKeys(mContext);
+
+ // In NonIndexableKeys == not searchable
+ assertThat(niks).contains(CAPTIONING_LOCALE_KEY);
+ }
+
+ private void setCaptioningEnabled(boolean enabled) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, enabled ? 1 : 0);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java
index 4b4dd8b..d137d82 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java
@@ -120,11 +120,7 @@
.thenAnswer(invocation -> ImmutableList.of(mConnectableProfiles));
setupDevice(mDeviceConfig);
- mController = new BluetoothDetailsProfilesController(mContext, mFragment, mLocalManager,
- mCachedDevice, mLifecycle);
- mProfiles.setKey(mController.getPreferenceKey());
- mController.mProfilesContainer = mProfiles;
- mScreen.addPreference(mProfiles);
+ initController(List.of());
BluetoothProperties.le_audio_allow_list(Lists.newArrayList(LE_DEVICE_MODEL));
}
@@ -554,6 +550,36 @@
@Test
public void prefKeyInBlockingList_hideToggle() {
+ initController(List.of("A2DP"));
+ setupDevice(makeDefaultDeviceConfig());
+
+ addA2dpProfileToDevice(true, true, true);
+ when(mFeatureProvider.getInvisibleProfilePreferenceKeys(any(), any()))
+ .thenReturn(ImmutableSet.of());
+
+ showScreen(mController);
+
+ List<SwitchPreferenceCompat> switches = getProfileSwitches(false);
+ assertThat(switches.get(0).isVisible()).isFalse();
+ }
+
+ @Test
+ public void prefKeyNotInBlockingList_showToggle() {
+ initController(List.of());
+ setupDevice(makeDefaultDeviceConfig());
+
+ addA2dpProfileToDevice(true, true, true);
+ when(mFeatureProvider.getInvisibleProfilePreferenceKeys(any(), any()))
+ .thenReturn(ImmutableSet.of());
+
+ showScreen(mController);
+
+ List<SwitchPreferenceCompat> switches = getProfileSwitches(false);
+ assertThat(switches.get(0).isVisible()).isTrue();
+ }
+
+ @Test
+ public void prefKeyInFeatureProviderBlockingList_hideToggle() {
setupDevice(makeDefaultDeviceConfig());
addA2dpProfileToDevice(true, true, true);
@@ -567,7 +593,7 @@
}
@Test
- public void prefKeyNotInBlockingList_showToggle() {
+ public void prefKeyNotInFeatureProviderBlockingList_showToggle() {
setupDevice(makeDefaultDeviceConfig());
addA2dpProfileToDevice(true, true, true);
@@ -627,4 +653,13 @@
assertThat(switches.getFirst().getTitle()).isEqualTo(
mContext.getString(mLeAudioProfile.getNameResource(mDevice)));
}
+
+ private void initController(List<String> invisibleProfiles) {
+ mController = new BluetoothDetailsProfilesController(mContext, mFragment, mLocalManager,
+ mCachedDevice, mLifecycle, invisibleProfiles);
+ mProfiles.setKey(mController.getPreferenceKey());
+ mController.mProfilesContainer = mProfiles;
+ mScreen.removeAll();
+ mScreen.addPreference(mProfiles);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
index a5bc246..8070b2e 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
+++ b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
@@ -122,10 +122,11 @@
.thenReturn(
DeviceSettingConfigModel(
listOf(
- DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
DeviceSettingId.DEVICE_SETTING_ID_HEADER,
- "bluetooth_device_header"),
- DeviceSettingConfigItemModel.BuiltinItem(
+ "bluetooth_device_header"
+ ),
+ DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, "action_buttons"),
),
listOf(),
@@ -203,10 +204,10 @@
.thenReturn(
DeviceSettingConfigModel(
listOf(
- DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
DeviceSettingId.DEVICE_SETTING_ID_HEADER,
"bluetooth_device_header"),
- DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
"keyboard_settings"),
),
@@ -227,12 +228,12 @@
.thenReturn(
DeviceSettingConfigModel(
listOf(
- DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
DeviceSettingId.DEVICE_SETTING_ID_HEADER,
"bluetooth_device_header"),
DeviceSettingConfigItemModel.AppProvidedItem(
DeviceSettingId.DEVICE_SETTING_ID_ANC),
- DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
"keyboard_settings"),
),
diff --git a/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt b/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt
index 9cbe6e3..6869c23 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt
+++ b/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt
@@ -282,10 +282,10 @@
private companion object {
val BUILTIN_SETTING_ITEM_1 =
- DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
DeviceSettingId.DEVICE_SETTING_ID_HEADER, "bluetooth_device_header")
val BUILDIN_SETTING_ITEM_2 =
- DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, "action_buttons")
val SETTING_ITEM_HELP = DeviceSettingConfigItemModel.AppProvidedItem(12345)
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java
new file mode 100644
index 0000000..7f17291
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.mockito.Mockito.when;
+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 android.widget.TextView;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.settings.R;
+import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+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.Mock;
+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;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowAlertDialogCompat.class, ShadowBluetoothAdapter.class})
+public class AudioSharingIncompatibleDialogFragmentTest {
+ private static final String TEST_DEVICE_NAME = "test";
+ private static final AudioSharingIncompatibleDialogFragment.DialogEventListener
+ EMPTY_EVENT_LISTENER = () -> {};
+
+ @Rule public final MockitoRule mocks = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Mock private CachedBluetoothDevice mCachedBluetoothDevice;
+ private Fragment mParent;
+ private AudioSharingIncompatibleDialogFragment 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);
+ when(mCachedBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME);
+ mFragment = new AudioSharingIncompatibleDialogFragment();
+ 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 to real metrics id
+ assertThat(mFragment.getMetricsCategory()).isEqualTo(0);
+ }
+
+ @Test
+ public void onCreateDialog_flagOff_dialogNotExist() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
+ EMPTY_EVENT_LISTENER);
+ shadowMainLooper().idle();
+ AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNull();
+ }
+
+ @Test
+ public void onCreateDialog_unattachedFragment_dialogNotExist() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ AudioSharingIncompatibleDialogFragment.show(new Fragment(), mCachedBluetoothDevice,
+ EMPTY_EVENT_LISTENER);
+ shadowMainLooper().idle();
+ AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNull();
+ }
+
+ @Test
+ public void onCreateDialog_flagOn_showDialog() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
+ EMPTY_EVENT_LISTENER);
+ shadowMainLooper().idle();
+ AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNotNull();
+ assertThat(dialog.isShowing()).isTrue();
+ TextView title = dialog.findViewById(R.id.title_text);
+ assertThat(title).isNotNull();
+ // TODO: use string res
+ assertThat(title.getText().toString()).isEqualTo(
+ "Can't share audio with " + TEST_DEVICE_NAME);
+ }
+
+ @Test
+ public void onCreateDialog_clickBtn_callbackTriggered() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ AtomicBoolean isBtnClicked = new AtomicBoolean(false);
+ AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
+ () -> isBtnClicked.set(true));
+ 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();
+ assertThat(isBtnClicked.get()).isTrue();
+ }
+}
+
+
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 0000000..4b0524b
--- /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 d1533c9..354c5c7 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 @@
@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 @@
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<Fragment, String> TAG_EQUALS =
+ private static final Correspondence<Fragment, String> 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();
@@ -344,6 +345,18 @@
}
@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);
mController.onStop(mLifecycleOwner);
@@ -398,15 +411,21 @@
}
@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<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+ assertThat(childFragments)
+ .comparingElementsUsing(CLAZZNAME_EQUALS)
+ .containsExactly(AudioSharingConfirmDialogFragment.class.getName());
}
@Test
@@ -526,8 +545,8 @@
List<Fragment> 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 @@
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,6 +727,8 @@
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();
@@ -713,6 +736,22 @@
}
@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);
shadowOf(Looper.getMainLooper()).idle();
@@ -721,14 +760,6 @@
}
@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);
AccessibilityEvent event =
diff --git a/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
index d35b608..9b5f452 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.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 @@
})
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 @@
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 @@
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 @@
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 @@
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 @@
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/screenshot/Android.bp b/tests/screenshot/Android.bp
index 5130fe5..f79d00f 100644
--- a/tests/screenshot/Android.bp
+++ b/tests/screenshot/Android.bp
@@ -66,9 +66,9 @@
],
libs: [
- "android.test.runner",
- "android.test.base",
- "android.test.mock",
+ "android.test.runner.impl",
+ "android.test.base.impl",
+ "android.test.mock.impl",
"truth",
],
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 8c54751..12bbcaf 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
@@ -78,6 +78,19 @@
}
@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
mockCarrierConfigManager.stub {
@@ -104,7 +117,8 @@
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 418a00b..c8fc977 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 @@
@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<CallStateRepository> {
- on { isInCallFlow() } doReturn flowOf(false)
- }
+ private val callStateRepository =
+ 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(
- 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 0000000..2df2a0f
--- /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<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
+ }
+}
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 9c20afe..90d0aa5 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 @@
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 @@
@RunWith(AndroidJUnit4::class)
class VoNrRepositoryTest {
- private val mockTelephonyManager = mock<TelephonyManager> {
- on { createForSubscriptionId(SUB_ID) } doReturn mock
- on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR
+ private val mockTelephonyManager =
+ mock<TelephonyManager> { on { createForSubscriptionId(SUB_ID) } doReturn mock }
+
+ 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
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)
}
diff --git a/tests/uitests/Android.bp b/tests/uitests/Android.bp
index d5852b3..be402f4 100644
--- a/tests/uitests/Android.bp
+++ b/tests/uitests/Android.bp
@@ -33,8 +33,8 @@
],
libs: [
- "android.test.runner",
- "android.test.base",
+ "android.test.runner.stubs.system",
+ "android.test.base.stubs.system",
],
static_libs: [