diff --git a/res/values/strings.xml b/res/values/strings.xml index 1ac6437720d..0c78eafe830 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -353,6 +353,11 @@ Cancel + + Enable LE Audio + + Enables the Bluetooth LE Audio feature if this device supports LE audio hardware capabilities. + Media devices diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 6319763f579..9367aec5a18 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -319,6 +319,11 @@ android:title="@string/bluetooth_disable_absolute_volume" android:summary="@string/bluetooth_disable_absolute_volume_summary" /> + + diff --git a/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java b/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java index 2ffa11b6c99..793b7fbc561 100644 --- a/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java +++ b/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java @@ -53,7 +53,7 @@ public class BluetoothA2dpHwOffloadPreferenceController extends DeveloperOptions @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - BluetoothHwOffloadRebootDialog.show(mFragment); + BluetoothRebootDialog.show(mFragment); mChanged = true; return false; } @@ -93,9 +93,9 @@ public class BluetoothA2dpHwOffloadPreferenceController extends DeveloperOptions } /** - * Called when the HwOffloadDialog confirm is clicked. + * Called when the RebootDialog confirm is clicked. */ - public void onHwOffloadDialogConfirmed() { + public void onRebootDialogConfirmed() { if (!mChanged) { return; } @@ -109,9 +109,9 @@ public class BluetoothA2dpHwOffloadPreferenceController extends DeveloperOptions } /** - * Called when the HwOffloadDialog cancel is clicked. + * Called when the RebootDialog cancel is clicked. */ - public void onHwOffloadDialogCanceled() { + public void onRebootDialogCanceled() { mChanged = false; } } diff --git a/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceController.java index 911b62d6cc4..f406ae782cf 100644 --- a/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceController.java +++ b/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceController.java @@ -59,7 +59,7 @@ public class BluetoothLeAudioHwOffloadPreferenceController @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - BluetoothHwOffloadRebootDialog.show(mFragment); + BluetoothRebootDialog.show(mFragment); mChanged = true; return false; } @@ -104,9 +104,9 @@ public class BluetoothLeAudioHwOffloadPreferenceController } /** - * Called when the HwOffloadDialog confirm is clicked. + * Called when the RebootDialog confirm is clicked. */ - public void onHwOffloadDialogConfirmed() { + public void onRebootDialogConfirmed() { if (!mChanged) { return; } @@ -119,9 +119,9 @@ public class BluetoothLeAudioHwOffloadPreferenceController } /** - * Called when the HwOffloadDialog cancel is clicked. + * Called when the RebootDialog cancel is clicked. */ - public void onHwOffloadDialogCanceled() { + public void onRebootDialogCanceled() { mChanged = false; } } diff --git a/src/com/android/settings/development/BluetoothLeAudioPreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioPreferenceController.java new file mode 100644 index 00000000000..00d0dd27af6 --- /dev/null +++ b/src/com/android/settings/development/BluetoothLeAudioPreferenceController.java @@ -0,0 +1,114 @@ +/* + * Copyright 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 android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothStatusCodes; +import android.content.Context; +import android.os.SystemProperties; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.SwitchPreference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.development.DeveloperOptionsPreferenceController; + +/** + * Preference controller to control Bluetooth LE audio feature + */ +public class BluetoothLeAudioPreferenceController + extends DeveloperOptionsPreferenceController + implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin { + + private static final String PREFERENCE_KEY = "bluetooth_enable_leaudio"; + + private static final String LE_AUDIO_DYNAMIC_SWITCH_PROPERTY = + "ro.bluetooth.leaudio_switcher.supported"; + @VisibleForTesting + static final String LE_AUDIO_DYNAMIC_ENABLED_PROPERTY = + "persist.bluetooth.leaudio_switcher.enabled"; + + private final DevelopmentSettingsDashboardFragment mFragment; + + @VisibleForTesting + BluetoothAdapter mBluetoothAdapter; + + @VisibleForTesting + boolean mChanged = false; + + public BluetoothLeAudioPreferenceController(Context context, + DevelopmentSettingsDashboardFragment fragment) { + super(context); + mFragment = fragment; + mBluetoothAdapter = context.getSystemService(BluetoothManager.class).getAdapter(); + } + + @Override + public String getPreferenceKey() { + return PREFERENCE_KEY; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + BluetoothRebootDialog.show(mFragment); + mChanged = true; + return false; + } + + @Override + public void updateState(Preference preference) { + if (mBluetoothAdapter == null) { + return; + } + + final boolean leAudioEnabled = + (mBluetoothAdapter.isLeAudioSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED); + ((SwitchPreference) mPreference).setChecked(leAudioEnabled); + + final boolean leAudioSwitchSupported = + SystemProperties.getBoolean(LE_AUDIO_DYNAMIC_SWITCH_PROPERTY, false); + if (!leAudioSwitchSupported) { + mPreference.setEnabled(false); + } else { + SystemProperties.set(LE_AUDIO_DYNAMIC_ENABLED_PROPERTY, + Boolean.toString(leAudioEnabled)); + } + } + + /** + * Called when the RebootDialog confirm is clicked. + */ + public void onRebootDialogConfirmed() { + if (!mChanged || mBluetoothAdapter == null) { + return; + } + + final boolean leAudioEnabled = + (mBluetoothAdapter.isLeAudioSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED); + SystemProperties.set(LE_AUDIO_DYNAMIC_ENABLED_PROPERTY, + Boolean.toString(!leAudioEnabled)); + } + + /** + * Called when the RebootDialog cancel is clicked. + */ + public void onRebootDialogCanceled() { + mChanged = false; + } +} diff --git a/src/com/android/settings/development/BluetoothHwOffloadRebootDialog.java b/src/com/android/settings/development/BluetoothRebootDialog.java similarity index 74% rename from src/com/android/settings/development/BluetoothHwOffloadRebootDialog.java rename to src/com/android/settings/development/BluetoothRebootDialog.java index 389103e816d..90f4c0c59b4 100644 --- a/src/com/android/settings/development/BluetoothHwOffloadRebootDialog.java +++ b/src/com/android/settings/development/BluetoothRebootDialog.java @@ -29,22 +29,23 @@ import com.android.settings.R; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; /** - * The a2dp and LE audio offload switch should reboot the device to take effect, the dialog is - * to ask the user to reboot the device after a2dp or LE audio offload user preference changed + * The a2dp/LE audio offload and LE audio feature switch should reboot the device to take effect, + * the dialog is to ask the user to reboot the device after a2dp/LE audio offload and LE audio + * feature user preference changed */ -public class BluetoothHwOffloadRebootDialog extends InstrumentedDialogFragment +public class BluetoothRebootDialog extends InstrumentedDialogFragment implements DialogInterface.OnClickListener { - public static final String TAG = "BluetoothHwOffloadReboot"; + public static final String TAG = "BluetoothReboot"; /** - * The function to show the HwOffloadReboot Dialog. + * The function to show the Reboot Dialog. */ public static void show(DevelopmentSettingsDashboardFragment host) { final FragmentManager manager = host.getActivity().getSupportFragmentManager(); if (manager.findFragmentByTag(TAG) == null) { - final BluetoothHwOffloadRebootDialog dialog = - new BluetoothHwOffloadRebootDialog(); + final BluetoothRebootDialog dialog = + new BluetoothRebootDialog(); dialog.setTargetFragment(host, 0 /* requestCode */); dialog.show(manager, TAG); } @@ -69,33 +70,33 @@ public class BluetoothHwOffloadRebootDialog extends InstrumentedDialogFragment @Override public void onClick(DialogInterface dialog, int which) { - final OnHwOffloadDialogListener host = - (OnHwOffloadDialogListener) getTargetFragment(); + final OnRebootDialogListener host = + (OnRebootDialogListener) getTargetFragment(); if (host == null) { return; } if (which == DialogInterface.BUTTON_POSITIVE) { - host.onHwOffloadDialogConfirmed(); + host.onRebootDialogConfirmed(); PowerManager pm = getContext().getSystemService(PowerManager.class); pm.reboot(null); } else { - host.onHwOffloadDialogCanceled(); + host.onRebootDialogCanceled(); } } /** - * The interface for the HsOffloadDialogListener to provide the action as the + * The interface for the RebootDialogListener to provide the action as the * confirmed or canceled clicked. */ - public interface OnHwOffloadDialogListener { + public interface OnRebootDialogListener { /** * Called when the user presses reboot on the warning dialog. */ - void onHwOffloadDialogConfirmed(); + void onRebootDialogConfirmed(); /** * Called when the user presses cancel on the warning dialog. */ - void onHwOffloadDialogCanceled(); + void onRebootDialogCanceled(); } } diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 3fbea5249a0..d92fb7fd99b 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -80,7 +80,7 @@ import java.util.List; public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFragment implements OnMainSwitchChangeListener, OemUnlockDialogHost, AdbDialogHost, AdbClearKeysDialogHost, LogPersistDialogHost, - BluetoothHwOffloadRebootDialog.OnHwOffloadDialogListener, + BluetoothRebootDialog.OnRebootDialogListener, AbstractBluetoothPreferenceController.Callback { private static final String TAG = "DevSettingsDashboard"; @@ -362,27 +362,37 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra } @Override - public void onHwOffloadDialogConfirmed() { + public void onRebootDialogConfirmed() { final BluetoothA2dpHwOffloadPreferenceController a2dpController = getDevelopmentOptionsController(BluetoothA2dpHwOffloadPreferenceController.class); - a2dpController.onHwOffloadDialogConfirmed(); + a2dpController.onRebootDialogConfirmed(); final BluetoothLeAudioHwOffloadPreferenceController leAudioController = getDevelopmentOptionsController( BluetoothLeAudioHwOffloadPreferenceController.class); - leAudioController.onHwOffloadDialogConfirmed(); + leAudioController.onRebootDialogConfirmed(); + + final BluetoothLeAudioPreferenceController leAudioFeatureController = + getDevelopmentOptionsController( + BluetoothLeAudioPreferenceController.class); + leAudioFeatureController.onRebootDialogConfirmed(); } @Override - public void onHwOffloadDialogCanceled() { + public void onRebootDialogCanceled() { final BluetoothA2dpHwOffloadPreferenceController a2dpController = getDevelopmentOptionsController(BluetoothA2dpHwOffloadPreferenceController.class); - a2dpController.onHwOffloadDialogCanceled(); + a2dpController.onRebootDialogCanceled(); final BluetoothLeAudioHwOffloadPreferenceController leAudioController = getDevelopmentOptionsController( BluetoothLeAudioHwOffloadPreferenceController.class); - leAudioController.onHwOffloadDialogCanceled(); + leAudioController.onRebootDialogCanceled(); + + final BluetoothLeAudioPreferenceController leAudioFeatureController = + getDevelopmentOptionsController( + BluetoothLeAudioPreferenceController.class); + leAudioFeatureController.onRebootDialogCanceled(); } @Override @@ -540,6 +550,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controllers.add(new BluetoothAbsoluteVolumePreferenceController(context)); controllers.add(new BluetoothAvrcpVersionPreferenceController(context)); controllers.add(new BluetoothMapVersionPreferenceController(context)); + controllers.add(new BluetoothLeAudioPreferenceController(context, fragment)); controllers.add(new BluetoothA2dpHwOffloadPreferenceController(context, fragment)); controllers.add(new BluetoothLeAudioHwOffloadPreferenceController(context, fragment)); controllers.add(new BluetoothMaxConnectedAudioDevicesPreferenceController(context)); diff --git a/tests/robotests/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceControllerTest.java index fcb3ea929a4..660c8f1bac8 100644 --- a/tests/robotests/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceControllerTest.java @@ -68,7 +68,7 @@ public class BluetoothA2dpHwOffloadPreferenceControllerTest { SystemProperties.set(A2DP_OFFLOAD_DISABLED_PROPERTY, Boolean.toString(false)); mController.mChanged = true; - mController.onHwOffloadDialogConfirmed(); + mController.onRebootDialogConfirmed(); final boolean mode = SystemProperties.getBoolean(A2DP_OFFLOAD_DISABLED_PROPERTY, false); assertThat(mode).isTrue(); } @@ -80,7 +80,7 @@ public class BluetoothA2dpHwOffloadPreferenceControllerTest { mController.mChanged = true; - mController.onHwOffloadDialogConfirmed(); + mController.onRebootDialogConfirmed(); final boolean a2dpMode = SystemProperties.getBoolean(A2DP_OFFLOAD_DISABLED_PROPERTY, true); final boolean leAudioMode = SystemProperties .getBoolean(LE_AUDIO_OFFLOAD_DISABLED_PROPERTY, true); @@ -93,7 +93,7 @@ public class BluetoothA2dpHwOffloadPreferenceControllerTest { SystemProperties.set(A2DP_OFFLOAD_DISABLED_PROPERTY, Boolean.toString(false)); mController.mChanged = true; - mController.onHwOffloadDialogCanceled(); + mController.onRebootDialogCanceled(); final boolean mode = SystemProperties.getBoolean(A2DP_OFFLOAD_DISABLED_PROPERTY, false); assertThat(mode).isFalse(); } diff --git a/tests/robotests/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceControllerTest.java index c82df40e038..ec64fe348d9 100644 --- a/tests/robotests/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceControllerTest.java @@ -66,7 +66,7 @@ public class BluetoothLeAudioHwOffloadPreferenceControllerTest { SystemProperties.set(LE_AUDIO_OFFLOAD_DISABLED_PROPERTY, Boolean.toString(false)); mController.mChanged = true; - mController.onHwOffloadDialogConfirmed(); + mController.onRebootDialogConfirmed(); final boolean mode = SystemProperties.getBoolean(LE_AUDIO_OFFLOAD_DISABLED_PROPERTY, false); assertThat(mode).isTrue(); } @@ -76,7 +76,7 @@ public class BluetoothLeAudioHwOffloadPreferenceControllerTest { SystemProperties.set(LE_AUDIO_OFFLOAD_DISABLED_PROPERTY, Boolean.toString(true)); mController.mChanged = true; - mController.onHwOffloadDialogConfirmed(); + mController.onRebootDialogConfirmed(); final boolean mode2 = SystemProperties.getBoolean( LE_AUDIO_OFFLOAD_DISABLED_PROPERTY, true); assertThat(mode2).isFalse(); @@ -87,7 +87,7 @@ public class BluetoothLeAudioHwOffloadPreferenceControllerTest { SystemProperties.set(LE_AUDIO_OFFLOAD_DISABLED_PROPERTY, Boolean.toString(false)); mController.mChanged = true; - mController.onHwOffloadDialogCanceled(); + mController.onRebootDialogCanceled(); final boolean mode = SystemProperties.getBoolean(LE_AUDIO_OFFLOAD_DISABLED_PROPERTY, false); assertThat(mode).isFalse(); } diff --git a/tests/robotests/src/com/android/settings/development/BluetoothLeAudioPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioPreferenceControllerTest.java new file mode 100644 index 00000000000..527fabbc526 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioPreferenceControllerTest.java @@ -0,0 +1,105 @@ +/* + * 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.BluetoothLeAudioPreferenceController + .LE_AUDIO_DYNAMIC_ENABLED_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.bluetooth.BluetoothStatusCodes; +import android.content.Context; +import android.os.SystemProperties; + +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; + +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 BluetoothLeAudioPreferenceControllerTest { + + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private DevelopmentSettingsDashboardFragment mFragment; + + @Mock + private BluetoothAdapter mBluetoothAdapter; + + private Context mContext; + private SwitchPreference mPreference; + private BluetoothLeAudioPreferenceController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mPreference = new SwitchPreference(mContext); + mController = spy(new BluetoothLeAudioPreferenceController(mContext, mFragment)); + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())) + .thenReturn(mPreference); + mController.mBluetoothAdapter = mBluetoothAdapter; + mController.displayPreference(mPreferenceScreen); + } + + @Test + public void onRebootDialogConfirmedAsLeAudioDisabled_shouldSwitchStatus() { + when(mBluetoothAdapter.isLeAudioSupported()) + .thenReturn(BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); + mController.mChanged = true; + + mController.onRebootDialogConfirmed(); + final boolean status = SystemProperties + .getBoolean(LE_AUDIO_DYNAMIC_ENABLED_PROPERTY, false); + assertThat(status).isTrue(); + } + + @Test + public void onRebootDialogConfirmedAsLeAudioEnabled_shouldSwitchStatus() { + when(mBluetoothAdapter.isLeAudioSupported()) + .thenReturn(BluetoothStatusCodes.FEATURE_SUPPORTED); + mController.mChanged = true; + + mController.onRebootDialogConfirmed(); + final boolean status = SystemProperties + .getBoolean(LE_AUDIO_DYNAMIC_ENABLED_PROPERTY, false); + assertThat(status).isFalse(); + } + + @Test + public void onRebootDialogCanceled_shouldNotSwitchStatus() { + when(mBluetoothAdapter.isLeAudioSupported()) + .thenReturn(BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); + mController.mChanged = true; + + mController.onRebootDialogCanceled(); + final boolean status = SystemProperties + .getBoolean(LE_AUDIO_DYNAMIC_ENABLED_PROPERTY, false); + assertThat(status).isFalse(); + } +}