/* * Copyright (C) 2018 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.sound; import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET; import static android.media.AudioSystem.STREAM_MUSIC; import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; import static com.android.settingslib.media.flags.Flags.FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING; 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; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.media.AudioManager; import android.media.session.MediaSessionManager; import android.platform.test.flag.junit.SetFlagsRule; import android.util.FeatureFlagUtils; import androidx.preference.ListPreference; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import com.android.settings.bluetooth.Utils; import com.android.settings.core.FeatureFlags; import com.android.settings.testutils.shadow.ShadowAudioManager; import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.BluetoothEventManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HeadsetProfile; import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import org.junit.After; import org.junit.Before; import org.junit.Rule; 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; import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowBluetoothDevice; import org.robolectric.shadows.ShadowPackageManager; import java.util.ArrayList; import java.util.Collection; import java.util.List; @RunWith(RobolectricTestRunner.class) @Config(shadows = { ShadowAudioManager.class, ShadowBluetoothUtils.class, ShadowBluetoothDevice.class} ) public class AudioOutputSwitchPreferenceControllerTest { private static final String TEST_KEY = "Test_Key"; private static final String TEST_DEVICE_NAME_1 = "Test_A2DP_BT_Device_NAME_1"; private static final String TEST_DEVICE_NAME_2 = "Test_A2DP_BT_Device_NAME_2"; private static final String TEST_DEVICE_ADDRESS_1 = "00:A1:A1:A1:A1:A1"; private static final String TEST_DEVICE_ADDRESS_2 = "00:B2:B2:B2:B2:B2"; private static final String TEST_DEVICE_ADDRESS_3 = "00:C3:C3:C3:C3:C3"; private final static long HISYNCID1 = 10; private final static long HISYNCID2 = 11; @Mock private LocalBluetoothManager mLocalManager; @Mock private BluetoothEventManager mBluetoothEventManager; @Mock private LocalBluetoothProfileManager mLocalBluetoothProfileManager; @Mock private A2dpProfile mA2dpProfile; @Mock private HeadsetProfile mHeadsetProfile; @Mock private HearingAidProfile mHearingAidProfile; @Mock private LeAudioProfile mLeAudioProfile; @Mock private CachedBluetoothDeviceManager mCachedDeviceManager; @Mock private CachedBluetoothDevice mCachedBluetoothDeviceL; @Mock private CachedBluetoothDevice mCachedBluetoothDeviceR; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private Context mContext; private PreferenceScreen mScreen; private ListPreference mPreference; private AudioManager mAudioManager; private ShadowAudioManager mShadowAudioManager; private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private BluetoothDevice mBluetoothDevice; private BluetoothDevice mLeftBluetoothHapDevice; private BluetoothDevice mRightBluetoothHapDevice; private LocalBluetoothManager mLocalBluetoothManager; private AudioSwitchPreferenceController mController; private List mProfileConnectedDevices; private List mHearingAidActiveDevices; private List mLeAudioActiveDevices; private List mEmptyDevices; private ShadowPackageManager mPackageManager; private Collection mCachedDevices; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); mAudioManager = mContext.getSystemService(AudioManager.class); mShadowAudioManager = ShadowAudioManager.getShadow(); ShadowBluetoothUtils.sLocalBluetoothManager = mLocalManager; mLocalBluetoothManager = Utils.getLocalBtManager(mContext); when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager); when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); when(mLocalBluetoothProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); when(mLocalBluetoothProfileManager.getHeadsetProfile()).thenReturn(mHeadsetProfile); when(mLocalBluetoothProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); mPackageManager = Shadow.extract(mContext.getPackageManager()); mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true); mBluetoothManager = mContext.getSystemService(BluetoothManager.class); mBluetoothAdapter = mBluetoothManager.getAdapter(); mCachedDevices = new ArrayList<>(); mCachedDevices.add(mCachedBluetoothDeviceL); mCachedDevices.add(mCachedBluetoothDeviceR); when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices); mBluetoothDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_1)); when(mBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME_1); when(mBluetoothDevice.isConnected()).thenReturn(true); mLeftBluetoothHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_2)); when(mLeftBluetoothHapDevice.isConnected()).thenReturn(true); mRightBluetoothHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_3)); when(mRightBluetoothHapDevice.isConnected()).thenReturn(true); mController = new AudioSwitchPreferenceControllerTestable(mContext, TEST_KEY); mScreen = spy(new PreferenceScreen(mContext, null)); mPreference = new ListPreference(mContext); mProfileConnectedDevices = new ArrayList<>(); mHearingAidActiveDevices = new ArrayList<>(2); mLeAudioActiveDevices = new ArrayList<>(); mEmptyDevices = new ArrayList<>(2); when(mScreen.getPreferenceManager()).thenReturn(mock(PreferenceManager.class)); when(mScreen.getContext()).thenReturn(mContext); when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); mScreen.addPreference(mPreference); mController.displayPreference(mScreen); } @After public void tearDown() { ShadowBluetoothUtils.reset(); } @Test public void constructor_notSupportBluetooth_shouldReturnBeforeUsingLocalBluetoothManager() { ShadowBluetoothUtils.reset(); mLocalBluetoothManager = Utils.getLocalBtManager(mContext); AudioSwitchPreferenceController controller = new AudioSwitchPreferenceControllerTestable( mContext, TEST_KEY); controller.onStart(); controller.onStop(); assertThat(mLocalBluetoothManager).isNull(); } @Test public void getAvailabilityStatus_disableFlagNoBluetoothFeature_returnUnavailable() { FeatureFlagUtils.setEnabled(mContext, FeatureFlags.AUDIO_SWITCHER_SETTINGS, false); mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false); assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); } @Test public void getAvailabilityStatus_disableFlagWithBluetoothFeature_returnUnavailable() { FeatureFlagUtils.setEnabled(mContext, FeatureFlags.AUDIO_SWITCHER_SETTINGS, false); mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true); assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); } @Test public void getAvailabilityStatus_enableFlagWithBluetoothFeature_returnAvailable() { FeatureFlagUtils.setEnabled(mContext, FeatureFlags.AUDIO_SWITCHER_SETTINGS, true); mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true); assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); } @Test public void getAvailabilityStatus_enableFlagNoBluetoothFeature_returnUnavailable() { FeatureFlagUtils.setEnabled(mContext, FeatureFlags.AUDIO_SWITCHER_SETTINGS, true); mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false); assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); } @Test public void onStart_shouldRegisterCallbackAndRegisterReceiver() { mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING); mController.onStart(); verify(mLocalBluetoothManager.getEventManager()).registerCallback( any(BluetoothCallback.class)); verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class)); verify(mLocalBluetoothManager).setForegroundActivity(mContext); } @Test public void onStop_shouldUnregisterCallbackAndUnregisterReceiver() { mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING); mController.onStart(); mController.onStop(); verify(mLocalBluetoothManager.getEventManager()).unregisterCallback( any(BluetoothCallback.class)); verify(mContext).unregisterReceiver(any(BroadcastReceiver.class)); verify(mLocalBluetoothManager).setForegroundActivity(null); } @Test public void onStart_shouldRegisterCallbackAndRegisterReceiverWithDefaultMediaOutput() { MediaSessionManager mediaSessionManager = spy(mContext.getSystemService(MediaSessionManager.class)); mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING); when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mediaSessionManager); mController = new AudioSwitchPreferenceControllerTestable(mContext, TEST_KEY); mController.onStart(); verify(mLocalBluetoothManager.getEventManager()).registerCallback( any(BluetoothCallback.class)); verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class), eq(Context.RECEIVER_NOT_EXPORTED)); verify(mLocalBluetoothManager).setForegroundActivity(mContext); verify(mediaSessionManager).addOnActiveSessionsChangedListener( any(MediaSessionManager.OnActiveSessionsChangedListener.class), any(), any()); } @Test public void onStop_shouldUnregisterCallbackAndUnregisterReceiverWithDefaultMediaOutput() { MediaSessionManager mediaSessionManager = spy(mContext.getSystemService(MediaSessionManager.class)); mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING); when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mediaSessionManager); mController = new AudioSwitchPreferenceControllerTestable(mContext, TEST_KEY); mController.onStart(); mController.onStop(); verify(mLocalBluetoothManager.getEventManager()).unregisterCallback( any(BluetoothCallback.class)); verify(mContext).unregisterReceiver(any(BroadcastReceiver.class)); verify(mLocalBluetoothManager).setForegroundActivity(null); verify(mediaSessionManager).removeOnActiveSessionsChangedListener( any(MediaSessionManager.OnActiveSessionsChangedListener.class)); } /** * Audio stream output to bluetooth sco headset which is the subset of all sco device. * isStreamFromOutputDevice should return true. */ @Test public void isStreamFromOutputDevice_outputDeviceIsBtScoHeadset_shouldReturnTrue() { mShadowAudioManager.setOutputDevice(DEVICE_OUT_BLUETOOTH_SCO_HEADSET); assertThat(mController.isStreamFromOutputDevice( STREAM_MUSIC, DEVICE_OUT_BLUETOOTH_SCO_HEADSET)).isTrue(); } /** * Left side of HAP device is active. * findActiveHearingAidDevice should return hearing aid device active device. */ @Test public void findActiveHearingAidDevice_leftActiveDevice_returnLeftDeviceAsActiveHapDevice() { mController.mConnectedDevices.clear(); mController.mConnectedDevices.add(mBluetoothDevice); mController.mConnectedDevices.add(mLeftBluetoothHapDevice); mHearingAidActiveDevices.clear(); mHearingAidActiveDevices.add(mLeftBluetoothHapDevice); mHearingAidActiveDevices.add(null); when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice); when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices); assertThat(mController.findActiveHearingAidDevice()).isEqualTo(mLeftBluetoothHapDevice); } /** * Right side of HAP device is active. * findActiveHearingAidDevice should return hearing aid device active device. */ @Test public void findActiveHearingAidDevice_rightActiveDevice_returnRightDeviceAsActiveHapDevice() { mController.mConnectedDevices.clear(); mController.mConnectedDevices.add(mBluetoothDevice); mController.mConnectedDevices.add(mRightBluetoothHapDevice); mHearingAidActiveDevices.clear(); mHearingAidActiveDevices.add(null); mHearingAidActiveDevices.add(mRightBluetoothHapDevice); when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice); when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices); assertThat(mController.findActiveHearingAidDevice()).isEqualTo(mRightBluetoothHapDevice); } /** * Both are active device. * findActiveHearingAidDevice only return the active device in mConnectedDevices. */ @Test public void findActiveHearingAidDevice_twoActiveDevice_returnActiveDeviceInConnectedDevices() { mController.mConnectedDevices.clear(); mController.mConnectedDevices.add(mBluetoothDevice); mController.mConnectedDevices.add(mRightBluetoothHapDevice); mHearingAidActiveDevices.clear(); mHearingAidActiveDevices.add(mLeftBluetoothHapDevice); mHearingAidActiveDevices.add(mRightBluetoothHapDevice); when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice); when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices); assertThat(mController.findActiveHearingAidDevice()).isEqualTo(mRightBluetoothHapDevice); } /** * None of them are active. * findActiveHearingAidDevice should return null. */ @Test public void findActiveHearingAidDevice_noActiveDevice_returnNull() { mController.mConnectedDevices.clear(); mController.mConnectedDevices.add(mBluetoothDevice); mController.mConnectedDevices.add(mLeftBluetoothHapDevice); mHearingAidActiveDevices.clear(); when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice); when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices); assertThat(mController.findActiveHearingAidDevice()).isNull(); } /** * Two hearing aid devices with different HisyncId * getConnectedHearingAidDevices should add both device to list. */ @Test public void getConnectedHearingAidDevices_deviceHisyncIdIsDifferent_shouldAddBothToList() { mEmptyDevices.clear(); mProfileConnectedDevices.clear(); mProfileConnectedDevices.add(mLeftBluetoothHapDevice); mProfileConnectedDevices.add(mRightBluetoothHapDevice); when(mHearingAidProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); when(mHearingAidProfile.getHiSyncId(mLeftBluetoothHapDevice)).thenReturn(HISYNCID1); when(mHearingAidProfile.getHiSyncId(mRightBluetoothHapDevice)).thenReturn(HISYNCID2); mEmptyDevices.addAll(mController.getConnectedHearingAidDevices()); assertThat(mEmptyDevices).containsExactly(mLeftBluetoothHapDevice, mRightBluetoothHapDevice); } /** * Two hearing aid devices with same HisyncId * getConnectedHearingAidDevices should only add first device to list. */ @Test public void getConnectedHearingAidDevices_deviceHisyncIdIsSame_shouldAddOneToList() { mEmptyDevices.clear(); mProfileConnectedDevices.clear(); mProfileConnectedDevices.add(mLeftBluetoothHapDevice); mProfileConnectedDevices.add(mRightBluetoothHapDevice); when(mHearingAidProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); when(mHearingAidProfile.getHiSyncId(mLeftBluetoothHapDevice)).thenReturn(HISYNCID1); when(mHearingAidProfile.getHiSyncId(mRightBluetoothHapDevice)).thenReturn(HISYNCID1); mEmptyDevices.addAll(mController.getConnectedHearingAidDevices()); assertThat(mEmptyDevices).containsExactly(mLeftBluetoothHapDevice); } /** * One hands free profile device is connected. * getConnectedA2dpDevices should add this device to list. */ @Test public void getConnectedHfpDevices_oneConnectedHfpDevice_shouldAddDeviceToList() { mEmptyDevices.clear(); mProfileConnectedDevices.clear(); mProfileConnectedDevices.add(mBluetoothDevice); when(mHeadsetProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); mEmptyDevices.addAll(mController.getConnectedHfpDevices()); assertThat(mEmptyDevices).containsExactly(mBluetoothDevice); } /** * More than one hands free profile devices are connected. * getConnectedA2dpDevices should add all devices to list. */ @Test public void getConnectedHfpDevices_moreThanOneConnectedHfpDevice_shouldAddDeviceToList() { mEmptyDevices.clear(); mProfileConnectedDevices.clear(); mProfileConnectedDevices.add(mBluetoothDevice); mProfileConnectedDevices.add(mLeftBluetoothHapDevice); when(mHeadsetProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); mEmptyDevices.addAll(mController.getConnectedHfpDevices()); assertThat(mEmptyDevices).containsExactly(mBluetoothDevice, mLeftBluetoothHapDevice); } @Test public void getConnectedLeAudioDevices_connectedLeAudioDevice_shouldAddDeviceToList() { when(mCachedBluetoothDeviceL.getDevice()).thenReturn(mBluetoothDevice); mEmptyDevices.clear(); mProfileConnectedDevices.clear(); mProfileConnectedDevices.add(mBluetoothDevice); when(mLeAudioProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); mEmptyDevices.addAll(mController.getConnectedLeAudioDevices()); assertThat(mEmptyDevices).containsExactly(mBluetoothDevice); } @Test public void getConnectedLeAudioDevices_disconnectedLeAudioDevice_shouldNotAddDeviceToList() { BluetoothDevice connectedBtLeAduioDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_2)); when(connectedBtLeAduioDevice.isConnected()).thenReturn(true); BluetoothDevice disconnectedBtLeAduioDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_3)); when(disconnectedBtLeAduioDevice.isConnected()).thenReturn(false); when(mCachedBluetoothDeviceL.getDevice()).thenReturn(mBluetoothDevice); when(mCachedBluetoothDeviceR.getDevice()).thenReturn(connectedBtLeAduioDevice); mEmptyDevices.clear(); mProfileConnectedDevices.clear(); mProfileConnectedDevices.add(mBluetoothDevice); mProfileConnectedDevices.add(connectedBtLeAduioDevice); mProfileConnectedDevices.add(disconnectedBtLeAduioDevice); when(mLeAudioProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); mEmptyDevices.addAll(mController.getConnectedLeAudioDevices()); assertThat(mEmptyDevices).containsExactly(mBluetoothDevice, connectedBtLeAduioDevice); } @Test public void getConnectedLeAudioDevices_notInCachedDeviceList_shouldNotAddDeviceToList() { BluetoothDevice connectedBtLeAduioDevice1 = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_2)); when(connectedBtLeAduioDevice1.isConnected()).thenReturn(true); BluetoothDevice connectedBtLeAduioDevice2 = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_3)); when(connectedBtLeAduioDevice2.isConnected()).thenReturn(true); when(mCachedBluetoothDeviceL.getDevice()).thenReturn(mBluetoothDevice); when(mCachedBluetoothDeviceR.getDevice()).thenReturn(connectedBtLeAduioDevice1); mEmptyDevices.clear(); mProfileConnectedDevices.clear(); mProfileConnectedDevices.add(connectedBtLeAduioDevice1); mProfileConnectedDevices.add(connectedBtLeAduioDevice2); when(mLeAudioProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); mEmptyDevices.addAll(mController.getConnectedLeAudioDevices()); assertThat(mEmptyDevices).containsExactly(connectedBtLeAduioDevice1); } @Test public void getConnectedLeAudioDevices_nullConnectedDevices_returnEmptyList() { when(mLeAudioProfile.getConnectedDevices()).thenReturn(null); List connectedDevices = mController.getConnectedLeAudioDevices(); assertThat(connectedDevices.isEmpty()).isTrue(); } @Test public void findActiveLeAudioDevice_noActiveDevice_returnNull() { mLeAudioActiveDevices.clear(); when(mLeAudioProfile.getActiveDevices()).thenReturn(mLeAudioActiveDevices); assertThat(mController.findActiveLeAudioDevice()).isNull(); } @Test public void findActiveLeAudioDevice_withActiveDevice_returnActiveDevice() { mLeAudioActiveDevices.clear(); mLeAudioActiveDevices.add(mBluetoothDevice); when(mLeAudioProfile.getActiveDevices()).thenReturn(mLeAudioActiveDevices); assertThat(mController.findActiveLeAudioDevice()).isEqualTo(mBluetoothDevice); } private class AudioSwitchPreferenceControllerTestable extends AudioSwitchPreferenceController { AudioSwitchPreferenceControllerTestable(Context context, String key) { super(context, key); } @Override public BluetoothDevice findActiveDevice() { return null; } @Override public String getPreferenceKey() { return TEST_KEY; } } }