diff --git a/res/xml/sound_settings.xml b/res/xml/sound_settings.xml index 735b3b7d986..7ba1c760a3b 100644 --- a/res/xml/sound_settings.xml +++ b/res/xml/sound_settings.xml @@ -30,6 +30,14 @@ android:order="-180" settings:controller="com.android.settings.notification.MediaVolumePreferenceController"/> + + + + + + mConnectedDevices; + + private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback; + private final LocalBluetoothManager mLocalBluetoothManager; + private final MediaRouterCallback mMediaRouterCallback; + private final WiredHeadsetBroadcastReceiver mReceiver; + private final Handler mHandler; + + public AudioSwitchPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); + mLocalBluetoothManager = Utils.getLocalBtManager(mContext); + mLocalBluetoothManager.setForegroundActivity(context); + mProfileManager = mLocalBluetoothManager.getProfileManager(); + mHandler = new Handler(Looper.getMainLooper()); + mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback(); + mReceiver = new WiredHeadsetBroadcastReceiver(); + mMediaRouterCallback = new MediaRouterCallback(); + } + + /** + * Make this method as final, ensure that subclass will checking + * the feature flag and they could mistakenly break it via overriding. + */ + @Override + public final int getAvailabilityStatus() { + return FeatureFlagUtils.isEnabled(mContext, FeatureFlags.AUDIO_SWITCHER_SETTINGS) + ? AVAILABLE : DISABLED_UNSUPPORTED; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final String address = (String) newValue; + if (!(preference instanceof ListPreference)) { + return false; + } + + final ListPreference listPreference = (ListPreference) preference; + if (TextUtils.equals(address, mContext.getText(R.string.media_output_default_summary))) { + // Switch to default device which address is device name + mSelectedIndex = getDefaultDeviceIndex(); + setActiveBluetoothDevice(null); + listPreference.setSummary(mContext.getText(R.string.media_output_default_summary)); + } else { + // Switch to BT device which address is hardware address + final int connectedDeviceIndex = getConnectedDeviceIndex(address); + if (connectedDeviceIndex == INVALID_INDEX) { + return false; + } + final BluetoothDevice btDevice = mConnectedDevices.get(connectedDeviceIndex); + mSelectedIndex = connectedDeviceIndex; + setActiveBluetoothDevice(btDevice); + listPreference.setSummary(btDevice.getName()); + } + return true; + } + + public abstract void setActiveBluetoothDevice(BluetoothDevice device); + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(mPreferenceKey); + } + + @Override + public void onStart() { + register(); + } + + @Override + public void onStop() { + unregister(); + } + + /** + * Only concerned about whether the local adapter is connected to any profile of any device and + * are not really concerned about which profile. + */ + @Override + public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { + updateState(mPreference); + } + + @Override + public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { + updateState(mPreference); + } + + @Override + public void onAudioModeChanged() { + updateState(mPreference); + } + + @Override + public void onBluetoothStateChanged(int bluetoothState) { + } + + /** + * The local Bluetooth adapter has started the remote device discovery process. + */ + @Override + public void onScanningStateChanged(boolean started) { + } + + /** + * Indicates a change in the bond state of a remote + * device. For example, if a device is bonded (paired). + */ + @Override + public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { + updateState(mPreference); + } + + @Override + public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { + } + + @Override + public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { + } + + protected boolean isOngoingCallStatus() { + int audioMode = mAudioManager.getMode(); + return audioMode == AudioManager.MODE_RINGTONE + || audioMode == AudioManager.MODE_IN_CALL + || audioMode == AudioManager.MODE_IN_COMMUNICATION; + } + + int getDefaultDeviceIndex() { + // Default device is after all connected devices. + return ArrayUtils.size(mConnectedDevices); + } + + void setupPreferenceEntries(CharSequence[] mediaOutputs, CharSequence[] mediaValues, + BluetoothDevice activeDevice) { + // default to current device + mSelectedIndex = getDefaultDeviceIndex(); + // default device is after all connected devices. + mediaOutputs[mSelectedIndex] = mContext.getText(R.string.media_output_default_summary); + // use default device name as address + mediaValues[mSelectedIndex] = mContext.getText(R.string.media_output_default_summary); + for (int i = 0, size = mConnectedDevices.size(); i < size; i++) { + final BluetoothDevice btDevice = mConnectedDevices.get(i); + mediaOutputs[i] = btDevice.getName(); + mediaValues[i] = btDevice.getAddress(); + if (btDevice.equals(activeDevice)) { + // select the active connected device. + mSelectedIndex = i; + } + } + } + + void setPreference(CharSequence[] mediaOutputs, CharSequence[] mediaValues, + Preference preference) { + final ListPreference listPreference = (ListPreference) preference; + listPreference.setEntries(mediaOutputs); + listPreference.setEntryValues(mediaValues); + listPreference.setValueIndex(mSelectedIndex); + listPreference.setSummary(mediaOutputs[mSelectedIndex]); + } + + private int getConnectedDeviceIndex(String hardwareAddress) { + if (mConnectedDevices != null) { + for (int i = 0, size = mConnectedDevices.size(); i < size; i++) { + final BluetoothDevice btDevice = mConnectedDevices.get(i); + if (TextUtils.equals(btDevice.getAddress(), hardwareAddress)) { + return i; + } + } + } + return INVALID_INDEX; + } + + private void register() { + mLocalBluetoothManager.getEventManager().registerCallback(this); + mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler); + mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback); + + // Register for misc other intent broadcasts. + IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); + intentFilter.addAction(STREAM_DEVICES_CHANGED_ACTION); + mContext.registerReceiver(mReceiver, intentFilter); + } + + private void unregister() { + mLocalBluetoothManager.getEventManager().unregisterCallback(this); + mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback); + mMediaRouter.removeCallback(mMediaRouterCallback); + mContext.unregisterReceiver(mReceiver); + } + + /** Callback for headset plugged and unplugged events. */ + private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback { + @Override + public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { + updateState(mPreference); + } + + @Override + public void onAudioDevicesRemoved(AudioDeviceInfo[] devices) { + updateState(mPreference); + } + } + + /** Receiver for wired headset plugged and unplugged events. */ + private class WiredHeadsetBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (AudioManager.ACTION_HEADSET_PLUG.equals(action) || + AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) { + updateState(mPreference); + } + } + } + + /** Callback for cast device events. */ + private class MediaRouterCallback extends Callback { + @Override + public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo info) { + } + + @Override + public void onRouteUnselected(MediaRouter router, int type, MediaRouter.RouteInfo info) { + } + + @Override + public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) { + if (info != null && !info.isDefault()) { + // cast mode + updateState(mPreference); + } + } + + @Override + public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) { + } + + @Override + public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) { + if (info != null && !info.isDefault()) { + // cast mode + updateState(mPreference); + } + } + + @Override + public void onRouteGrouped(MediaRouter router, MediaRouter.RouteInfo info, + MediaRouter.RouteGroup group, int index) { + } + + @Override + public void onRouteUngrouped(MediaRouter router, MediaRouter.RouteInfo info, + MediaRouter.RouteGroup group) { + } + + @Override + public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo info) { + } + } +} diff --git a/src/com/android/settings/sound/HandsFreeProfileOutputPreferenceController.java b/src/com/android/settings/sound/HandsFreeProfileOutputPreferenceController.java new file mode 100644 index 00000000000..b0b3dc503d6 --- /dev/null +++ b/src/com/android/settings/sound/HandsFreeProfileOutputPreferenceController.java @@ -0,0 +1,94 @@ +/* + * 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 android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.support.v7.preference.Preference; + +import com.android.internal.util.ArrayUtils; +import com.android.settings.R; +import com.android.settingslib.bluetooth.HeadsetProfile; + +/** + * This class allows switching between HFP-connected BT devices + * while in on-call state. + */ +public class HandsFreeProfileOutputPreferenceController extends + AudioSwitchPreferenceController { + + public HandsFreeProfileOutputPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public void updateState(Preference preference) { + if (preference == null) { + // In case UI is not ready. + return; + } + + if (!isOngoingCallStatus()) { + // Without phone call, disable the switch entry. + preference.setEnabled(false); + preference.setSummary(mContext.getText(R.string.media_output_default_summary)); + return; + } + + // Ongoing call status, list all the connected devices support hands free profile. + // Select current active device. + // Disable switch entry if there is no connected device. + mConnectedDevices = null; + BluetoothDevice activeDevice = null; + + final HeadsetProfile headsetProfile = mProfileManager.getHeadsetProfile(); + if (headsetProfile != null) { + mConnectedDevices = headsetProfile.getConnectedDevices(); + activeDevice = headsetProfile.getActiveDevice(); + } + + final int numDevices = ArrayUtils.size(mConnectedDevices); + if (numDevices == 0) { + // No connected devices, disable switch entry. + preference.setEnabled(false); + preference.setSummary(mContext.getText(R.string.media_output_default_summary)); + return; + } + + preference.setEnabled(true); + CharSequence[] mediaOutputs = new CharSequence[numDevices + 1]; + CharSequence[] mediaValues = new CharSequence[numDevices + 1]; + + // Setup devices entries, select active connected device + setupPreferenceEntries(mediaOutputs, mediaValues, activeDevice); + + if (mAudioManager.isWiredHeadsetOn() && !mAudioManager.isBluetoothScoOn()) { + // If wired headset is plugged in and active, select to default device. + mSelectedIndex = getDefaultDeviceIndex(); + } + + // Display connected devices, default device and show the active device + setPreference(mediaOutputs, mediaValues, preference); + } + + @Override + public void setActiveBluetoothDevice(BluetoothDevice device) { + if (isOngoingCallStatus()) { + mProfileManager.getHeadsetProfile().setActiveDevice(device); + } + } +} diff --git a/src/com/android/settings/sound/MediaOutputPreferenceController.java b/src/com/android/settings/sound/MediaOutputPreferenceController.java new file mode 100644 index 00000000000..2e52f77b066 --- /dev/null +++ b/src/com/android/settings/sound/MediaOutputPreferenceController.java @@ -0,0 +1,116 @@ +/* + * 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.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.media.AudioManager; +import android.media.MediaRouter; +import android.support.v7.preference.Preference; + +import com.android.internal.util.ArrayUtils; +import com.android.settings.R; +import com.android.settingslib.bluetooth.A2dpProfile; + + +/** + * This class which allows switching between a2dp-connected BT devices. + * A few conditions will disable this switcher: + * - No available BT device(s) + * - Media stream captured by cast device + * - During a call. + */ +public class MediaOutputPreferenceController extends AudioSwitchPreferenceController { + + public MediaOutputPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public void updateState(Preference preference) { + if (preference == null) { + // In case UI is not ready. + return; + } + + if (mAudioManager.isMusicActiveRemotely() || isCastDevice(mMediaRouter)) { + // TODO(76455906): Workaround for cast mode, need a solid way to identify cast mode. + // In cast mode, disable switch entry. + preference.setEnabled(false); + preference.setSummary(mContext.getText(R.string.media_output_summary_unavailable)); + return; + } + + if (isOngoingCallStatus()) { + // Ongoing call status, switch entry for media will be disabled. + preference.setEnabled(false); + preference.setSummary( + mContext.getText(R.string.media_out_summary_ongoing_call_state)); + return; + } + + // Otherwise, list all of the A2DP connected device and display the active device. + mConnectedDevices = null; + BluetoothDevice activeDevice = null; + if (mAudioManager.getMode() == AudioManager.MODE_NORMAL) { + final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); + if (a2dpProfile != null) { + mConnectedDevices = a2dpProfile.getConnectedDevices(); + activeDevice = a2dpProfile.getActiveDevice(); + } + } + + final int numDevices = ArrayUtils.size(mConnectedDevices); + if (numDevices == 0) { + // Disable switch entry if there is no connected devices. + preference.setEnabled(false); + preference.setSummary(mContext.getText(R.string.media_output_default_summary)); + return; + } + + preference.setEnabled(true); + CharSequence[] mediaOutputs = new CharSequence[numDevices + 1]; + CharSequence[] mediaValues = new CharSequence[numDevices + 1]; + + // Setup devices entries, select active connected device + setupPreferenceEntries(mediaOutputs, mediaValues, activeDevice); + + if (mAudioManager.isWiredHeadsetOn() && !mAudioManager.isBluetoothA2dpOn()) { + // If wired headset is plugged in and active, select to default device. + mSelectedIndex = getDefaultDeviceIndex(); + } + + // Display connected devices, default device and show the active device + setPreference(mediaOutputs, mediaValues, preference); + } + + @Override + public void setActiveBluetoothDevice(BluetoothDevice device) { + if (mAudioManager.getMode() == AudioManager.MODE_NORMAL) { + mProfileManager.getA2dpProfile().setActiveDevice(device); + } + } + + private static boolean isCastDevice(MediaRouter mediaRouter) { + final MediaRouter.RouteInfo selected = mediaRouter.getSelectedRoute( + ROUTE_TYPE_REMOTE_DISPLAY); + return selected != null && selected.getPresentationDisplay() != null + && selected.getPresentationDisplay().isValid(); + } +} diff --git a/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java new file mode 100644 index 00000000000..2168a2ad62f --- /dev/null +++ b/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java @@ -0,0 +1,251 @@ +/* + * 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 com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.DISABLED_UNSUPPORTED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +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.support.v7.preference.ListPreference; +import android.support.v7.preference.PreferenceManager; +import android.support.v7.preference.PreferenceScreen; +import android.util.FeatureFlagUtils; + +import com.android.settings.R; +import com.android.settings.core.FeatureFlags; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowAudioManager; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settings.testutils.shadow.ShadowMediaRouter; +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.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowBluetoothDevice; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = { + ShadowAudioManager.class, + ShadowMediaRouter.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:07:80:78:A4:69"; + private static final String TEST_DEVICE_ADDRESS_2 = "00:00:00:00:00:00"; + + @Mock + private LocalBluetoothManager mLocalManager; + @Mock + private BluetoothEventManager mBluetoothEventManager; + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private LocalBluetoothProfileManager mLocalBluetoothProfileManager; + @Mock + private A2dpProfile mA2dpProfile; + + private Context mContext; + private PreferenceScreen mScreen; + private ListPreference mPreference; + private ShadowAudioManager mShadowAudioManager; + private ShadowMediaRouter mShadowMediaRouter; + private BluetoothManager mBluetoothManager; + private BluetoothAdapter mBluetoothAdapter; + private BluetoothDevice mBluetoothDevice; + private ShadowBluetoothDevice mShadowBluetoothDevice; + private LocalBluetoothManager mLocalBluetoothManager; + private AudioSwitchPreferenceController mController; + private List mConnectedDevices; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + + mShadowAudioManager = ShadowAudioManager.getShadow(); + mShadowMediaRouter = ShadowMediaRouter.getShadow(); + + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalManager; + mLocalBluetoothManager = ShadowBluetoothUtils.getLocalBtManager(mContext); + + when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager); + when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); + when(mLocalBluetoothProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); + + mBluetoothManager = new BluetoothManager(mContext); + mBluetoothAdapter = mBluetoothManager.getAdapter(); + + mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_1); + mShadowBluetoothDevice = Shadows.shadowOf(mBluetoothDevice); + mShadowBluetoothDevice.setName(TEST_DEVICE_NAME_1); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + + mController = new AudioSwitchPreferenceControllerTestable(mContext, TEST_KEY); + mScreen = spy(new PreferenceScreen(mContext, null)); + mPreference = new ListPreference(mContext); + mConnectedDevices = new ArrayList<>(1); + mConnectedDevices.add(mBluetoothDevice); + + 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() { + mShadowAudioManager.reset(); + mShadowMediaRouter.reset(); + ShadowBluetoothUtils.reset(); + } + + @Test + public void getAvailabilityStatus_byDefault_isAvailable() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_whenNotVisible_isDisable() { + FeatureFlagUtils.setEnabled(mContext, FeatureFlags.AUDIO_SWITCHER_SETTINGS, false); + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_UNSUPPORTED); + } + + @Test + public void onStart_shouldRegisterCallbackAndRegisterReceiver() { + mController.onStart(); + + verify(mLocalBluetoothManager.getEventManager()).registerCallback( + any(BluetoothCallback.class)); + verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class)); + } + + @Test + public void onStop_shouldUnregisterCallbackAndUnregisterReceiver() { + mController.onStart(); + mController.onStop(); + + verify(mLocalBluetoothManager.getEventManager()).unregisterCallback( + any(BluetoothCallback.class)); + verify(mContext).unregisterReceiver(any(BroadcastReceiver.class)); + } + + @Test + public void onPreferenceChange_toThisDevice_shouldSetDefaultSummary() { + mController.mConnectedDevices = mConnectedDevices; + + mController.onPreferenceChange(mPreference, + mContext.getText(R.string.media_output_default_summary)); + + assertThat(mPreference.getSummary()).isEqualTo( + mContext.getText(R.string.media_output_default_summary)); + } + + /** + * One Bluetooth devices are available, and select the device. + * Preference summary should be device name. + */ + @Test + public void onPreferenceChange_toBtDevice_shouldSetBtDeviceName() { + mController.mConnectedDevices = mConnectedDevices; + + mController.onPreferenceChange(mPreference, TEST_DEVICE_ADDRESS_1); + + assertThat(mPreference.getSummary()).isEqualTo(mBluetoothDevice.getName()); + } + + /** + * More than one Bluetooth devices are available, and select second device. + * Preference summary should be second device name. + */ + @Test + public void onPreferenceChange_toBtDevices_shouldSetSecondBtDeviceName() { + ShadowBluetoothDevice shadowBluetoothDevice; + BluetoothDevice secondBluetoothDevice; + secondBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_2); + shadowBluetoothDevice = Shadows.shadowOf(secondBluetoothDevice); + shadowBluetoothDevice.setName(TEST_DEVICE_NAME_2); + List connectedDevices = new ArrayList<>(2); + connectedDevices.add(mBluetoothDevice); + connectedDevices.add(secondBluetoothDevice); + mController.mConnectedDevices = connectedDevices; + + mController.onPreferenceChange(mPreference, TEST_DEVICE_ADDRESS_2); + + assertThat(mPreference.getSummary()).isEqualTo(secondBluetoothDevice.getName()); + } + + /** + * mConnectedDevices is Null. + * onPreferenceChange should return false. + */ + @Test + public void onPreferenceChange_connectedDeviceIsNull_shouldReturnFalse() { + mController.mConnectedDevices = null; + + assertThat(mController.onPreferenceChange(mPreference, TEST_DEVICE_ADDRESS_1)).isFalse(); + } + + private class AudioSwitchPreferenceControllerTestable extends + AudioSwitchPreferenceController { + AudioSwitchPreferenceControllerTestable(Context context, String key) { + super(context, key); + } + + @Override + public void setActiveBluetoothDevice(BluetoothDevice device) { + } + + @Override + public String getPreferenceKey() { + return null; + } + } +} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/sound/HandsFreeProfileOutputPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/sound/HandsFreeProfileOutputPreferenceControllerTest.java new file mode 100644 index 00000000000..c6c4b451c23 --- /dev/null +++ b/tests/robotests/src/com/android/settings/sound/HandsFreeProfileOutputPreferenceControllerTest.java @@ -0,0 +1,240 @@ +/* + * 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 com.google.common.truth.Truth.assertThat; + +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.Context; +import android.media.AudioManager; +import android.support.v7.preference.ListPreference; +import android.support.v7.preference.PreferenceManager; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowAudioManager; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settings.testutils.shadow.ShadowMediaRouter; +import com.android.settingslib.bluetooth.BluetoothEventManager; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HeadsetProfile; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowBluetoothDevice; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = { + ShadowAudioManager.class, + ShadowMediaRouter.class, + ShadowBluetoothUtils.class, + ShadowBluetoothDevice.class} +) +public class HandsFreeProfileOutputPreferenceControllerTest { + private static final String TEST_KEY = "Test_Key"; + private static final String TEST_DEVICE_NAME_1 = "Test_HAP_BT_Device_NAME_1"; + private static final String TEST_DEVICE_NAME_2 = "Test_HAP_BT_Device_NAME_2"; + private static final String TEST_DEVICE_ADDRESS_1 = "00:07:80:78:A4:69"; + private static final String TEST_DEVICE_ADDRESS_2 = "00:00:00:00:00:00"; + + @Mock + private LocalBluetoothManager mLocalManager; + @Mock + private BluetoothEventManager mBluetoothEventManager; + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private LocalBluetoothProfileManager mLocalBluetoothProfileManager; + @Mock + private HeadsetProfile mHeadsetProfile; + + private Context mContext; + private PreferenceScreen mScreen; + private ListPreference mPreference; + private ShadowAudioManager mShadowAudioManager; + private ShadowMediaRouter mShadowMediaRouter; + private BluetoothManager mBluetoothManager; + private BluetoothAdapter mBluetoothAdapter; + private BluetoothDevice mBluetoothDevice; + private ShadowBluetoothDevice mShadowBluetoothDevice; + private LocalBluetoothManager mLocalBluetoothManager; + private AudioSwitchPreferenceController mController; + private List mConnectedDevices; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + + mShadowAudioManager = ShadowAudioManager.getShadow(); + mShadowMediaRouter = ShadowMediaRouter.getShadow(); + + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalManager; + mLocalBluetoothManager = ShadowBluetoothUtils.getLocalBtManager(mContext); + + when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager); + when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); + when(mLocalBluetoothProfileManager.getHeadsetProfile()).thenReturn(mHeadsetProfile); + + mBluetoothManager = new BluetoothManager(mContext); + mBluetoothAdapter = mBluetoothManager.getAdapter(); + mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_1); + mShadowBluetoothDevice = Shadows.shadowOf(mBluetoothDevice); + mShadowBluetoothDevice.setName(TEST_DEVICE_NAME_1); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + + mController = new HandsFreeProfileOutputPreferenceController(mContext, TEST_KEY); + mScreen = spy(new PreferenceScreen(mContext, null)); + mPreference = new ListPreference(mContext); + mConnectedDevices = new ArrayList<>(1); + mConnectedDevices.add(mBluetoothDevice); + + 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() { + mShadowAudioManager.reset(); + mShadowMediaRouter.reset(); + ShadowBluetoothUtils.reset(); + } + + @Test + public void setActiveBluetoothDevice_duringACalling_shouldSetBtDeviceActive() { + mShadowAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); + + mController.setActiveBluetoothDevice(mBluetoothDevice); + + verify(mHeadsetProfile).setActiveDevice(mBluetoothDevice); + } + + @Test + public void updateState_shouldSetSummary() { + mController.updateState(mPreference); + + assertThat(mPreference.getSummary()).isEqualTo( + mContext.getText(R.string.media_output_default_summary)); + } + + /** + * One Headset Bluetooth device is available and activated + * Preference should be enabled + * Preference summary should be activate device name + */ + @Test + public void updateState_oneHeadsetsAvailableAndActivated_shouldSetDeviceName() { + mShadowAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); + when(mHeadsetProfile.getConnectedDevices()).thenReturn(mConnectedDevices); + when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.getSummary()).isEqualTo(mBluetoothDevice.getName()); + } + + /** + * More than one Headset Bluetooth devices are available, and second device is active. + * Preference should be enabled + * Preference summary should be activate device name + */ + @Test + public void updateState_moreThanOneHapBtDevicesAreAvailable_shouldSetActivatedDeviceName() { + ShadowBluetoothDevice shadowBluetoothDevice; + mShadowAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); + BluetoothDevice secondBluetoothDevice; + secondBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_2); + shadowBluetoothDevice = Shadows.shadowOf(secondBluetoothDevice); + shadowBluetoothDevice.setName(TEST_DEVICE_NAME_2); + List connectedDevices = new ArrayList<>(2); + connectedDevices.add(mBluetoothDevice); + connectedDevices.add(secondBluetoothDevice); + + when(mHeadsetProfile.getConnectedDevices()).thenReturn(connectedDevices); + when(mHeadsetProfile.getActiveDevice()).thenReturn(secondBluetoothDevice); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.getSummary()).isEqualTo(secondBluetoothDevice.getName()); + } + + /** + * Hands Free Profile Bluetooth device(s) are available, but wired headset is plugged in + * and activated. + * Preference should be enabled + * Preference summary should be "This device" + */ + @Test + public void hapBtDevicesAreAvailableButWiredHeadsetIsActivated_shouldSetDefaultSummary() { + mShadowAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); + mShadowAudioManager.setWiredHeadsetOn(true); + mShadowAudioManager.setBluetoothScoOn(false); + when(mHeadsetProfile.getConnectedDevices()).thenReturn(mConnectedDevices); + when(mHeadsetProfile.getActiveDevice()).thenReturn( + mBluetoothDevice); // BT device is still activated in this case + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.getSummary()).isEqualTo( + mContext.getText(R.string.media_output_default_summary)); + } + + /** + * No available Headset BT devices + * Preference should be disabled + * Preference summary should be "This device" + */ + @Test + public void noAvailableHeadsetBtDevices_preferenceEnableIsFalse_shouldSetDefaultSummary() { + mShadowAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); + List emptyDeviceList = new ArrayList<>(); + when(mHeadsetProfile.getConnectedDevices()).thenReturn(emptyDeviceList); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + assertThat(mPreference.getSummary()).isEqualTo( + mContext.getText(R.string.media_output_default_summary)); + } +} diff --git a/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java new file mode 100644 index 00000000000..2b15b8e0145 --- /dev/null +++ b/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java @@ -0,0 +1,290 @@ +/* + * 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 com.google.common.truth.Truth.assertThat; + +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.Context; +import android.media.AudioManager; +import android.support.v7.preference.ListPreference; +import android.support.v7.preference.PreferenceManager; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowAudioManager; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settings.testutils.shadow.ShadowMediaRouter; +import com.android.settingslib.bluetooth.A2dpProfile; +import com.android.settingslib.bluetooth.BluetoothEventManager; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowBluetoothDevice; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = { + ShadowAudioManager.class, + ShadowMediaRouter.class, + ShadowBluetoothUtils.class, + ShadowBluetoothDevice.class} +) +public class MediaOutputPreferenceControllerTest { + 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:07:80:78:A4:69"; + private static final String TEST_DEVICE_ADDRESS_2 = "00:00:00:00:00:00"; + + @Mock + private LocalBluetoothManager mLocalManager; + @Mock + private BluetoothEventManager mBluetoothEventManager; + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private LocalBluetoothProfileManager mLocalBluetoothProfileManager; + @Mock + private A2dpProfile mA2dpProfile; + + private Context mContext; + private PreferenceScreen mScreen; + private ListPreference mPreference; + private ShadowAudioManager mShadowAudioManager; + private ShadowMediaRouter mShadowMediaRouter; + private BluetoothManager mBluetoothManager; + private BluetoothAdapter mBluetoothAdapter; + private BluetoothDevice mBluetoothDevice; + private ShadowBluetoothDevice mShadowBluetoothDevice; + private LocalBluetoothManager mLocalBluetoothManager; + private AudioSwitchPreferenceController mController; + private List mConnectedDevices; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + + mShadowAudioManager = ShadowAudioManager.getShadow(); + mShadowMediaRouter = ShadowMediaRouter.getShadow(); + + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalManager; + mLocalBluetoothManager = ShadowBluetoothUtils.getLocalBtManager(mContext); + + when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager); + when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); + when(mLocalBluetoothProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); + + mBluetoothManager = new BluetoothManager(mContext); + mBluetoothAdapter = mBluetoothManager.getAdapter(); + mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_1); + mShadowBluetoothDevice = Shadows.shadowOf(mBluetoothDevice); + mShadowBluetoothDevice.setName(TEST_DEVICE_NAME_1); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + + mController = new MediaOutputPreferenceController(mContext, TEST_KEY); + mScreen = spy(new PreferenceScreen(mContext, null)); + mPreference = new ListPreference(mContext); + mConnectedDevices = new ArrayList<>(1); + mConnectedDevices.add(mBluetoothDevice); + + 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() { + mShadowAudioManager.reset(); + mShadowMediaRouter.reset(); + ShadowBluetoothUtils.reset(); + } + + @Test + public void setActiveBluetoothDevice_withoutRingAndCall_shouldSetBtDeviceActive() { + mShadowAudioManager.setMode(AudioManager.MODE_NORMAL); + + mController.setActiveBluetoothDevice(mBluetoothDevice); + + verify(mA2dpProfile).setActiveDevice(mBluetoothDevice); + } + + @Test + public void updateState_shouldSetSummary() { + mController.updateState(mPreference); + + assertThat(mPreference.getSummary()).isEqualTo( + mContext.getText(R.string.media_output_default_summary)); + } + + /** + * On going call state: + * Preference should be disabled + * Default string should be "Unavailable during calls" + */ + @Test + public void updateState_duringACall_shouldSetDefaultSummary() { + mShadowAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + assertThat(mPreference.getSummary()).isEqualTo( + mContext.getText(R.string.media_out_summary_ongoing_call_state)); + } + + /** + * No available A2dp BT devices: + * Preference should be disabled + * Preference summary should be "This device" + */ + @Test + public void updateState_noAvailableA2dpBtDevices_shouldDisableAndSetDefaultSummary() { + mShadowAudioManager.setMode(AudioManager.MODE_NORMAL); + List emptyDeviceList = new ArrayList<>(); + when(mA2dpProfile.getConnectedDevices()).thenReturn(emptyDeviceList); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + String defaultString = mContext.getString(R.string.media_output_default_summary); + assertThat(mPreference.getSummary()).isEqualTo(defaultString); + } + + /** + * Media stream is captured by something else (cast device): + * Preference should be disabled + * Preference summary should be "unavailable" + */ + @Test + public void updateState_mediaStreamIsCapturedByCast_shouldDisableAndSetDefaultSummary() { + mShadowAudioManager.setMusicActiveRemotely(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + String defaultString = mContext.getString(R.string.media_output_summary_unavailable); + assertThat(mPreference.getSummary()).isEqualTo(defaultString); + } + + /** + * One A2DP Bluetooth device is available and active. + * Preference should be enabled + * Preference summary should be activate device name + */ + @Test + public void updateState_oneA2dpBtDeviceAreAvailable_shouldSetActivatedDeviceName() { + mShadowAudioManager.setMode(AudioManager.MODE_NORMAL); + when(mA2dpProfile.getConnectedDevices()).thenReturn(mConnectedDevices); + when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.getSummary()).isEqualTo(mBluetoothDevice.getName()); + } + + /** + * More than one A2DP Bluetooth devices are available, and second device is active. + * Preference should be enabled + * Preference summary should be activate device name + */ + @Test + public void updateState_moreThanOneA2DpBtDevicesAreAvailable_shouldSetActivatedDeviceName() { + ShadowBluetoothDevice shadowBluetoothDevice; + mShadowAudioManager.setMode(AudioManager.MODE_NORMAL); + BluetoothDevice secondBluetoothDevice; + secondBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_2); + shadowBluetoothDevice = Shadows.shadowOf(secondBluetoothDevice); + shadowBluetoothDevice.setName(TEST_DEVICE_NAME_2); + List connectedDevices = new ArrayList<>(2); + connectedDevices.add(mBluetoothDevice); + connectedDevices.add(secondBluetoothDevice); + + when(mA2dpProfile.getConnectedDevices()).thenReturn(connectedDevices); + when(mA2dpProfile.getActiveDevice()).thenReturn(secondBluetoothDevice); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.getSummary()).isEqualTo(secondBluetoothDevice.getName()); + } + + /** + * A2DP Bluetooth device(s) are available, but wired headset is plugged in and activated + * Preference should be enabled + * Preference summary should be "This device" + */ + @Test + public void updateState_a2dpDevicesAvailableWiredHeadsetIsActivated_shouldSetDefaultSummary() { + mShadowAudioManager.setMode(AudioManager.MODE_NORMAL); + mShadowAudioManager.setWiredHeadsetOn(true); + mShadowAudioManager.setBluetoothA2dpOn(false); + when(mA2dpProfile.getConnectedDevices()).thenReturn(mConnectedDevices); + when(mA2dpProfile.getActiveDevice()).thenReturn( + mBluetoothDevice); // BT device is still activated in this case + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + String defaultString = mContext.getString(R.string.media_output_default_summary); + assertThat(mPreference.getSummary()).isEqualTo(defaultString); + } + + + /** + * A2DP Bluetooth device(s) are available, but current device speaker is activated + * Preference should be enabled + * Preference summary should be "This device" + */ + @Test + public void updateState_a2dpDevicesAvailableCurrentDeviceActivated_shouldSetDefaultSummary() { + mShadowAudioManager.setMode(AudioManager.MODE_NORMAL); + when(mA2dpProfile.getConnectedDevices()).thenReturn(mConnectedDevices); + when(mA2dpProfile.getActiveDevice()).thenReturn(null); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + String defaultString = mContext.getString(R.string.media_output_default_summary); + assertThat(mPreference.getSummary()).isEqualTo(defaultString); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioManager.java index ed53eeaaf54..6817648f09b 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioManager.java @@ -16,23 +16,58 @@ package com.android.settings.testutils.shadow; +import static org.robolectric.RuntimeEnvironment.application; + +import android.media.AudioDeviceCallback; import android.media.AudioManager; +import android.os.Handler; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; +import org.robolectric.shadow.api.Shadow; -@Implements(AudioManager.class) -public class ShadowAudioManager { +import java.util.ArrayList; +@Implements(value = AudioManager.class, inheritImplementationMethods = true) +public class ShadowAudioManager extends org.robolectric.shadows.ShadowAudioManager { private int mRingerMode; - - @Implementation - public void setRingerModeInternal(int mode) { - mRingerMode = mode; - } + private boolean mMusicActiveRemotely = false; + private ArrayList mDeviceCallbacks = new ArrayList(); @Implementation private int getRingerModeInternal() { return mRingerMode; } + + public static ShadowAudioManager getShadow() { + return Shadow.extract(application.getSystemService(AudioManager.class)); + } + + public void setRingerModeInternal(int mode) { + mRingerMode = mode; + } + + public void registerAudioDeviceCallback(AudioDeviceCallback callback, Handler handler) { + mDeviceCallbacks.add(callback); + } + + public void unregisterAudioDeviceCallback(AudioDeviceCallback callback) { + if (mDeviceCallbacks.contains(callback)) { + mDeviceCallbacks.remove(callback); + } + } + + public void setMusicActiveRemotely(boolean flag) { + mMusicActiveRemotely = flag; + } + + public boolean isMusicActiveRemotely() { + return mMusicActiveRemotely; + } + + @Resetter + public void reset() { + mDeviceCallbacks.clear(); + } } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowBluetoothUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowBluetoothUtils.java new file mode 100644 index 00000000000..fcf79e2ff72 --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowBluetoothUtils.java @@ -0,0 +1,43 @@ +/* + * 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.testutils.shadow; + +import android.content.Context; + +import com.android.settings.bluetooth.Utils; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; + +@Implements(Utils.class) +public class ShadowBluetoothUtils { + public static LocalBluetoothManager sLocalBluetoothManager; + + + + @Implementation + public static LocalBluetoothManager getLocalBtManager(Context context) { + return sLocalBluetoothManager; + } + + @Resetter + public static void reset() { + sLocalBluetoothManager = null; + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowMediaRouter.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowMediaRouter.java new file mode 100644 index 00000000000..faaa0f05bfd --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowMediaRouter.java @@ -0,0 +1,57 @@ +/* + * 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.testutils.shadow; + +import android.media.MediaRouter; + +import static org.robolectric.RuntimeEnvironment.application; + +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; +import org.robolectric.shadow.api.Shadow; + +import java.util.concurrent.CopyOnWriteArrayList; + +@Implements(value = MediaRouter.class, inheritImplementationMethods = true) +public class ShadowMediaRouter extends org.robolectric.shadows.ShadowMediaRouter { + MediaRouter.RouteInfo mSelectedRoute; + + final CopyOnWriteArrayList mCallbacks = + new CopyOnWriteArrayList<>(); + + public MediaRouter.RouteInfo getSelectedRoute(int type) { + return mSelectedRoute; + } + + public void addCallback(int types, MediaRouter.Callback cb) { + mCallbacks.add(cb); + } + + public void removeCallback(MediaRouter.Callback cb) { + if (mCallbacks.contains(cb)) + mCallbacks.remove(cb); + } + + public static ShadowMediaRouter getShadow() { + return Shadow.extract(application.getSystemService(MediaRouter.class)); + } + + @Resetter + public void reset() { + mCallbacks.clear(); + } +}