diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 20554429d0b..04523665ef8 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -265,6 +265,23 @@ 5 + + + Decide automatically + Play on hearing device + Play on phone speaker + + + + + + 0 + + 1 + + 2 + + diff --git a/res/xml/bluetooth_audio_routing_fragment.xml b/res/xml/bluetooth_audio_routing_fragment.xml new file mode 100644 index 00000000000..18f18f20740 --- /dev/null +++ b/res/xml/bluetooth_audio_routing_fragment.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingController.java b/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingController.java index 509a9b0c05d..4e40323c0d8 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingController.java @@ -17,10 +17,13 @@ package com.android.settings.bluetooth; import static com.android.settings.bluetooth.BluetoothDeviceDetailsFragment.FEATURE_AUDIO_ROUTING_ORDER; +import static com.android.settings.bluetooth.BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS; import android.content.Context; +import android.os.Bundle; import android.util.FeatureFlagUtils; +import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceFragmentCompat; @@ -36,7 +39,8 @@ import com.android.settingslib.core.lifecycle.Lifecycle; public class BluetoothDetailsAudioRoutingController extends BluetoothDetailsController { private static final String KEY_FEATURE_CONTROLS_GROUP = "feature_controls_group"; - private static final String KEY_AUDIO_ROUTING = "audio_routing"; + @VisibleForTesting + static final String KEY_AUDIO_ROUTING = "audio_routing"; public BluetoothDetailsAudioRoutingController(Context context, PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) { @@ -71,9 +75,13 @@ public class BluetoothDetailsAudioRoutingController extends BluetoothDetailsCont private Preference createAudioRoutingPreference(Context context) { final Preference preference = new Preference(context); + preference.setKey(KEY_AUDIO_ROUTING); preference.setTitle(context.getString(R.string.bluetooth_audio_routing_title)); preference.setSummary(context.getString(R.string.bluetooth_audio_routing_summary)); + final Bundle extras = preference.getExtras(); + extras.putString(KEY_DEVICE_ADDRESS, mCachedDevice.getAddress()); + preference.setFragment(BluetoothDetailsAudioRoutingFragment.class.getName()); return preference; } diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragment.java b/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragment.java new file mode 100644 index 00000000000..691aceeab1c --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragment.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 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.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; + +import static com.android.settings.bluetooth.BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; + +import com.android.settings.R; +import com.android.settings.dashboard.RestrictedDashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.search.SearchIndexable; + +/** Settings fragment containing bluetooth audio routing. */ +@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) +public class BluetoothDetailsAudioRoutingFragment extends RestrictedDashboardFragment { + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.bluetooth_audio_routing_fragment); + private static final String TAG = "BluetoothDetailsAudioRoutingFragment"; + @VisibleForTesting + CachedBluetoothDevice mCachedDevice; + + public BluetoothDetailsAudioRoutingFragment() { + super(DISALLOW_CONFIG_BLUETOOTH); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + final LocalBluetoothManager localBtMgr = Utils.getLocalBtManager(context); + final CachedBluetoothDeviceManager cachedDeviceMgr = localBtMgr.getCachedDeviceManager(); + final BluetoothDevice bluetoothDevice = localBtMgr.getBluetoothAdapter().getRemoteDevice( + getArguments().getString(KEY_DEVICE_ADDRESS)); + + mCachedDevice = cachedDeviceMgr.findDevice(bluetoothDevice); + if (mCachedDevice == null) { + // Close this page if device is null with invalid device mac address + Log.w(TAG, "onAttach() CachedDevice is null! Can not find address: " + + bluetoothDevice.getAnonymizedAddress()); + finish(); + return; + } + + // TODO: mCachedDevice will pass to control in next CLs. + } + + @Override + public int getMetricsCategory() { + // TODO(b/262839191): To be updated settings_enums.proto + return 0; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.bluetooth_audio_routing_fragment; + } + + @Override + protected String getLogTag() { + return TAG; + } +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingControllerTest.java index 92174f30222..ea65856de61 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingControllerTest.java @@ -16,12 +16,17 @@ package com.android.settings.bluetooth; +import static com.android.settings.bluetooth.BluetoothDetailsAudioRoutingController.KEY_AUDIO_ROUTING; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; import android.util.FeatureFlagUtils; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,6 +41,8 @@ public class BluetoothDetailsAudioRoutingControllerTest extends @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + private static final String TEST_ADDRESS = "55:66:77:88:99:AA"; + private BluetoothDetailsAudioRoutingController mController; @Override @@ -44,7 +51,9 @@ public class BluetoothDetailsAudioRoutingControllerTest extends mController = new BluetoothDetailsAudioRoutingController(mContext, mFragment, mCachedDevice, mLifecycle); - mController.init(mScreen); + final PreferenceCategory preferenceCategory = new PreferenceCategory(mContext); + preferenceCategory.setKey(mController.getPreferenceKey()); + mScreen.addPreference(preferenceCategory); } @Test @@ -64,4 +73,20 @@ public class BluetoothDetailsAudioRoutingControllerTest extends assertThat(mController.isAvailable()).isFalse(); } + + @Test + public void init_isHearingAidDevice_expectedAudioRoutingPreference() { + when(mCachedDevice.isHearingAidDevice()).thenReturn(true); + when(mCachedDevice.getAddress()).thenReturn(TEST_ADDRESS); + + mController.init(mScreen); + final Preference preference = mScreen.findPreference(KEY_AUDIO_ROUTING); + final String address = preference.getExtras().getString( + BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS); + final String fragment = preference.getFragment(); + + assertThat(address).isEqualTo(TEST_ADDRESS); + assertThat(fragment).isEqualTo(BluetoothDetailsAudioRoutingFragment.class.getName()); + + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragmentTest.java new file mode 100644 index 00000000000..b2da5798ead --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragmentTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 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.bluetooth.BluetoothDevice; +import android.content.Context; +import android.os.Bundle; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settings.testutils.XmlTestUtils; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.LocalBluetoothAdapter; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.List; + +/** Tests for {@link BluetoothDetailsAudioRoutingFragment}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothUtils.class}) +public class BluetoothDetailsAudioRoutingFragmentTest { + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + + private static final String TEST_ADDRESS = "55:66:77:88:99:AA"; + + private final Context mContext = ApplicationProvider.getApplicationContext(); + + private BluetoothDetailsAudioRoutingFragment mFragment; + @Mock + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private CachedBluetoothDeviceManager mCachedDeviceManager; + @Mock + private LocalBluetoothAdapter mLocalBluetoothAdapter; + @Mock + private BluetoothDevice mBluetoothDevice; + @Mock + private CachedBluetoothDevice mCachedDevice; + + @Before + public void setUp() { + setupEnvironment(); + + when(mLocalBluetoothAdapter.getRemoteDevice(TEST_ADDRESS)).thenReturn(mBluetoothDevice); + when(mCachedDevice.getAddress()).thenReturn(TEST_ADDRESS); + when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedDevice); + + mFragment = new BluetoothDetailsAudioRoutingFragment(); + } + + @Test + public void onAttach_setArgumentsWithAddress_expectedCachedDeviceWithAddress() { + final Bundle args = new Bundle(); + args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, TEST_ADDRESS); + mFragment.setArguments(args); + + mFragment.onAttach(mContext); + + assertThat(mFragment.mCachedDevice.getAddress()).isEqualTo(TEST_ADDRESS); + } + + @Test + public void getNonIndexableKeys_existInXmlLayout() { + final List niks = BluetoothDetailsAudioRoutingFragment.SEARCH_INDEX_DATA_PROVIDER + .getNonIndexableKeys(mContext); + final List keys = + XmlTestUtils.getKeysFromPreferenceXml(mContext, + R.xml.bluetooth_audio_routing_fragment); + + assertThat(keys).containsAtLeastElementsIn(niks); + } + + private void setupEnvironment() { + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; + when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); + when(mLocalBluetoothManager.getBluetoothAdapter()).thenReturn(mLocalBluetoothAdapter); + } +}