infoList = mHapClientProfile.getAllPresetInfo(
+ mCachedDevice.getDevice());
+ CharSequence[] presetNames = new CharSequence[infoList.size()];
+ CharSequence[] presetIndexes = new CharSequence[infoList.size()];
+ for (int i = 0; i < infoList.size(); i++) {
+ presetNames[i] = infoList.get(i).getName();
+ presetIndexes[i] = Integer.toString(infoList.get(i).getIndex());
+ }
+ mPreference.setEntries(presetNames);
+ mPreference.setEntryValues(presetIndexes);
+ }
+
+ @VisibleForTesting
+ @Nullable
+ ListPreference getPreference() {
+ return mPreference;
+ }
+
+ void showErrorToast() {
+ Toast.makeText(mContext, R.string.bluetooth_hearing_aids_presets_error,
+ Toast.LENGTH_SHORT).show();
+ }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java
new file mode 100644
index 00000000000..3703b7180af
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java
@@ -0,0 +1,116 @@
+/*
+ * 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.bluetooth;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.accessibility.Flags;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The controller of the hearing device controls.
+ *
+ * Note: It is responsible for creating the sub-controllers inside this preference
+ * category controller.
+ */
+public class BluetoothDetailsHearingDeviceController extends BluetoothDetailsController {
+
+ public static final int ORDER_HEARING_DEVICE_SETTINGS = 1;
+ public static final int ORDER_HEARING_AIDS_PRESETS = 2;
+ static final String KEY_HEARING_DEVICE_GROUP = "hearing_device_group";
+
+ private final List mControllers = new ArrayList<>();
+ private Lifecycle mLifecycle;
+ private LocalBluetoothManager mManager;
+
+ public BluetoothDetailsHearingDeviceController(@NonNull Context context,
+ @NonNull PreferenceFragmentCompat fragment,
+ @NonNull LocalBluetoothManager manager,
+ @NonNull CachedBluetoothDevice device,
+ @NonNull Lifecycle lifecycle) {
+ super(context, fragment, device, lifecycle);
+ mManager = manager;
+ mLifecycle = lifecycle;
+ }
+
+ @VisibleForTesting
+ void setSubControllers(
+ BluetoothDetailsHearingDeviceSettingsController hearingDeviceSettingsController,
+ BluetoothDetailsHearingAidsPresetsController presetsController) {
+ mControllers.clear();
+ mControllers.add(hearingDeviceSettingsController);
+ mControllers.add(presetsController);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mControllers.stream().anyMatch(BluetoothDetailsController::isAvailable);
+ }
+
+ @Override
+ @NonNull
+ public String getPreferenceKey() {
+ return KEY_HEARING_DEVICE_GROUP;
+ }
+
+ @Override
+ protected void init(PreferenceScreen screen) {
+
+ }
+
+ @Override
+ protected void refresh() {
+
+ }
+
+ /**
+ * Initiates the sub controllers controlled by this group controller.
+ *
+ * Note: The caller must call this method when creating this class.
+ *
+ * @param isLaunchFromHearingDevicePage a boolean that determines if the caller is launch from
+ * hearing device page
+ */
+ void initSubControllers(boolean isLaunchFromHearingDevicePage) {
+ mControllers.clear();
+ // Don't need to show the entrance to hearing device page when launched from the same page
+ if (!isLaunchFromHearingDevicePage) {
+ mControllers.add(new BluetoothDetailsHearingDeviceSettingsController(mContext,
+ mFragment, mCachedDevice, mLifecycle));
+ }
+ if (Flags.enableHearingAidPresetControl()) {
+ mControllers.add(new BluetoothDetailsHearingAidsPresetsController(mContext, mFragment,
+ mManager, mCachedDevice, mLifecycle));
+ }
+ }
+
+ @NonNull
+ public List getSubControllers() {
+ return mControllers;
+ }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsController.java
similarity index 71%
rename from src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
rename to src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsController.java
index 162abc78aef..7e5f3b1a78f 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsController.java
@@ -16,6 +16,9 @@
package com.android.settings.bluetooth;
+import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
+import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.ORDER_HEARING_DEVICE_SETTINGS;
+
import android.content.Context;
import android.text.TextUtils;
@@ -36,15 +39,13 @@ import com.google.common.annotations.VisibleForTesting;
/**
* The controller of the hearing device settings to launch Hearing device page.
*/
-public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDetailsController
+public class BluetoothDetailsHearingDeviceSettingsController extends BluetoothDetailsController
implements Preference.OnPreferenceClickListener {
@VisibleForTesting
- static final String KEY_DEVICE_CONTROLS_GENERAL_GROUP = "device_controls_general";
- @VisibleForTesting
- static final String KEY_HEARING_DEVICE_CONTROLS = "hearing_device_controls";
+ static final String KEY_HEARING_DEVICE_SETTINGS = "hearing_device_settings";
- public BluetoothDetailsHearingDeviceControlsController(Context context,
+ public BluetoothDetailsHearingDeviceSettingsController(Context context,
PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) {
super(context, fragment, device, lifecycle);
lifecycle.addObserver(this);
@@ -57,37 +58,40 @@ public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDe
@Override
protected void init(PreferenceScreen screen) {
- if (!mCachedDevice.isHearingAidDevice()) {
+ if (!isAvailable()) {
return;
}
-
- final PreferenceCategory prefCategory = screen.findPreference(getPreferenceKey());
- final Preference pref = createHearingDeviceControlsPreference(prefCategory.getContext());
- prefCategory.addPreference(pref);
+ final PreferenceCategory group = screen.findPreference(KEY_HEARING_DEVICE_GROUP);
+ final Preference pref = createHearingDeviceSettingsPreference(group.getContext());
+ group.addPreference(pref);
}
@Override
- protected void refresh() {}
+ protected void refresh() {
+
+ }
@Override
public String getPreferenceKey() {
- return KEY_DEVICE_CONTROLS_GENERAL_GROUP;
+ return KEY_HEARING_DEVICE_SETTINGS;
}
@Override
public boolean onPreferenceClick(Preference preference) {
- if (TextUtils.equals(preference.getKey(), KEY_HEARING_DEVICE_CONTROLS)) {
+ if (TextUtils.equals(preference.getKey(), KEY_HEARING_DEVICE_SETTINGS)) {
launchAccessibilityHearingDeviceSettings();
return true;
}
return false;
}
- private Preference createHearingDeviceControlsPreference(Context context) {
+ private Preference createHearingDeviceSettingsPreference(Context context) {
final ArrowPreference preference = new ArrowPreference(context);
- preference.setKey(KEY_HEARING_DEVICE_CONTROLS);
- preference.setTitle(context.getString(R.string.bluetooth_device_controls_title));
- preference.setSummary(context.getString(R.string.bluetooth_device_controls_summary));
+ preference.setKey(KEY_HEARING_DEVICE_SETTINGS);
+ preference.setOrder(ORDER_HEARING_DEVICE_SETTINGS);
+ preference.setTitle(context.getString(R.string.bluetooth_hearing_device_settings_title));
+ preference.setSummary(
+ context.getString(R.string.bluetooth_hearing_device_settings_summary));
preference.setOnPreferenceClickListener(this);
return preference;
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
index 9c68c9cc870..87b2c6b65d0 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -326,16 +326,16 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
lifecycle));
controllers.add(new BluetoothDetailsPairOtherController(context, this, mCachedDevice,
lifecycle));
- // Don't need to show hearing device again when launched from the same page.
- if (!isLaunchFromHearingDevicePage()) {
- controllers.add(new BluetoothDetailsHearingDeviceControlsController(context, this,
- mCachedDevice, lifecycle));
- }
- controllers.add(new BluetoothDetailsDataSyncController(context, this,
- mCachedDevice, lifecycle));
- controllers.add(
- new BluetoothDetailsExtraOptionsController(
- context, this, mCachedDevice, lifecycle));
+ controllers.add(new BluetoothDetailsDataSyncController(context, this, mCachedDevice,
+ lifecycle));
+ controllers.add(new BluetoothDetailsExtraOptionsController(context, this, mCachedDevice,
+ lifecycle));
+ BluetoothDetailsHearingDeviceController hearingDeviceController =
+ new BluetoothDetailsHearingDeviceController(context, this, mManager,
+ mCachedDevice, lifecycle);
+ controllers.add(hearingDeviceController);
+ hearingDeviceController.initSubControllers(isLaunchFromHearingDevicePage());
+ controllers.addAll(hearingDeviceController.getSubControllers());
}
return controllers;
}
diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
index 6ef5aa82a49..42e6d9c4b68 100644
--- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
@@ -52,7 +52,6 @@ import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.Instrumentable;
-import com.android.settingslib.datastore.ChangeReason;
import com.android.settingslib.widget.LayoutPreference;
import java.util.ArrayList;
@@ -272,7 +271,6 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
public void onPause() {
super.onPause();
- notifyBackupManager();
final int currentOptimizeMode = mBatteryOptimizeUtils.getAppOptimizationMode();
mLogStringBuilder.append(", onPause mode = ").append(currentOptimizeMode);
logMetricCategory(currentOptimizeMode);
@@ -289,13 +287,6 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
Log.d(TAG, "Leave with mode: " + currentOptimizeMode);
}
- @VisibleForTesting
- void notifyBackupManager() {
- if (mOptimizationMode != mBatteryOptimizeUtils.getAppOptimizationMode()) {
- BatterySettingsStorage.get(getContext()).notifyChange(ChangeReason.UPDATE);
- }
- }
-
@VisibleForTesting
void initHeader() {
final View appSnippet = mHeaderPreference.findViewById(R.id.entity_header);
diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
index dc4aade4545..001876c394b 100644
--- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
@@ -33,6 +33,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
+import com.android.settingslib.datastore.ChangeReason;
import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
import java.lang.annotation.Retention;
@@ -222,6 +223,10 @@ public class BatteryOptimizeUtils {
return;
}
+ // App preferences are already clear when code reach here, and there may be no
+ // setAppUsageStateInternal call to notifyChange. So always trigger notifyChange here.
+ BatterySettingsStorage.get(context).notifyChange(ChangeReason.DELETE);
+
allowlistBackend.refreshList();
// Resets optimization mode for each application.
for (ApplicationInfo info : applications) {
@@ -351,6 +356,9 @@ public class BatteryOptimizeUtils {
}
BatteryOptimizeLogUtils.writeLog(
context, action, packageNameKey, createLogEvent(appStandbyMode, allowListed));
+ if (action != Action.RESET) { // reset has been notified in resetAppOptimizationMode
+ BatterySettingsStorage.get(context).notifyChange(toChangeReason(action));
+ }
}
private static String createLogEvent(int appStandbyMode, boolean allowListed) {
@@ -362,4 +370,8 @@ public class BatteryOptimizeUtils {
allowListed,
getAppOptimizationMode(appStandbyMode, allowListed));
}
+
+ private static @ChangeReason int toChangeReason(Action action) {
+ return action == Action.RESTORE ? ChangeReason.RESTORE : ChangeReason.UPDATE;
+ }
}
diff --git a/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java b/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java
index 56702cf5c2a..b662d3ef908 100644
--- a/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java
@@ -41,7 +41,6 @@ import com.android.settingslib.HelpUtils;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.datastore.ChangeReason;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.LayoutPreference;
import com.android.settingslib.widget.MainSwitchPreference;
@@ -116,7 +115,6 @@ public class PowerBackgroundUsageDetail extends DashboardFragment
public void onPause() {
super.onPause();
- notifyBackupManager();
final int currentOptimizeMode = mBatteryOptimizeUtils.getAppOptimizationMode();
mLogStringBuilder.append(", onPause mode = ").append(currentOptimizeMode);
logMetricCategory(currentOptimizeMode);
@@ -183,13 +181,6 @@ public class PowerBackgroundUsageDetail extends DashboardFragment
onRadioButtonClicked(isEnabled ? mOptimizePreference : null);
}
- @VisibleForTesting
- void notifyBackupManager() {
- if (mOptimizationMode != mBatteryOptimizeUtils.getAppOptimizationMode()) {
- BatterySettingsStorage.get(getContext()).notifyChange(ChangeReason.UPDATE);
- }
- }
-
@VisibleForTesting
int getSelectedPreference() {
if (!mMainSwitchPreference.isChecked()) {
diff --git a/src/com/android/settings/inputmethod/TrackpadTapDraggingPreferenceController.java b/src/com/android/settings/inputmethod/TrackpadTapDraggingPreferenceController.java
index 28c2915e4d7..30253a8a30f 100644
--- a/src/com/android/settings/inputmethod/TrackpadTapDraggingPreferenceController.java
+++ b/src/com/android/settings/inputmethod/TrackpadTapDraggingPreferenceController.java
@@ -16,16 +16,22 @@
package com.android.settings.inputmethod;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.hardware.input.InputSettings;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
public class TrackpadTapDraggingPreferenceController extends TogglePreferenceController {
+ private MetricsFeatureProvider mMetricsFeatureProvider;
+
public TrackpadTapDraggingPreferenceController(Context context, String key) {
super(context, key);
+ mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
}
@Override
@@ -36,7 +42,8 @@ public class TrackpadTapDraggingPreferenceController extends TogglePreferenceCon
@Override
public boolean setChecked(boolean isChecked) {
InputSettings.setTouchpadTapDragging(mContext, isChecked);
- // TODO(b/321978150): add a metric for tap dragging settings changes.
+ mMetricsFeatureProvider.action(
+ mContext, SettingsEnums.ACTION_GESTURE_TAP_DRAGGING_CHANGED, isChecked);
return true;
}
diff --git a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
index a0c363a206b..5a2a3947621 100644
--- a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
+++ b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
@@ -44,13 +44,13 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.settings.R
import com.android.settings.network.SubscriptionInfoListViewModel
import com.android.settings.network.telephony.MobileNetworkUtils
+import com.android.settings.spa.network.PrimarySimRepository.PrimarySimInfo
import com.android.settings.wifi.WifiPickerTrackerHelper
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
-import com.android.settingslib.spa.widget.preference.ListPreferenceOption
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreference
@@ -173,27 +173,23 @@ fun PageImpl(
) {
val selectableSubscriptionInfoList by selectableSubscriptionInfoListFlow
.collectAsStateWithLifecycle(initialValue = emptyList())
- val activeSubscriptionInfoList: List =
- selectableSubscriptionInfoList.filter { subscriptionInfo ->
- subscriptionInfo.simSlotIndex != -1
- }
val stringSims = stringResource(R.string.provider_network_settings_title)
RegularScaffold(title = stringSims) {
SimsSection(selectableSubscriptionInfoList)
PrimarySimSectionImpl(
- activeSubscriptionInfoList,
- defaultVoiceSubId,
- defaultSmsSubId,
- defaultDataSubId,
- nonDds
+ selectableSubscriptionInfoListFlow,
+ defaultVoiceSubId,
+ defaultSmsSubId,
+ defaultDataSubId,
+ nonDds
)
}
}
@Composable
fun PrimarySimImpl(
- subscriptionInfoList: List,
+ primarySimInfo: PrimarySimInfo,
callsSelectedId: MutableIntState,
textsSelectedId: MutableIntState,
mobileDataSelectedId: MutableIntState,
@@ -237,108 +233,83 @@ fun PrimarySimImpl(
}
},
) {
- var state = rememberSaveable { mutableStateOf(false) }
- var callsAndSmsList = remember {
- mutableListOf(ListPreferenceOption(id = -1, text = "Loading"))
- }
- var dataList = remember {
- mutableListOf(ListPreferenceOption(id = -1, text = "Loading"))
+ val telephonyManagerForNonDds: TelephonyManager? =
+ context.getSystemService(TelephonyManager::class.java)
+ ?.createForSubscriptionId(nonDds.intValue)
+ val automaticDataChecked = rememberSaveable() {
+ mutableStateOf(false)
}
- if (subscriptionInfoList.size >= 2) {
- state.value = true
- callsAndSmsList.clear()
- dataList.clear()
- for (info in subscriptionInfoList) {
- var item = ListPreferenceOption(
- id = info.subscriptionId,
- text = "${info.displayName}",
- summary = "${info.number}"
- )
- callsAndSmsList.add(item)
- dataList.add(item)
- }
- callsAndSmsList.add(
- ListPreferenceOption(
- id = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
- text = stringResource(id = R.string.sim_calls_ask_first_prefs_title)
- )
- )
- } else {
- // hide the primary sim
- state.value = false
- Log.d(NetworkCellularGroupProvider.name, "Hide primary sim")
- }
+ CreatePrimarySimListPreference(
+ stringResource(id = R.string.primary_sim_calls_title),
+ primarySimInfo.callsAndSmsList,
+ callsSelectedId,
+ ImageVector.vectorResource(R.drawable.ic_phone),
+ actionSetCalls
+ )
+ CreatePrimarySimListPreference(
+ stringResource(id = R.string.primary_sim_texts_title),
+ primarySimInfo.callsAndSmsList,
+ textsSelectedId,
+ Icons.AutoMirrored.Outlined.Message,
+ actionSetTexts
+ )
+ CreatePrimarySimListPreference(
+ stringResource(id = R.string.mobile_data_settings_title),
+ primarySimInfo.dataList,
+ mobileDataSelectedId,
+ Icons.Outlined.DataUsage,
+ actionSetMobileData
+ )
- if (state.value) {
- val telephonyManagerForNonDds: TelephonyManager? =
- context.getSystemService(TelephonyManager::class.java)
- ?.createForSubscriptionId(nonDds.intValue)
- val automaticDataChecked = rememberSaveable() {
- mutableStateOf(false)
- }
-
- CreatePrimarySimListPreference(
- stringResource(id = R.string.primary_sim_calls_title),
- callsAndSmsList,
- callsSelectedId,
- ImageVector.vectorResource(R.drawable.ic_phone),
- actionSetCalls
- )
- CreatePrimarySimListPreference(
- stringResource(id = R.string.primary_sim_texts_title),
- callsAndSmsList,
- textsSelectedId,
- Icons.AutoMirrored.Outlined.Message,
- actionSetTexts
- )
- CreatePrimarySimListPreference(
- stringResource(id = R.string.mobile_data_settings_title),
- dataList,
- mobileDataSelectedId,
- Icons.Outlined.DataUsage,
- actionSetMobileData
- )
-
- val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title)
- val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg)
- SwitchPreference(
- object : SwitchPreferenceModel {
- override val title = autoDataTitle
- override val summary = { autoDataSummary }
- override val checked = {
- if (nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- coroutineScope.launch {
- automaticDataChecked.value = getAutomaticData(telephonyManagerForNonDds)
- Log.d(
- NetworkCellularGroupProvider.name,
- "NonDds:${nonDds.intValue}" +
- "getAutomaticData:${automaticDataChecked.value}"
- )
- }
+ val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title)
+ val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg)
+ SwitchPreference(
+ object : SwitchPreferenceModel {
+ override val title = autoDataTitle
+ override val summary = { autoDataSummary }
+ override val checked = {
+ if (nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ coroutineScope.launch {
+ automaticDataChecked.value = getAutomaticData(telephonyManagerForNonDds)
+ Log.d(
+ NetworkCellularGroupProvider.name,
+ "NonDds:${nonDds.intValue}" +
+ "getAutomaticData:${automaticDataChecked.value}"
+ )
}
- automaticDataChecked.value
- }
- override val onCheckedChange: ((Boolean) -> Unit)? = {
- automaticDataChecked.value = it
- actionSetAutoDataSwitch(it)
}
+ automaticDataChecked.value
}
- )
- }
+ override val onCheckedChange: ((Boolean) -> Unit)? = {
+ automaticDataChecked.value = it
+ actionSetAutoDataSwitch(it)
+ }
+ }
+ )
}
@Composable
fun PrimarySimSectionImpl(
- subscriptionInfoList: List,
+ subscriptionInfoListFlow: Flow>,
callsSelectedId: MutableIntState,
textsSelectedId: MutableIntState,
mobileDataSelectedId: MutableIntState,
nonDds: MutableIntState,
) {
+ val context = LocalContext.current
+ val primarySimInfo = remember(subscriptionInfoListFlow) {
+ subscriptionInfoListFlow
+ .map { subscriptionInfoList ->
+ subscriptionInfoList.filter { subInfo -> subInfo.simSlotIndex != -1 }
+ }
+ .map(PrimarySimRepository(context)::getPrimarySimInfo)
+ .flowOn(Dispatchers.Default)
+ }.collectAsStateWithLifecycle(initialValue = null).value ?: return
+
Category(title = stringResource(id = R.string.primary_sim_title)) {
PrimarySimImpl(
- subscriptionInfoList,
+ primarySimInfo,
callsSelectedId,
textsSelectedId,
mobileDataSelectedId,
diff --git a/src/com/android/settings/spa/network/PrimarySimRepository.kt b/src/com/android/settings/spa/network/PrimarySimRepository.kt
new file mode 100644
index 00000000000..b9eb3ffcb0a
--- /dev/null
+++ b/src/com/android/settings/spa/network/PrimarySimRepository.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.spa.network
+
+import android.content.Context
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.util.Log
+import com.android.settings.R
+import com.android.settings.network.SubscriptionUtil
+import com.android.settingslib.spa.widget.preference.ListPreferenceOption
+
+class PrimarySimRepository(private val context: Context) {
+
+ data class PrimarySimInfo(
+ val callsAndSmsList: List,
+ val dataList: List,
+ )
+
+ fun getPrimarySimInfo(selectableSubscriptionInfoList: List): PrimarySimInfo? {
+ if (selectableSubscriptionInfoList.size < 2) {
+ Log.d(TAG, "Hide primary sim")
+ return null
+ }
+
+ val callsAndSmsList = mutableListOf()
+ val dataList = mutableListOf()
+ for (info in selectableSubscriptionInfoList) {
+ val item = ListPreferenceOption(
+ id = info.subscriptionId,
+ text = "${info.displayName}",
+ summary = SubscriptionUtil.getFormattedPhoneNumber(context, info) ?: "",
+ )
+ callsAndSmsList += item
+ dataList += item
+ }
+ callsAndSmsList += ListPreferenceOption(
+ id = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ text = context.getString(R.string.sim_calls_ask_first_prefs_title),
+ )
+
+ return PrimarySimInfo(callsAndSmsList, dataList)
+ }
+
+ private companion object {
+ private const val TAG = "PrimarySimRepository"
+ }
+}
diff --git a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt
index b9849666e53..a8c0575e3e8 100644
--- a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt
+++ b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt
@@ -24,10 +24,13 @@ import androidx.compose.material.icons.outlined.SignalCellularAlt
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settings.network.SimOnboardingService
import com.android.settingslib.spa.framework.theme.SettingsDimension
@@ -38,6 +41,9 @@ import com.android.settingslib.spa.widget.scaffold.BottomAppBarButton
import com.android.settingslib.spa.widget.scaffold.SuwScaffold
import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SettingsIcon
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
/**
* the sim onboarding primary sim compose
@@ -77,13 +83,19 @@ fun SimOnboardingPrimarySimImpl(
SettingsBody(stringResource(id = R.string.sim_onboarding_primary_sim_msg))
}
- var selectedSubscriptionInfoList =
- onboardingService.getSelectedSubscriptionInfoListWithRenaming()
+ val context = LocalContext.current
+ val primarySimInfo = remember {
+ flow {
+ val selectableSubInfoList =
+ onboardingService.getSelectedSubscriptionInfoListWithRenaming()
+ emit(PrimarySimRepository(context).getPrimarySimInfo(selectableSubInfoList))
+ }.flowOn(Dispatchers.Default)
+ }.collectAsStateWithLifecycle(initialValue = null).value ?: return@SuwScaffold
callsSelectedId.intValue = onboardingService.targetPrimarySimCalls
textsSelectedId.intValue = onboardingService.targetPrimarySimTexts
mobileDataSelectedId.intValue = onboardingService.targetPrimarySimMobileData
PrimarySimImpl(
- subscriptionInfoList = selectedSubscriptionInfoList,
+ primarySimInfo = primarySimInfo,
callsSelectedId = callsSelectedId,
textsSelectedId = textsSelectedId,
mobileDataSelectedId = mobileDataSelectedId,
diff --git a/src/com/android/settings/spa/network/SimsSection.kt b/src/com/android/settings/spa/network/SimsSection.kt
index 334ca61bc9b..9e4cf9f4c12 100644
--- a/src/com/android/settings/spa/network/SimsSection.kt
+++ b/src/com/android/settings/spa/network/SimsSection.kt
@@ -36,10 +36,10 @@ import com.android.settings.network.telephony.isSubscriptionEnabledFlow
import com.android.settings.network.telephony.phoneNumberFlow
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
import com.android.settingslib.spa.widget.ui.SettingsIcon
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference
+import com.android.settingslib.spaprivileged.template.preference.RestrictedTwoTargetSwitchPreference
@Composable
fun SimsSection(subscriptionInfoList: List) {
@@ -61,9 +61,8 @@ private fun SimPreference(subInfo: SubscriptionInfo) {
val phoneNumber = remember(subInfo) {
context.phoneNumberFlow(subInfo)
}.collectAsStateWithLifecycle(initialValue = null)
- //TODO: Add the Restricted TwoTargetSwitchPreference in SPA
- TwoTargetSwitchPreference(
- object : SwitchPreferenceModel {
+ RestrictedTwoTargetSwitchPreference(
+ model = object : SwitchPreferenceModel {
override val title = subInfo.displayName.toString()
override val summary = { phoneNumber.value ?: "" }
override val checked = { checked.value }
@@ -74,7 +73,8 @@ private fun SimPreference(subInfo: SubscriptionInfo) {
newChecked,
)
}
- }
+ },
+ restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)),
) {
MobileNetworkUtils.launchMobileNetworkSettings(context, subInfo)
}
diff --git a/tests/robotests/src/com/android/settings/applications/AppInfoWithHeaderTest.java b/tests/robotests/src/com/android/settings/applications/AppInfoWithHeaderTest.java
index ce520271de6..562212e3569 100644
--- a/tests/robotests/src/com/android/settings/applications/AppInfoWithHeaderTest.java
+++ b/tests/robotests/src/com/android/settings/applications/AppInfoWithHeaderTest.java
@@ -19,6 +19,7 @@ package com.android.settings.applications;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -48,12 +49,13 @@ import com.android.settingslib.widget.LayoutPreference;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@@ -62,6 +64,8 @@ import org.robolectric.util.ReflectionHelpers;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowEntityHeaderController.class, ShadowSettingsLibUtils.class})
public class AppInfoWithHeaderTest {
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private EntityHeaderController mHeaderController;
@@ -71,7 +75,6 @@ public class AppInfoWithHeaderTest {
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
mFactory = FakeFeatureFactory.setupForTest();
when(mFactory.metricsFeatureProvider.getMetricsCategory(any(Object.class)))
.thenReturn(MetricsProto.MetricsEvent.SETTINGS_APP_NOTIF_CATEGORY);
@@ -120,7 +123,6 @@ public class AppInfoWithHeaderTest {
assertThat(mAppInfoWithHeader.mPackageRemovedCalled).isTrue();
}
- @Ignore("b/315135755")
@Test
public void noExtraUserHandleInIntent_retrieveAppEntryWithMyUserId()
throws PackageManager.NameNotFoundException {
@@ -133,10 +135,8 @@ public class AppInfoWithHeaderTest {
when(mAppInfoWithHeader.mState.getEntry(packageName,
UserHandle.myUserId())).thenReturn(entry);
- when(mAppInfoWithHeader.mPm.getPackageInfoAsUser(entry.info.packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS |
- PackageManager.GET_SIGNING_CERTIFICATES |
- PackageManager.GET_PERMISSIONS, UserHandle.myUserId())).thenReturn(
+ when(mAppInfoWithHeader.mPm.getPackageInfoAsUser(eq(entry.info.packageName),
+ any(), eq(UserHandle.myUserId()))).thenReturn(
mAppInfoWithHeader.mPackageInfo);
mAppInfoWithHeader.retrieveAppEntry();
@@ -146,7 +146,6 @@ public class AppInfoWithHeaderTest {
assertThat(mAppInfoWithHeader.mAppEntry).isNotNull();
}
- @Ignore("b/315135755")
@Test
public void extraUserHandleInIntent_retrieveAppEntryWithMyUserId()
throws PackageManager.NameNotFoundException {
@@ -161,10 +160,8 @@ public class AppInfoWithHeaderTest {
entry.info.packageName = packageName;
when(mAppInfoWithHeader.mState.getEntry(packageName, USER_ID)).thenReturn(entry);
- when(mAppInfoWithHeader.mPm.getPackageInfoAsUser(entry.info.packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS |
- PackageManager.GET_SIGNING_CERTIFICATES |
- PackageManager.GET_PERMISSIONS, USER_ID)).thenReturn(
+ when(mAppInfoWithHeader.mPm.getPackageInfoAsUser(eq(entry.info.packageName),
+ any(), eq(USER_ID))).thenReturn(
mAppInfoWithHeader.mPackageInfo);
mAppInfoWithHeader.retrieveAppEntry();
@@ -232,6 +229,8 @@ public class AppInfoWithHeaderTest {
}
@Override
- protected Intent getIntent() { return mIntent; }
+ protected Intent getIntent() {
+ return mIntent;
+ }
}
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java
new file mode 100644
index 00000000000..c08bb98e55b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java
@@ -0,0 +1,270 @@
+/*
+ * 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.bluetooth;
+
+import static android.bluetooth.BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+import static android.bluetooth.BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+
+import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
+import static com.android.settings.bluetooth.BluetoothDetailsHearingAidsPresetsController.KEY_HEARING_AIDS_PRESETS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothHapPresetInfo;
+
+import androidx.preference.ListPreference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.HapClientProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+
+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 java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/** Tests for {@link BluetoothDetailsHearingAidsPresetsController}. */
+@RunWith(RobolectricTestRunner.class)
+public class BluetoothDetailsHearingAidsPresetsControllerTest extends
+ BluetoothDetailsControllerTestBase {
+
+ private static final int TEST_PRESET_INDEX = 1;
+ private static final String TEST_PRESET_NAME = "test_preset";
+ private static final int TEST_HAP_GROUP_ID = 1;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private LocalBluetoothManager mLocalManager;
+ @Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private HapClientProfile mHapClientProfile;
+ @Mock
+ private CachedBluetoothDevice mCachedChildDevice;
+ @Mock
+ private BluetoothDevice mChildDevice;
+
+ private BluetoothDetailsHearingAidsPresetsController mController;
+
+ @Override
+ public void setUp() {
+ super.setUp();
+
+ when(mLocalManager.getProfileManager()).thenReturn(mProfileManager);
+ when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
+ when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
+ when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
+ when(mCachedChildDevice.getDevice()).thenReturn(mChildDevice);
+ PreferenceCategory deviceControls = new PreferenceCategory(mContext);
+ deviceControls.setKey(KEY_HEARING_DEVICE_GROUP);
+ mScreen.addPreference(deviceControls);
+ mController = new BluetoothDetailsHearingAidsPresetsController(mContext, mFragment,
+ mLocalManager, mCachedDevice, mLifecycle);
+ mController.init(mScreen);
+ }
+
+ @Test
+ public void isAvailable_supportHap_returnTrue() {
+ when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
+
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void isAvailable_notSupportHap_returnFalse() {
+ when(mCachedDevice.getProfiles()).thenReturn(new ArrayList<>());
+
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ @Test
+ public void onResume_registerCallback() {
+ mController.onResume();
+
+ verify(mHapClientProfile).registerCallback(any(Executor.class),
+ any(BluetoothHapClient.Callback.class));
+ }
+
+ @Test
+ public void onPause_unregisterCallback() {
+ mController.onPause();
+
+ verify(mHapClientProfile).unregisterCallback(any(BluetoothHapClient.Callback.class));
+ }
+
+ @Test
+ public void onPreferenceChange_keyMatched_verifyStatusUpdated() {
+ final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
+
+ boolean handled = mController.onPreferenceChange(presetPreference,
+ String.valueOf(TEST_PRESET_INDEX));
+
+ assertThat(handled).isTrue();
+ verify(presetPreference).setSummary(TEST_PRESET_NAME);
+ }
+
+ @Test
+ public void onPreferenceChange_keyNotMatched_doNothing() {
+ final ListPreference presetPreference = getTestPresetPreference("wrong_key");
+
+ boolean handled = mController.onPreferenceChange(
+ presetPreference, String.valueOf(TEST_PRESET_INDEX));
+
+ assertThat(handled).isFalse();
+ verify(presetPreference, never()).setSummary(any());
+ }
+
+ @Test
+ public void onPreferenceChange_supportGroupOperation_validGroupId_verifySelectPresetForGroup() {
+ final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
+
+ mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
+
+ verify(mHapClientProfile).selectPresetForGroup(TEST_HAP_GROUP_ID, TEST_PRESET_INDEX);
+ }
+
+ @Test
+ public void onPreferenceChange_notSupportGroupOperation_verifySelectPreset() {
+ final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
+
+ mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
+
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ }
+
+ @Test
+ public void onPreferenceChange_invalidGroupId_verifySelectPreset() {
+ final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(GROUP_ID_INVALID);
+
+ mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
+
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ }
+
+ @Test
+ public void onPreferenceChange_notSupportGroupOperation_hasSubDevice_verifyStatusUpdated() {
+ final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false);
+ when(mCachedDevice.getSubDevice()).thenReturn(mCachedChildDevice);
+
+ mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
+
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mChildDevice, TEST_PRESET_INDEX);
+ }
+
+ @Test
+ public void onPreferenceChange_notSupportGroupOperation_hasMemberDevice_verifyStatusUpdated() {
+ final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS);
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false);
+ when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mCachedChildDevice));
+
+ mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX));
+
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mChildDevice, TEST_PRESET_INDEX);
+ }
+
+ @Test
+ public void refresh_emptyPresetInfo_preferenceDisabled() {
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(new ArrayList<>());
+
+ mController.refresh();
+
+ assertThat(mController.getPreference()).isNotNull();
+ assertThat(mController.getPreference().isEnabled()).isFalse();
+ }
+
+ @Test
+ public void refresh_validPresetInfo_preferenceEnabled() {
+ BluetoothHapPresetInfo info = getTestPresetInfo();
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
+
+ mController.refresh();
+
+ assertThat(mController.getPreference()).isNotNull();
+ assertThat(mController.getPreference().isEnabled()).isTrue();
+ }
+
+ @Test
+ public void refresh_invalidActivePresetIndex_summaryIsNull() {
+ BluetoothHapPresetInfo info = getTestPresetInfo();
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(PRESET_INDEX_UNAVAILABLE);
+
+ mController.refresh();
+
+ assertThat(mController.getPreference()).isNotNull();
+ assertThat(mController.getPreference().getSummary()).isNull();
+ }
+
+ @Test
+ public void refresh_validActivePresetIndex_summaryIsNotNull() {
+ BluetoothHapPresetInfo info = getTestPresetInfo();
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
+
+ mController.refresh();
+
+ assertThat(mController.getPreference()).isNotNull();
+ assertThat(mController.getPreference().getSummary()).isNotNull();
+ }
+
+ private BluetoothHapPresetInfo getTestPresetInfo() {
+ BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class);
+ when(info.getName()).thenReturn(TEST_PRESET_NAME);
+ when(info.getIndex()).thenReturn(TEST_PRESET_INDEX);
+ return info;
+ }
+
+ private ListPreference getTestPresetPreference(String key) {
+ final ListPreference presetPreference = spy(new ListPreference(mContext));
+ when(presetPreference.findIndexOfValue(String.valueOf(TEST_PRESET_INDEX))).thenReturn(0);
+ when(presetPreference.getEntries()).thenReturn(new CharSequence[]{TEST_PRESET_NAME});
+ when(presetPreference.getEntryValues()).thenReturn(
+ new CharSequence[]{String.valueOf(TEST_PRESET_INDEX)});
+ presetPreference.setKey(key);
+ return presetPreference;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java
new file mode 100644
index 00000000000..2a50f892add
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import com.android.settings.accessibility.Flags;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+
+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;
+
+/** Tests for {@link BluetoothDetailsHearingDeviceController}. */
+@RunWith(RobolectricTestRunner.class)
+public class BluetoothDetailsHearingDeviceControllerTest extends
+ BluetoothDetailsControllerTestBase {
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private LocalBluetoothManager mLocalManager;
+ @Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private BluetoothDetailsHearingDeviceController mHearingDeviceController;
+ @Mock
+ private BluetoothDetailsHearingAidsPresetsController mPresetsController;
+ @Mock
+ private BluetoothDetailsHearingDeviceSettingsController mHearingDeviceSettingsController;
+
+ @Override
+ public void setUp() {
+ super.setUp();
+
+ when(mLocalManager.getProfileManager()).thenReturn(mProfileManager);
+ mHearingDeviceController = new BluetoothDetailsHearingDeviceController(mContext,
+ mFragment, mLocalManager, mCachedDevice, mLifecycle);
+ mHearingDeviceController.setSubControllers(mHearingDeviceSettingsController,
+ mPresetsController);
+ }
+
+ @Test
+ public void isAvailable_hearingDeviceSettingsAvailable_returnTrue() {
+ when(mHearingDeviceSettingsController.isAvailable()).thenReturn(true);
+
+ assertThat(mHearingDeviceController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void isAvailable_presetsControlsAvailable_returnTrue() {
+ when(mPresetsController.isAvailable()).thenReturn(true);
+
+ assertThat(mHearingDeviceController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void isAvailable_noControllersAvailable_returnFalse() {
+ when(mHearingDeviceSettingsController.isAvailable()).thenReturn(false);
+ when(mPresetsController.isAvailable()).thenReturn(false);
+
+ assertThat(mHearingDeviceController.isAvailable()).isFalse();
+ }
+
+
+ @Test
+ public void initSubControllers_launchFromHearingDevicePage_hearingDeviceSettingsNotExist() {
+ mHearingDeviceController.initSubControllers(true);
+
+ assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch(
+ c -> c instanceof BluetoothDetailsHearingDeviceSettingsController)).isFalse();
+ }
+
+ @Test
+ public void initSubControllers_notLaunchFromHearingDevicePage_hearingDeviceSettingsExist() {
+ mHearingDeviceController.initSubControllers(false);
+
+ assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch(
+ c -> c instanceof BluetoothDetailsHearingDeviceSettingsController)).isTrue();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HEARING_AID_PRESET_CONTROL)
+ public void initSubControllers_flagEnabled_presetControllerExist() {
+ mHearingDeviceController.initSubControllers(false);
+
+ assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch(
+ c -> c instanceof BluetoothDetailsHearingAidsPresetsController)).isTrue();
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_HEARING_AID_PRESET_CONTROL)
+ public void initSubControllers_flagDisabled_presetControllerNotExist() {
+ mHearingDeviceController.initSubControllers(false);
+
+ assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch(
+ c -> c instanceof BluetoothDetailsHearingAidsPresetsController)).isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsControllerTest.java
similarity index 81%
rename from tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java
rename to tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsControllerTest.java
index 364d299e519..b420717d397 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsControllerTest.java
@@ -39,23 +39,24 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
-/** Tests for {@link BluetoothDetailsHearingDeviceControlsController}. */
+/** Tests for {@link BluetoothDetailsHearingDeviceSettingsController}. */
@RunWith(RobolectricTestRunner.class)
-public class BluetoothDetailsHearingDeviceControlsControllerTest extends
+public class BluetoothDetailsHearingDeviceSettingsControllerTest extends
BluetoothDetailsControllerTestBase {
+
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@Captor
private ArgumentCaptor mIntentArgumentCaptor;
- private BluetoothDetailsHearingDeviceControlsController mController;
+ private BluetoothDetailsHearingDeviceSettingsController mController;
@Override
public void setUp() {
super.setUp();
FakeFeatureFactory.setupForTest();
- mController = new BluetoothDetailsHearingDeviceControlsController(mActivity, mFragment,
+ mController = new BluetoothDetailsHearingDeviceSettingsController(mActivity, mFragment,
mCachedDevice, mLifecycle);
when(mCachedDevice.isHearingAidDevice()).thenReturn(true);
}
@@ -75,12 +76,12 @@ public class BluetoothDetailsHearingDeviceControlsControllerTest extends
}
@Test
- public void onPreferenceClick_hearingDeviceControlsKey_LaunchExpectedFragment() {
- final Preference hearingControlsKeyPreference = new Preference(mContext);
- hearingControlsKeyPreference.setKey(
- BluetoothDetailsHearingDeviceControlsController.KEY_HEARING_DEVICE_CONTROLS);
+ public void onPreferenceClick_hearingDeviceSettingsKey_launchExpectedFragment() {
+ final Preference hearingDeviceSettingsPreference = new Preference(mContext);
+ hearingDeviceSettingsPreference.setKey(
+ BluetoothDetailsHearingDeviceSettingsController.KEY_HEARING_DEVICE_SETTINGS);
- mController.onPreferenceClick(hearingControlsKeyPreference);
+ mController.onPreferenceClick(hearingDeviceSettingsPreference);
assertStartActivityWithExpectedFragment(mActivity,
AccessibilityHearingAidsFragment.class.getName());
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
index fc72c412b6e..50aa7719ccb 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
@@ -18,7 +18,7 @@ package com.android.settings.bluetooth;
import static android.bluetooth.BluetoothDevice.BOND_NONE;
-import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceControlsController.KEY_DEVICE_CONTROLS_GENERAL_GROUP;
+import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceSettingsController.KEY_HEARING_DEVICE_SETTINGS;
import static com.google.common.truth.Truth.assertThat;
@@ -237,7 +237,7 @@ public class BluetoothDeviceDetailsFragmentTest {
assertThat(controllerList.stream()
.anyMatch(controller -> controller.getPreferenceKey().equals(
- KEY_DEVICE_CONTROLS_GENERAL_GROUP))).isFalse();
+ KEY_HEARING_DEVICE_SETTINGS))).isFalse();
}
@Test
@@ -253,7 +253,7 @@ public class BluetoothDeviceDetailsFragmentTest {
assertThat(controllerList.stream()
.anyMatch(controller -> controller.getPreferenceKey().equals(
- KEY_DEVICE_CONTROLS_GENERAL_GROUP))).isTrue();
+ KEY_HEARING_DEVICE_SETTINGS))).isTrue();
}
private InputDevice createInputDeviceWithMatchingBluetoothAddress() {
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java
index 0648de4ad69..80739e9d47a 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java
@@ -60,12 +60,8 @@ import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-import com.android.settingslib.datastore.ChangeReason;
-import com.android.settingslib.datastore.Observer;
import com.android.settingslib.widget.LayoutPreference;
-import com.google.common.util.concurrent.MoreExecutors;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -119,10 +115,8 @@ public class AdvancedPowerUsageDetailTest {
@Mock private AppOpsManager mAppOpsManager;
@Mock private LoaderManager mLoaderManager;
@Mock private BatteryOptimizeUtils mBatteryOptimizeUtils;
- @Mock private Observer mObserver;
private Context mContext;
- private BatterySettingsStorage mBatterySettingsStorage;
private PrimarySwitchPreference mAllowBackgroundUsagePreference;
private AdvancedPowerUsageDetail mFragment;
private SettingsActivity mTestActivity;
@@ -134,7 +128,6 @@ public class AdvancedPowerUsageDetailTest {
@Before
public void setUp() {
mContext = spy(ApplicationProvider.getApplicationContext());
- mBatterySettingsStorage = BatterySettingsStorage.get(mContext);
when(mContext.getPackageName()).thenReturn("foo");
mFeatureFactory = FakeFeatureFactory.setupForTest();
mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider;
@@ -448,28 +441,4 @@ public class AdvancedPowerUsageDetailTest {
TimeUnit.SECONDS.sleep(1);
verifyNoInteractions(mMetricsFeatureProvider);
}
-
- @Test
- public void notifyBackupManager_optimizationModeIsNotChanged_notInvokeDataChanged() {
- mBatterySettingsStorage.addObserver(mObserver, MoreExecutors.directExecutor());
- final int mode = BatteryOptimizeUtils.MODE_RESTRICTED;
- mFragment.mOptimizationMode = mode;
- when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(mode);
-
- mFragment.notifyBackupManager();
-
- verifyNoInteractions(mObserver);
- }
-
- @Test
- public void notifyBackupManager_optimizationModeIsChanged_invokeDataChanged() {
- mBatterySettingsStorage.addObserver(mObserver, MoreExecutors.directExecutor());
- mFragment.mOptimizationMode = BatteryOptimizeUtils.MODE_RESTRICTED;
- when(mBatteryOptimizeUtils.getAppOptimizationMode())
- .thenReturn(BatteryOptimizeUtils.MODE_UNRESTRICTED);
-
- mFragment.notifyBackupManager();
-
- verify(mObserver).onChanged(ChangeReason.UPDATE);
- }
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java
index 3551eeb431e..6085b9a3ce4 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java
@@ -49,8 +49,12 @@ import android.os.UserManager;
import android.util.ArraySet;
import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
+import com.android.settingslib.datastore.ChangeReason;
+import com.android.settingslib.datastore.Observer;
import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
+import com.google.common.util.concurrent.MoreExecutors;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -74,14 +78,18 @@ public class BatteryOptimizeUtilsTest {
@Mock private PowerAllowlistBackend mMockBackend;
@Mock private IPackageManager mMockIPackageManager;
@Mock private UserManager mMockUserManager;
+ @Mock private Observer mObserver;
private Context mContext;
private BatteryOptimizeUtils mBatteryOptimizeUtils;
+ private BatterySettingsStorage mBatterySettingsStorage;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
+ mBatterySettingsStorage = BatterySettingsStorage.get(mContext);
+ mBatterySettingsStorage.addObserver(mObserver, MoreExecutors.directExecutor());
mBatteryOptimizeUtils = spy(new BatteryOptimizeUtils(mContext, UID, PACKAGE_NAME));
mBatteryOptimizeUtils.mAppOpsManager = mMockAppOpsManager;
mBatteryOptimizeUtils.mBatteryUtils = mMockBatteryUtils;
@@ -156,6 +164,7 @@ public class BatteryOptimizeUtilsTest {
TimeUnit.SECONDS.sleep(1);
verifySetAppOptimizationMode(AppOpsManager.MODE_IGNORED, /* allowListed */ false);
+ verify(mObserver).onChanged(ChangeReason.UPDATE);
}
@Test
@@ -169,6 +178,7 @@ public class BatteryOptimizeUtilsTest {
TimeUnit.SECONDS.sleep(1);
verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ true);
+ verify(mObserver).onChanged(ChangeReason.UPDATE);
}
@Test
@@ -182,6 +192,7 @@ public class BatteryOptimizeUtilsTest {
TimeUnit.SECONDS.sleep(1);
verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false);
+ verify(mObserver).onChanged(ChangeReason.UPDATE);
}
@Test
@@ -197,6 +208,7 @@ public class BatteryOptimizeUtilsTest {
verify(mMockBatteryUtils, never()).setForceAppStandby(anyInt(), anyString(), anyInt());
verify(mMockBackend, never()).addApp(anyString());
verify(mMockBackend, never()).removeApp(anyString());
+ verifyNoInteractions(mObserver);
}
@Test
@@ -288,6 +300,7 @@ public class BatteryOptimizeUtilsTest {
inOrder.verify(mMockBackend).isAllowlisted(PACKAGE_NAME, UID);
inOrder.verify(mMockBackend).isSysAllowlisted(PACKAGE_NAME);
verifyNoMoreInteractions(mMockBackend);
+ verify(mObserver).onChanged(ChangeReason.DELETE);
}
@Test
@@ -298,6 +311,7 @@ public class BatteryOptimizeUtilsTest {
/* isSystemOrDefaultApp */ false);
verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false);
+ verify(mObserver).onChanged(ChangeReason.DELETE);
}
@Test
@@ -308,6 +322,7 @@ public class BatteryOptimizeUtilsTest {
/* isSystemOrDefaultApp */ false);
verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false);
+ verify(mObserver).onChanged(ChangeReason.DELETE);
}
private void runTestForResetWithMode(