From 37e1fef9d41236ebfad85f197c01beb0dc6a64f7 Mon Sep 17 00:00:00 2001 From: Alice Kuo Date: Wed, 3 Jan 2024 07:07:08 +0800 Subject: [PATCH 1/2] Change the switcher to switch LE audio mode with broadcast Change the previous toggle design to list option for broadcast feature. The new toggle change is only applied as broadcast feature enabled whcih is behind a feature flag. Bug: 273153850 Test: make RunSettingsRoboTests ROBOTEST_FILTER=BluetoothLeAudioPreferenceControllerTest Change-Id: Ic2ea10d9d9529a2d413525c1b660f8fbac371502 --- res/values/arrays.xml | 20 +++ res/values/strings.xml | 3 +- res/xml/development_settings.xml | 7 + ...etoothLeAudioModePreferenceController.java | 137 ++++++++++++++++++ .../BluetoothLeAudioPreferenceController.java | 7 + .../DevelopmentSettingsDashboardFragment.java | 11 ++ 6 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 357818cd137..0e35fed6a11 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -233,6 +233,26 @@ + + + + Disabled + + Unicast + + Unicast and Broadcast + + + + + + disabled + + unicast + + broadcast + + Use System Default: %1$d diff --git a/res/values/strings.xml b/res/values/strings.xml index cbc1f957196..863b86e4484 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -249,7 +249,8 @@ Disable Bluetooth LE audio Disables Bluetooth LE audio feature if the device supports LE audio hardware capabilities. - + + Bluetooth LE Audio mode Show LE audio toggle in Device Details diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index d44927fe512..fb5e2809434 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -373,6 +373,13 @@ android:title="@string/bluetooth_disable_leaudio" android:summary="@string/bluetooth_disable_leaudio_summary" /> + + diff --git a/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java new file mode 100644 index 00000000000..2298c63cc4f --- /dev/null +++ b/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java @@ -0,0 +1,137 @@ +/* + * Copyright 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.development; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothStatusCodes; +import android.content.Context; +import android.os.SystemProperties; +import android.sysprop.BluetoothProperties; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.ListPreference; +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.development.DeveloperOptionsPreferenceController; + + +/** + * Preference controller to control Bluetooth LE audio mode + */ +public class BluetoothLeAudioModePreferenceController + extends DeveloperOptionsPreferenceController + implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin { + + private static final String PREFERENCE_KEY = "bluetooth_leaudio_mode"; + + static final String LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY = + "persist.bluetooth.leaudio_dynamic_switcher.mode"; + + @Nullable private final DevelopmentSettingsDashboardFragment mFragment; + + private final String[] mListValues; + private final String[] mListSummaries; + @Nullable private String mNewMode; + + BluetoothAdapter mBluetoothAdapter; + + boolean mChanged = false; + + public BluetoothLeAudioModePreferenceController(@NonNull Context context, + @Nullable DevelopmentSettingsDashboardFragment fragment) { + super(context); + mFragment = fragment; + mBluetoothAdapter = context.getSystemService(BluetoothManager.class).getAdapter(); + + mListValues = context.getResources().getStringArray(R.array.bluetooth_leaudio_mode_values); + mListSummaries = context.getResources().getStringArray(R.array.bluetooth_leaudio_mode); + } + + @Override + @NonNull public String getPreferenceKey() { + return PREFERENCE_KEY; + } + + @Override + public boolean isAvailable() { + return BluetoothProperties.isProfileBapBroadcastSourceEnabled().orElse(false); + } + + @Override + public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) { + if (mFragment == null) { + return false; + } + + BluetoothRebootDialog.show(mFragment); + mChanged = true; + mNewMode = newValue.toString(); + return false; + } + + @Override + public void updateState(@NonNull Preference preference) { + if (mBluetoothAdapter == null) { + return; + } + + if (mBluetoothAdapter.isLeAudioBroadcastSourceSupported() + == BluetoothStatusCodes.FEATURE_SUPPORTED) { + SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, "broadcast"); + } else if (mBluetoothAdapter.isLeAudioSupported() + == BluetoothStatusCodes.FEATURE_SUPPORTED) { + SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, "unicast"); + } else { + SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, "disabled"); + } + + final String currentValue = SystemProperties.get(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY); + int index = 0; + for (int i = 0; i < mListValues.length; i++) { + if (TextUtils.equals(currentValue, mListValues[i])) { + index = i; + break; + } + } + + final ListPreference listPreference = (ListPreference) preference; + listPreference.setValue(mListValues[index]); + listPreference.setSummary(mListSummaries[index]); + } + + /** + * Called when the RebootDialog confirm is clicked. + */ + public void onRebootDialogConfirmed() { + if (!mChanged) { + return; + } + SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mNewMode); + } + + /** + * Called when the RebootDialog cancel is clicked. + */ + public void onRebootDialogCanceled() { + mChanged = false; + } +} diff --git a/src/com/android/settings/development/BluetoothLeAudioPreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioPreferenceController.java index f1b81b4a2d6..2a544f263e3 100644 --- a/src/com/android/settings/development/BluetoothLeAudioPreferenceController.java +++ b/src/com/android/settings/development/BluetoothLeAudioPreferenceController.java @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothStatusCodes; import android.content.Context; import android.os.SystemProperties; +import android.sysprop.BluetoothProperties; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; @@ -64,6 +65,12 @@ public class BluetoothLeAudioPreferenceController return PREFERENCE_KEY; } + @Override + public boolean isAvailable() { + return BluetoothProperties.isProfileBapUnicastClientEnabled().orElse(false) + && !BluetoothProperties.isProfileBapBroadcastSourceEnabled().orElse(false); + } + @Override public boolean onPreferenceChange(Preference preference, Object newValue) { BluetoothRebootDialog.show(mFragment); diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 73567bcf392..504eda8132b 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -454,6 +454,11 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra getDevelopmentOptionsController( BluetoothLeAudioPreferenceController.class); leAudioFeatureController.onRebootDialogConfirmed(); + + final BluetoothLeAudioModePreferenceController leAudioModeController = + getDevelopmentOptionsController( + BluetoothLeAudioModePreferenceController.class); + leAudioModeController.onRebootDialogConfirmed(); } @Override @@ -471,6 +476,11 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra getDevelopmentOptionsController( BluetoothLeAudioPreferenceController.class); leAudioFeatureController.onRebootDialogCanceled(); + + final BluetoothLeAudioModePreferenceController leAudioModeController = + getDevelopmentOptionsController( + BluetoothLeAudioModePreferenceController.class); + leAudioModeController.onRebootDialogCanceled(); } @Override @@ -670,6 +680,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controllers.add(new BluetoothAvrcpVersionPreferenceController(context)); controllers.add(new BluetoothMapVersionPreferenceController(context)); controllers.add(new BluetoothLeAudioPreferenceController(context, fragment)); + controllers.add(new BluetoothLeAudioModePreferenceController(context, fragment)); controllers.add(new BluetoothLeAudioDeviceDetailsPreferenceController(context)); controllers.add(new BluetoothLeAudioAllowListPreferenceController(context, fragment)); controllers.add(new BluetoothA2dpHwOffloadPreferenceController(context, fragment)); From fdbdc16285f4c36a880aca0a2b5e7ceff6076900 Mon Sep 17 00:00:00 2001 From: Alice Kuo Date: Sat, 13 Jan 2024 05:15:18 +0800 Subject: [PATCH 2/2] Add BluetoothLeAudioModePreferenceControllerTest Bug: 273153850 Test: make RunSettingsRoboTests ROBOTEST_FILTER=BluetoothLeAudioModePreferenceControllerTest Change-Id: I967764848c953a9156981e121e924d5a18617d2f --- ...etoothLeAudioModePreferenceController.java | 6 +- ...thLeAudioModePreferenceControllerTest.java | 108 ++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/development/BluetoothLeAudioModePreferenceControllerTest.java diff --git a/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java index 2298c63cc4f..06cfe65043e 100644 --- a/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java +++ b/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java @@ -26,6 +26,7 @@ import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.preference.ListPreference; import androidx.preference.Preference; @@ -50,8 +51,9 @@ public class BluetoothLeAudioModePreferenceController private final String[] mListValues; private final String[] mListSummaries; - @Nullable private String mNewMode; - + @VisibleForTesting + @Nullable String mNewMode; + @VisibleForTesting BluetoothAdapter mBluetoothAdapter; boolean mChanged = false; diff --git a/tests/robotests/src/com/android/settings/development/BluetoothLeAudioModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioModePreferenceControllerTest.java new file mode 100644 index 00000000000..f35fb17f8ca --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioModePreferenceControllerTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2022 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.development; + +import static com.android.settings.development.BluetoothLeAudioModePreferenceController + .LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.os.SystemProperties; + +import androidx.preference.ListPreference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class BluetoothLeAudioModePreferenceControllerTest { + + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private DevelopmentSettingsDashboardFragment mFragment; + @Mock + private BluetoothAdapter mBluetoothAdapter; + @Mock + private ListPreference mPreference; + + private Context mContext; + private BluetoothLeAudioModePreferenceController mController; + private String[] mListValues; + private String[] mListSummaries; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mListValues = mContext.getResources().getStringArray( + R.array.bluetooth_leaudio_mode_values); + mListSummaries = mContext.getResources().getStringArray( + R.array.bluetooth_leaudio_mode); + mController = spy(new BluetoothLeAudioModePreferenceController(mContext, mFragment)); + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())) + .thenReturn(mPreference); + mController.mBluetoothAdapter = mBluetoothAdapter; + mController.displayPreference(mPreferenceScreen); + } + + @Test + public void onRebootDialogConfirmed_changeLeAudioMode_shouldSetLeAudioMode() { + mController.mChanged = true; + SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mListValues[0]); + mController.mNewMode = mListValues[1]; + + mController.onRebootDialogConfirmed(); + assertThat(SystemProperties.get(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mListValues[0]) + .equals(mController.mNewMode)).isTrue(); + } + + @Test + public void onRebootDialogConfirmed_notChangeLeAudioMode_shouldNotSetLeAudioMode() { + mController.mChanged = false; + SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mListValues[0]); + mController.mNewMode = mListValues[1]; + + mController.onRebootDialogConfirmed(); + assertThat(SystemProperties.get(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mListValues[0]) + .equals(mController.mNewMode)).isFalse(); + } + + @Test + public void onRebootDialogCanceled_shouldNotSetLeAudioMode() { + mController.mChanged = true; + SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mListValues[0]); + mController.mNewMode = mListValues[1]; + + mController.onRebootDialogCanceled(); + assertThat(SystemProperties.get(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mListValues[0]) + .equals(mController.mNewMode)).isFalse(); + } +}