Loading aconfig/settings_bluetooth_declarations.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -6,3 +6,10 @@ flag { description: "Gates whether to offload bluetooth operations to background thread" bug: "305636727" } flag { name: "enable_bluetooth_profile_toggle_visibility_checker" namespace: "pixel_cross_device_control" description: "Gates whether to enable checker for bluetooth profile toggle visibility" bug: "321178209" } No newline at end of file src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +33 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,8 @@ import androidx.preference.TwoStatePreference; import com.android.settings.R; import com.android.settings.core.SettingsUIDeviceConfig; import com.android.settings.flags.Flags; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; Loading @@ -49,11 +51,14 @@ import com.android.settingslib.bluetooth.MapProfile; import com.android.settingslib.bluetooth.PanProfile; import com.android.settingslib.bluetooth.PbapServerProfile; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.utils.ThreadUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; /** * This class adds switches for toggling the individual profiles that a Bluetooth device Loading @@ -79,6 +84,8 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll private static final String LE_AUDIO_TOGGLE_VISIBLE_PROPERTY = "persist.bluetooth.leaudio.toggle_visible"; private final AtomicReference<Set<String>> mInvisiblePreferenceKey = new AtomicReference<>(); private LocalBluetoothManager mManager; private LocalBluetoothProfileManager mProfileManager; private CachedBluetoothDevice mCachedDevice; Loading Loading @@ -547,6 +554,22 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll */ @Override protected void refresh() { if (Flags.enableBluetoothProfileToggleVisibilityChecker()) { ThreadUtils.postOnBackgroundThread( () -> { mInvisiblePreferenceKey.set( FeatureFactory.getFeatureFactory() .getBluetoothFeatureProvider() .getInvisibleProfilePreferenceKeys( mContext, mCachedDevice.getDevice())); ThreadUtils.postOnMainThread(this::refreshUi); }); } else { refreshUi(); } } private void refreshUi() { for (LocalBluetoothProfile profile : getProfiles()) { if (profile == null || !profile.isProfileReady()) { continue; Loading Loading @@ -577,6 +600,16 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll preference.setSelectable(false); mProfilesContainer.addPreference(preference); } if (Flags.enableBluetoothProfileToggleVisibilityChecker()) { Set<String> invisibleKeys = mInvisiblePreferenceKey.get(); if (invisibleKeys != null) { for (int i = 0; i < mProfilesContainer.getPreferenceCount(); ++i) { Preference pref = mProfilesContainer.getPreference(i); pref.setVisible(pref.isVisible() && !invisibleKeys.contains(pref.getKey())); } } } } @Override Loading src/com/android/settings/bluetooth/BluetoothFeatureProvider.java +11 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import androidx.preference.Preference; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import java.util.List; import java.util.Set; /** * Provider for bluetooth related features. Loading Loading @@ -73,4 +74,14 @@ public interface BluetoothFeatureProvider { * @return the extra bluetooth preference list */ List<Preference> getBluetoothExtraOptions(Context context, CachedBluetoothDevice device); /** * Gets the bluetooth profile preference keys which should be hidden in the device details page. * * @param context Context * @param bluetoothDevice the bluetooth device * @return the profiles which should be hidden */ Set<String> getInvisibleProfilePreferenceKeys( Context context, BluetoothDevice bluetoothDevice); } src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java +8 −0 Original line number Diff line number Diff line Loading @@ -29,8 +29,10 @@ import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.List; import java.util.Set; /** * Impl of {@link BluetoothFeatureProvider} Loading Loading @@ -65,4 +67,10 @@ public class BluetoothFeatureProviderImpl implements BluetoothFeatureProvider { CachedBluetoothDevice device) { return ImmutableList.of(); } @Override public Set<String> getInvisibleProfilePreferenceKeys( Context context, BluetoothDevice bluetoothDevice) { return ImmutableSet.of(); } } tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java +98 −29 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settings.bluetooth; 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.spy; Loading @@ -28,23 +29,33 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import android.sysprop.BluetoothProperties; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.SwitchPreference; import androidx.preference.SwitchPreferenceCompat; import com.android.settings.flags.Flags; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowBluetoothDevice; import com.android.settingslib.R; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.bluetooth.MapProfile; import com.android.settingslib.bluetooth.PbapServerProfile; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; Loading @@ -59,30 +70,41 @@ import java.util.Map; import java.util.Set; @RunWith(RobolectricTestRunner.class) @Ignore @Config(shadows = ShadowBluetoothDevice.class) public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsControllerTestBase { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private static final String LE_DEVICE_MODEL = "le_audio_headset"; private static final String NON_LE_DEVICE_MODEL = "non_le_audio_headset"; private BluetoothDetailsProfilesController mController; private List<LocalBluetoothProfile> mConnectableProfiles; private PreferenceCategory mProfiles; private BluetoothFeatureProvider mFeatureProvider; @Mock private LocalBluetoothManager mLocalManager; @Mock private LocalBluetoothProfileManager mProfileManager; @Mock private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager; @Override public void setUp() { super.setUp(); FakeFeatureFactory fakeFeatureFactory = FakeFeatureFactory.setupForTest(); mFeatureProvider = fakeFeatureFactory.getBluetoothFeatureProvider(); mProfiles = spy(new PreferenceCategory(mContext)); when(mProfiles.getPreferenceManager()).thenReturn(mPreferenceManager); mConnectableProfiles = new ArrayList<>(); when(mLocalManager.getProfileManager()).thenReturn(mProfileManager); when(mLocalManager.getCachedDeviceManager()).thenReturn(mCachedBluetoothDeviceManager); when(mCachedBluetoothDeviceManager.getCachedDevicesCopy()) .thenReturn(ImmutableList.of(mCachedDevice)); when(mCachedDevice.getConnectableProfiles()).thenAnswer(invocation -> new ArrayList<>(mConnectableProfiles) ); Loading Loading @@ -196,25 +218,26 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont return profile; } /** Returns the list of SwitchPreference objects added to the screen - there should be one per * Bluetooth profile. /** * Returns the list of SwitchPreferenceCompat objects added to the screen - there should be one * per Bluetooth profile. */ private List<SwitchPreference> getProfileSwitches(boolean expectOnlyMConnectable) { private List<SwitchPreferenceCompat> getProfileSwitches(boolean expectOnlyMConnectable) { if (expectOnlyMConnectable) { assertThat(mConnectableProfiles).isNotEmpty(); assertThat(mProfiles.getPreferenceCount() - 1).isEqualTo(mConnectableProfiles.size()); } List<SwitchPreference> result = new ArrayList<>(); List<SwitchPreferenceCompat> result = new ArrayList<>(); for (int i = 0; i < mProfiles.getPreferenceCount(); i++) { final Preference preference = mProfiles.getPreference(i); if (preference instanceof SwitchPreference) { result.add((SwitchPreference) preference); if (preference instanceof SwitchPreferenceCompat) { result.add((SwitchPreferenceCompat) preference); } } return result; } private void verifyProfileSwitchTitles(List<SwitchPreference> switches) { private void verifyProfileSwitchTitles(List<SwitchPreferenceCompat> switches) { for (int i = 0; i < switches.size(); i++) { String expectedTitle = mContext.getString(mConnectableProfiles.get(i).getNameResource(mDevice)); Loading @@ -234,7 +257,7 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont addFakeProfile(com.android.settingslib.R.string.bluetooth_profile_a2dp, true); addFakeProfile(com.android.settingslib.R.string.bluetooth_profile_headset, false); showScreen(mController); List<SwitchPreference> switches = getProfileSwitches(true); List<SwitchPreferenceCompat> switches = getProfileSwitches(true); verifyProfileSwitchTitles(switches); assertThat(switches.get(0).isChecked()).isTrue(); assertThat(switches.get(1).isChecked()).isFalse(); Loading @@ -260,8 +283,8 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont addFakeProfile(com.android.settingslib.R.string.bluetooth_profile_a2dp, true); addFakeProfile(com.android.settingslib.R.string.bluetooth_profile_headset, true); showScreen(mController); List<SwitchPreference> switches = getProfileSwitches(true); SwitchPreference pref = switches.get(0); List<SwitchPreferenceCompat> switches = getProfileSwitches(true); SwitchPreferenceCompat pref = switches.get(0); // Clicking the pref should cause the profile to become not-preferred. assertThat(pref.isChecked()).isTrue(); Loading Loading @@ -296,14 +319,16 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont PbapServerProfile psp = mock(PbapServerProfile.class); when(psp.getNameResource(mDevice)) .thenReturn(com.android.settingslib.R.string.bluetooth_profile_pbap); when(psp.getSummaryResourceForDevice(mDevice)) .thenReturn(R.string.bluetooth_profile_pbap_summary); when(psp.toString()).thenReturn(PbapServerProfile.NAME); when(psp.isProfileReady()).thenReturn(true); when(mProfileManager.getPbapProfile()).thenReturn(psp); showScreen(mController); List<SwitchPreference> switches = getProfileSwitches(false); List<SwitchPreferenceCompat> switches = getProfileSwitches(false); assertThat(switches.size()).isEqualTo(1); SwitchPreference pref = switches.get(0); SwitchPreferenceCompat pref = switches.get(0); assertThat(pref.getTitle()).isEqualTo( mContext.getString(com.android.settingslib.R.string.bluetooth_profile_pbap)); assertThat(pref.isChecked()).isTrue(); Loading @@ -321,14 +346,16 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont PbapServerProfile psp = mock(PbapServerProfile.class); when(psp.getNameResource(mDevice)) .thenReturn(com.android.settingslib.R.string.bluetooth_profile_pbap); when(psp.getSummaryResourceForDevice(mDevice)) .thenReturn(R.string.bluetooth_profile_pbap_summary); when(psp.toString()).thenReturn(PbapServerProfile.NAME); when(psp.isProfileReady()).thenReturn(true); when(mProfileManager.getPbapProfile()).thenReturn(psp); showScreen(mController); List<SwitchPreference> switches = getProfileSwitches(false); List<SwitchPreferenceCompat> switches = getProfileSwitches(false); assertThat(switches.size()).isEqualTo(1); SwitchPreference pref = switches.get(0); SwitchPreferenceCompat pref = switches.get(0); assertThat(pref.getTitle()).isEqualTo( mContext.getString(com.android.settingslib.R.string.bluetooth_profile_pbap)); assertThat(pref.isChecked()).isFalse(); Loading @@ -350,9 +377,9 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont when(mProfileManager.getProfileByName(eq(mapProfile.toString()))).thenReturn(mapProfile); mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED); showScreen(mController); List<SwitchPreference> switches = getProfileSwitches(false); List<SwitchPreferenceCompat> switches = getProfileSwitches(false); assertThat(switches.size()).isEqualTo(1); SwitchPreference pref = switches.get(0); SwitchPreferenceCompat pref = switches.get(0); assertThat(pref.getTitle()).isEqualTo( mContext.getString(com.android.settingslib.R.string.bluetooth_profile_map)); assertThat(pref.isChecked()).isFalse(); Loading @@ -379,8 +406,8 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont return profile; } private SwitchPreference getHighQualityAudioPref() { return (SwitchPreference) mScreen.findPreference( private SwitchPreferenceCompat getHighQualityAudioPref() { return (SwitchPreferenceCompat) mScreen.findPreference( BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG); } Loading @@ -389,7 +416,7 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont setupDevice(makeDefaultDeviceConfig()); addMockA2dpProfile(true, true, true); showScreen(mController); SwitchPreference pref = getHighQualityAudioPref(); SwitchPreferenceCompat pref = getHighQualityAudioPref(); assertThat(pref.getKey()).isEqualTo( BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG); Loading @@ -407,7 +434,7 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont addMockA2dpProfile(true, false, false); showScreen(mController); assertThat(mProfiles.getPreferenceCount()).isEqualTo(2); SwitchPreference pref = (SwitchPreference) mProfiles.getPreference(0); SwitchPreferenceCompat pref = (SwitchPreferenceCompat) mProfiles.getPreference(0); assertThat(pref.getKey()) .isNotEqualTo(BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG); assertThat(pref.getTitle()).isEqualTo( Loading @@ -420,7 +447,7 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont addMockA2dpProfile(true, true, true); when(mCachedDevice.isBusy()).thenReturn(true); showScreen(mController); SwitchPreference pref = getHighQualityAudioPref(); SwitchPreferenceCompat pref = getHighQualityAudioPref(); assertThat(pref.isEnabled()).isFalse(); } Loading @@ -433,14 +460,14 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont // Disabling media audio should cause the high quality audio switch to disappear, but not // the regular audio one. SwitchPreference audioPref = (SwitchPreference) mScreen.findPreference(audioProfile.toString()); SwitchPreferenceCompat audioPref = (SwitchPreferenceCompat) mScreen.findPreference(audioProfile.toString()); audioPref.performClick(); verify(audioProfile).setEnabled(mDevice, false); when(audioProfile.isEnabled(mDevice)).thenReturn(false); mController.onDeviceAttributesChanged(); assertThat(audioPref.isVisible()).isTrue(); SwitchPreference highQualityAudioPref = getHighQualityAudioPref(); SwitchPreferenceCompat highQualityAudioPref = getHighQualityAudioPref(); assertThat(highQualityAudioPref.isVisible()).isFalse(); // And re-enabling media audio should make high quality switch to reappear. Loading @@ -457,8 +484,8 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont setupDevice(makeDefaultDeviceConfig()); A2dpProfile audioProfile = addMockA2dpProfile(false, true, true); showScreen(mController); SwitchPreference audioPref = mScreen.findPreference(audioProfile.toString()); SwitchPreference highQualityAudioPref = getHighQualityAudioPref(); SwitchPreferenceCompat audioPref = mScreen.findPreference(audioProfile.toString()); SwitchPreferenceCompat highQualityAudioPref = getHighQualityAudioPref(); assertThat(audioPref).isNotNull(); assertThat(audioPref.isChecked()).isFalse(); assertThat(highQualityAudioPref).isNotNull(); Loading Loading @@ -489,4 +516,46 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont assertThat(mController.isModelNameInAllowList(null)).isFalse(); assertThat(mController.isModelNameInAllowList(NON_LE_DEVICE_MODEL)).isFalse(); } @Test public void prefKeyInBlockingList_hideToggle() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_BLUETOOTH_PROFILE_TOGGLE_VISIBILITY_CHECKER); setupDevice(makeDefaultDeviceConfig()); LeAudioProfile leAudioProfile = mock(LeAudioProfile.class); when(leAudioProfile.getNameResource(mDevice)) .thenReturn(com.android.settingslib.R.string.bluetooth_profile_le_audio); when(leAudioProfile.isProfileReady()).thenReturn(true); when(leAudioProfile.toString()).thenReturn("LE_AUDIO"); when(mProfileManager.getLeAudioProfile()).thenReturn(leAudioProfile); when(mFeatureProvider.getInvisibleProfilePreferenceKeys(any(), any())) .thenReturn(ImmutableSet.of("LE_AUDIO")); mConnectableProfiles.add(leAudioProfile); showScreen(mController); List<SwitchPreferenceCompat> switches = getProfileSwitches(false); assertThat(switches.get(0).isVisible()).isFalse(); } @Test public void prefKeyNotInBlockingList_showToggle() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_BLUETOOTH_PROFILE_TOGGLE_VISIBILITY_CHECKER); setupDevice(makeDefaultDeviceConfig()); LeAudioProfile leAudioProfile = mock(LeAudioProfile.class); when(leAudioProfile.getNameResource(mDevice)) .thenReturn(com.android.settingslib.R.string.bluetooth_profile_le_audio); when(leAudioProfile.isProfileReady()).thenReturn(true); when(leAudioProfile.toString()).thenReturn("LE_AUDIO"); when(mProfileManager.getLeAudioProfile()).thenReturn(leAudioProfile); when(mFeatureProvider.getInvisibleProfilePreferenceKeys(any(), any())) .thenReturn(ImmutableSet.of("A2DP")); mConnectableProfiles.add(leAudioProfile); showScreen(mController); List<SwitchPreferenceCompat> switches = getProfileSwitches(false); assertThat(switches.get(0).isVisible()).isTrue(); } } Loading
aconfig/settings_bluetooth_declarations.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -6,3 +6,10 @@ flag { description: "Gates whether to offload bluetooth operations to background thread" bug: "305636727" } flag { name: "enable_bluetooth_profile_toggle_visibility_checker" namespace: "pixel_cross_device_control" description: "Gates whether to enable checker for bluetooth profile toggle visibility" bug: "321178209" } No newline at end of file
src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +33 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,8 @@ import androidx.preference.TwoStatePreference; import com.android.settings.R; import com.android.settings.core.SettingsUIDeviceConfig; import com.android.settings.flags.Flags; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; Loading @@ -49,11 +51,14 @@ import com.android.settingslib.bluetooth.MapProfile; import com.android.settingslib.bluetooth.PanProfile; import com.android.settingslib.bluetooth.PbapServerProfile; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.utils.ThreadUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; /** * This class adds switches for toggling the individual profiles that a Bluetooth device Loading @@ -79,6 +84,8 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll private static final String LE_AUDIO_TOGGLE_VISIBLE_PROPERTY = "persist.bluetooth.leaudio.toggle_visible"; private final AtomicReference<Set<String>> mInvisiblePreferenceKey = new AtomicReference<>(); private LocalBluetoothManager mManager; private LocalBluetoothProfileManager mProfileManager; private CachedBluetoothDevice mCachedDevice; Loading Loading @@ -547,6 +554,22 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll */ @Override protected void refresh() { if (Flags.enableBluetoothProfileToggleVisibilityChecker()) { ThreadUtils.postOnBackgroundThread( () -> { mInvisiblePreferenceKey.set( FeatureFactory.getFeatureFactory() .getBluetoothFeatureProvider() .getInvisibleProfilePreferenceKeys( mContext, mCachedDevice.getDevice())); ThreadUtils.postOnMainThread(this::refreshUi); }); } else { refreshUi(); } } private void refreshUi() { for (LocalBluetoothProfile profile : getProfiles()) { if (profile == null || !profile.isProfileReady()) { continue; Loading Loading @@ -577,6 +600,16 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll preference.setSelectable(false); mProfilesContainer.addPreference(preference); } if (Flags.enableBluetoothProfileToggleVisibilityChecker()) { Set<String> invisibleKeys = mInvisiblePreferenceKey.get(); if (invisibleKeys != null) { for (int i = 0; i < mProfilesContainer.getPreferenceCount(); ++i) { Preference pref = mProfilesContainer.getPreference(i); pref.setVisible(pref.isVisible() && !invisibleKeys.contains(pref.getKey())); } } } } @Override Loading
src/com/android/settings/bluetooth/BluetoothFeatureProvider.java +11 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import androidx.preference.Preference; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import java.util.List; import java.util.Set; /** * Provider for bluetooth related features. Loading Loading @@ -73,4 +74,14 @@ public interface BluetoothFeatureProvider { * @return the extra bluetooth preference list */ List<Preference> getBluetoothExtraOptions(Context context, CachedBluetoothDevice device); /** * Gets the bluetooth profile preference keys which should be hidden in the device details page. * * @param context Context * @param bluetoothDevice the bluetooth device * @return the profiles which should be hidden */ Set<String> getInvisibleProfilePreferenceKeys( Context context, BluetoothDevice bluetoothDevice); }
src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java +8 −0 Original line number Diff line number Diff line Loading @@ -29,8 +29,10 @@ import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.List; import java.util.Set; /** * Impl of {@link BluetoothFeatureProvider} Loading Loading @@ -65,4 +67,10 @@ public class BluetoothFeatureProviderImpl implements BluetoothFeatureProvider { CachedBluetoothDevice device) { return ImmutableList.of(); } @Override public Set<String> getInvisibleProfilePreferenceKeys( Context context, BluetoothDevice bluetoothDevice) { return ImmutableSet.of(); } }
tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java +98 −29 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settings.bluetooth; 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.spy; Loading @@ -28,23 +29,33 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import android.sysprop.BluetoothProperties; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.SwitchPreference; import androidx.preference.SwitchPreferenceCompat; import com.android.settings.flags.Flags; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowBluetoothDevice; import com.android.settingslib.R; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.bluetooth.MapProfile; import com.android.settingslib.bluetooth.PbapServerProfile; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; Loading @@ -59,30 +70,41 @@ import java.util.Map; import java.util.Set; @RunWith(RobolectricTestRunner.class) @Ignore @Config(shadows = ShadowBluetoothDevice.class) public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsControllerTestBase { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private static final String LE_DEVICE_MODEL = "le_audio_headset"; private static final String NON_LE_DEVICE_MODEL = "non_le_audio_headset"; private BluetoothDetailsProfilesController mController; private List<LocalBluetoothProfile> mConnectableProfiles; private PreferenceCategory mProfiles; private BluetoothFeatureProvider mFeatureProvider; @Mock private LocalBluetoothManager mLocalManager; @Mock private LocalBluetoothProfileManager mProfileManager; @Mock private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager; @Override public void setUp() { super.setUp(); FakeFeatureFactory fakeFeatureFactory = FakeFeatureFactory.setupForTest(); mFeatureProvider = fakeFeatureFactory.getBluetoothFeatureProvider(); mProfiles = spy(new PreferenceCategory(mContext)); when(mProfiles.getPreferenceManager()).thenReturn(mPreferenceManager); mConnectableProfiles = new ArrayList<>(); when(mLocalManager.getProfileManager()).thenReturn(mProfileManager); when(mLocalManager.getCachedDeviceManager()).thenReturn(mCachedBluetoothDeviceManager); when(mCachedBluetoothDeviceManager.getCachedDevicesCopy()) .thenReturn(ImmutableList.of(mCachedDevice)); when(mCachedDevice.getConnectableProfiles()).thenAnswer(invocation -> new ArrayList<>(mConnectableProfiles) ); Loading Loading @@ -196,25 +218,26 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont return profile; } /** Returns the list of SwitchPreference objects added to the screen - there should be one per * Bluetooth profile. /** * Returns the list of SwitchPreferenceCompat objects added to the screen - there should be one * per Bluetooth profile. */ private List<SwitchPreference> getProfileSwitches(boolean expectOnlyMConnectable) { private List<SwitchPreferenceCompat> getProfileSwitches(boolean expectOnlyMConnectable) { if (expectOnlyMConnectable) { assertThat(mConnectableProfiles).isNotEmpty(); assertThat(mProfiles.getPreferenceCount() - 1).isEqualTo(mConnectableProfiles.size()); } List<SwitchPreference> result = new ArrayList<>(); List<SwitchPreferenceCompat> result = new ArrayList<>(); for (int i = 0; i < mProfiles.getPreferenceCount(); i++) { final Preference preference = mProfiles.getPreference(i); if (preference instanceof SwitchPreference) { result.add((SwitchPreference) preference); if (preference instanceof SwitchPreferenceCompat) { result.add((SwitchPreferenceCompat) preference); } } return result; } private void verifyProfileSwitchTitles(List<SwitchPreference> switches) { private void verifyProfileSwitchTitles(List<SwitchPreferenceCompat> switches) { for (int i = 0; i < switches.size(); i++) { String expectedTitle = mContext.getString(mConnectableProfiles.get(i).getNameResource(mDevice)); Loading @@ -234,7 +257,7 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont addFakeProfile(com.android.settingslib.R.string.bluetooth_profile_a2dp, true); addFakeProfile(com.android.settingslib.R.string.bluetooth_profile_headset, false); showScreen(mController); List<SwitchPreference> switches = getProfileSwitches(true); List<SwitchPreferenceCompat> switches = getProfileSwitches(true); verifyProfileSwitchTitles(switches); assertThat(switches.get(0).isChecked()).isTrue(); assertThat(switches.get(1).isChecked()).isFalse(); Loading @@ -260,8 +283,8 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont addFakeProfile(com.android.settingslib.R.string.bluetooth_profile_a2dp, true); addFakeProfile(com.android.settingslib.R.string.bluetooth_profile_headset, true); showScreen(mController); List<SwitchPreference> switches = getProfileSwitches(true); SwitchPreference pref = switches.get(0); List<SwitchPreferenceCompat> switches = getProfileSwitches(true); SwitchPreferenceCompat pref = switches.get(0); // Clicking the pref should cause the profile to become not-preferred. assertThat(pref.isChecked()).isTrue(); Loading Loading @@ -296,14 +319,16 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont PbapServerProfile psp = mock(PbapServerProfile.class); when(psp.getNameResource(mDevice)) .thenReturn(com.android.settingslib.R.string.bluetooth_profile_pbap); when(psp.getSummaryResourceForDevice(mDevice)) .thenReturn(R.string.bluetooth_profile_pbap_summary); when(psp.toString()).thenReturn(PbapServerProfile.NAME); when(psp.isProfileReady()).thenReturn(true); when(mProfileManager.getPbapProfile()).thenReturn(psp); showScreen(mController); List<SwitchPreference> switches = getProfileSwitches(false); List<SwitchPreferenceCompat> switches = getProfileSwitches(false); assertThat(switches.size()).isEqualTo(1); SwitchPreference pref = switches.get(0); SwitchPreferenceCompat pref = switches.get(0); assertThat(pref.getTitle()).isEqualTo( mContext.getString(com.android.settingslib.R.string.bluetooth_profile_pbap)); assertThat(pref.isChecked()).isTrue(); Loading @@ -321,14 +346,16 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont PbapServerProfile psp = mock(PbapServerProfile.class); when(psp.getNameResource(mDevice)) .thenReturn(com.android.settingslib.R.string.bluetooth_profile_pbap); when(psp.getSummaryResourceForDevice(mDevice)) .thenReturn(R.string.bluetooth_profile_pbap_summary); when(psp.toString()).thenReturn(PbapServerProfile.NAME); when(psp.isProfileReady()).thenReturn(true); when(mProfileManager.getPbapProfile()).thenReturn(psp); showScreen(mController); List<SwitchPreference> switches = getProfileSwitches(false); List<SwitchPreferenceCompat> switches = getProfileSwitches(false); assertThat(switches.size()).isEqualTo(1); SwitchPreference pref = switches.get(0); SwitchPreferenceCompat pref = switches.get(0); assertThat(pref.getTitle()).isEqualTo( mContext.getString(com.android.settingslib.R.string.bluetooth_profile_pbap)); assertThat(pref.isChecked()).isFalse(); Loading @@ -350,9 +377,9 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont when(mProfileManager.getProfileByName(eq(mapProfile.toString()))).thenReturn(mapProfile); mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED); showScreen(mController); List<SwitchPreference> switches = getProfileSwitches(false); List<SwitchPreferenceCompat> switches = getProfileSwitches(false); assertThat(switches.size()).isEqualTo(1); SwitchPreference pref = switches.get(0); SwitchPreferenceCompat pref = switches.get(0); assertThat(pref.getTitle()).isEqualTo( mContext.getString(com.android.settingslib.R.string.bluetooth_profile_map)); assertThat(pref.isChecked()).isFalse(); Loading @@ -379,8 +406,8 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont return profile; } private SwitchPreference getHighQualityAudioPref() { return (SwitchPreference) mScreen.findPreference( private SwitchPreferenceCompat getHighQualityAudioPref() { return (SwitchPreferenceCompat) mScreen.findPreference( BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG); } Loading @@ -389,7 +416,7 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont setupDevice(makeDefaultDeviceConfig()); addMockA2dpProfile(true, true, true); showScreen(mController); SwitchPreference pref = getHighQualityAudioPref(); SwitchPreferenceCompat pref = getHighQualityAudioPref(); assertThat(pref.getKey()).isEqualTo( BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG); Loading @@ -407,7 +434,7 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont addMockA2dpProfile(true, false, false); showScreen(mController); assertThat(mProfiles.getPreferenceCount()).isEqualTo(2); SwitchPreference pref = (SwitchPreference) mProfiles.getPreference(0); SwitchPreferenceCompat pref = (SwitchPreferenceCompat) mProfiles.getPreference(0); assertThat(pref.getKey()) .isNotEqualTo(BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG); assertThat(pref.getTitle()).isEqualTo( Loading @@ -420,7 +447,7 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont addMockA2dpProfile(true, true, true); when(mCachedDevice.isBusy()).thenReturn(true); showScreen(mController); SwitchPreference pref = getHighQualityAudioPref(); SwitchPreferenceCompat pref = getHighQualityAudioPref(); assertThat(pref.isEnabled()).isFalse(); } Loading @@ -433,14 +460,14 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont // Disabling media audio should cause the high quality audio switch to disappear, but not // the regular audio one. SwitchPreference audioPref = (SwitchPreference) mScreen.findPreference(audioProfile.toString()); SwitchPreferenceCompat audioPref = (SwitchPreferenceCompat) mScreen.findPreference(audioProfile.toString()); audioPref.performClick(); verify(audioProfile).setEnabled(mDevice, false); when(audioProfile.isEnabled(mDevice)).thenReturn(false); mController.onDeviceAttributesChanged(); assertThat(audioPref.isVisible()).isTrue(); SwitchPreference highQualityAudioPref = getHighQualityAudioPref(); SwitchPreferenceCompat highQualityAudioPref = getHighQualityAudioPref(); assertThat(highQualityAudioPref.isVisible()).isFalse(); // And re-enabling media audio should make high quality switch to reappear. Loading @@ -457,8 +484,8 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont setupDevice(makeDefaultDeviceConfig()); A2dpProfile audioProfile = addMockA2dpProfile(false, true, true); showScreen(mController); SwitchPreference audioPref = mScreen.findPreference(audioProfile.toString()); SwitchPreference highQualityAudioPref = getHighQualityAudioPref(); SwitchPreferenceCompat audioPref = mScreen.findPreference(audioProfile.toString()); SwitchPreferenceCompat highQualityAudioPref = getHighQualityAudioPref(); assertThat(audioPref).isNotNull(); assertThat(audioPref.isChecked()).isFalse(); assertThat(highQualityAudioPref).isNotNull(); Loading Loading @@ -489,4 +516,46 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont assertThat(mController.isModelNameInAllowList(null)).isFalse(); assertThat(mController.isModelNameInAllowList(NON_LE_DEVICE_MODEL)).isFalse(); } @Test public void prefKeyInBlockingList_hideToggle() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_BLUETOOTH_PROFILE_TOGGLE_VISIBILITY_CHECKER); setupDevice(makeDefaultDeviceConfig()); LeAudioProfile leAudioProfile = mock(LeAudioProfile.class); when(leAudioProfile.getNameResource(mDevice)) .thenReturn(com.android.settingslib.R.string.bluetooth_profile_le_audio); when(leAudioProfile.isProfileReady()).thenReturn(true); when(leAudioProfile.toString()).thenReturn("LE_AUDIO"); when(mProfileManager.getLeAudioProfile()).thenReturn(leAudioProfile); when(mFeatureProvider.getInvisibleProfilePreferenceKeys(any(), any())) .thenReturn(ImmutableSet.of("LE_AUDIO")); mConnectableProfiles.add(leAudioProfile); showScreen(mController); List<SwitchPreferenceCompat> switches = getProfileSwitches(false); assertThat(switches.get(0).isVisible()).isFalse(); } @Test public void prefKeyNotInBlockingList_showToggle() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_BLUETOOTH_PROFILE_TOGGLE_VISIBILITY_CHECKER); setupDevice(makeDefaultDeviceConfig()); LeAudioProfile leAudioProfile = mock(LeAudioProfile.class); when(leAudioProfile.getNameResource(mDevice)) .thenReturn(com.android.settingslib.R.string.bluetooth_profile_le_audio); when(leAudioProfile.isProfileReady()).thenReturn(true); when(leAudioProfile.toString()).thenReturn("LE_AUDIO"); when(mProfileManager.getLeAudioProfile()).thenReturn(leAudioProfile); when(mFeatureProvider.getInvisibleProfilePreferenceKeys(any(), any())) .thenReturn(ImmutableSet.of("A2DP")); mConnectableProfiles.add(leAudioProfile); showScreen(mController); List<SwitchPreferenceCompat> switches = getProfileSwitches(false); assertThat(switches.get(0).isVisible()).isTrue(); } }