1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.sound; 18 19 import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; 20 import static android.media.AudioSystem.DEVICE_OUT_EARPIECE; 21 import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import static org.mockito.ArgumentMatchers.any; 26 import static org.mockito.Mockito.doReturn; 27 import static org.mockito.Mockito.mock; 28 import static org.mockito.Mockito.never; 29 import static org.mockito.Mockito.spy; 30 import static org.mockito.Mockito.verify; 31 import static org.mockito.Mockito.when; 32 33 import android.bluetooth.BluetoothAdapter; 34 import android.bluetooth.BluetoothDevice; 35 import android.bluetooth.BluetoothManager; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.pm.ApplicationInfo; 39 import android.content.pm.PackageInfo; 40 import android.content.pm.PackageStats; 41 import android.media.AudioAttributes; 42 import android.media.AudioManager; 43 import android.media.VolumeProvider; 44 import android.media.session.MediaController; 45 import android.media.session.MediaSessionManager; 46 import android.media.session.PlaybackState; 47 48 import androidx.preference.Preference; 49 import androidx.preference.PreferenceManager; 50 import androidx.preference.PreferenceScreen; 51 52 import com.android.settings.R; 53 import com.android.settings.bluetooth.Utils; 54 import com.android.settings.testutils.shadow.ShadowAudioManager; 55 import com.android.settings.testutils.shadow.ShadowBluetoothUtils; 56 import com.android.settingslib.bluetooth.A2dpProfile; 57 import com.android.settingslib.bluetooth.BluetoothEventManager; 58 import com.android.settingslib.bluetooth.HearingAidProfile; 59 import com.android.settingslib.bluetooth.LocalBluetoothManager; 60 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; 61 import com.android.settingslib.media.MediaOutputConstants; 62 63 import org.junit.After; 64 import org.junit.Before; 65 import org.junit.Ignore; 66 import org.junit.Test; 67 import org.junit.runner.RunWith; 68 import org.mockito.ArgumentCaptor; 69 import org.mockito.Mock; 70 import org.mockito.MockitoAnnotations; 71 import org.robolectric.RobolectricTestRunner; 72 import org.robolectric.RuntimeEnvironment; 73 import org.robolectric.Shadows; 74 import org.robolectric.annotation.Config; 75 import org.robolectric.shadows.ShadowBluetoothDevice; 76 import org.robolectric.shadows.ShadowPackageManager; 77 78 import java.util.ArrayList; 79 import java.util.List; 80 81 @RunWith(RobolectricTestRunner.class) 82 @Config(shadows = { 83 ShadowAudioManager.class, 84 ShadowBluetoothUtils.class, 85 ShadowBluetoothDevice.class} 86 ) 87 public class MediaOutputPreferenceControllerTest { 88 private static final String TEST_KEY = "Test_Key"; 89 private static final String TEST_DEVICE_NAME_1 = "Test_A2DP_BT_Device_NAME_1"; 90 private static final String TEST_DEVICE_NAME_2 = "Test_A2DP_BT_Device_NAME_2"; 91 private static final String TEST_HAP_DEVICE_NAME_1 = "Test_HAP_BT_Device_NAME_1"; 92 private static final String TEST_HAP_DEVICE_NAME_2 = "Test_HAP_BT_Device_NAME_2"; 93 private static final String TEST_DEVICE_ADDRESS_1 = "00:A1:A1:A1:A1:A1"; 94 private static final String TEST_DEVICE_ADDRESS_2 = "00:B2:B2:B2:B2:B2"; 95 private static final String TEST_DEVICE_ADDRESS_3 = "00:C3:C3:C3:C3:C3"; 96 private static final String TEST_DEVICE_ADDRESS_4 = "00:D4:D4:D4:D4:D4"; 97 private static final String TEST_PACKAGE_NAME = "com.test.packagename"; 98 private static final String TEST_APPLICATION_LABEL = "APP Test Label"; 99 100 @Mock 101 private LocalBluetoothManager mLocalManager; 102 @Mock 103 private BluetoothEventManager mBluetoothEventManager; 104 @Mock 105 private LocalBluetoothProfileManager mLocalBluetoothProfileManager; 106 @Mock 107 private A2dpProfile mA2dpProfile; 108 @Mock 109 private HearingAidProfile mHearingAidProfile; 110 @Mock 111 private AudioSwitchPreferenceController.AudioSwitchCallback mAudioSwitchPreferenceCallback; 112 @Mock 113 private MediaSessionManager mMediaSessionManager; 114 @Mock 115 private MediaController mMediaController; 116 117 private Context mContext; 118 private PreferenceScreen mScreen; 119 private Preference mPreference; 120 private AudioManager mAudioManager; 121 private ShadowAudioManager mShadowAudioManager; 122 private BluetoothManager mBluetoothManager; 123 private BluetoothAdapter mBluetoothAdapter; 124 private BluetoothDevice mBluetoothDevice; 125 private BluetoothDevice mSecondBluetoothDevice; 126 private BluetoothDevice mLeftBluetoothHapDevice; 127 private BluetoothDevice mRightBluetoothHapDevice; 128 private LocalBluetoothManager mLocalBluetoothManager; 129 private MediaOutputPreferenceController mController; 130 private List<BluetoothDevice> mProfileConnectedDevices; 131 private List<BluetoothDevice> mHearingAidActiveDevices; 132 private List<MediaController> mMediaControllers = new ArrayList<>(); 133 private MediaController.PlaybackInfo mPlaybackInfo; 134 private PlaybackState mPlaybackState; 135 private ShadowPackageManager mShadowPackageManager; 136 private ApplicationInfo mAppInfo; 137 private PackageInfo mPackageInfo; 138 private PackageStats mPackageStats; 139 140 @Before setUp()141 public void setUp() { 142 MockitoAnnotations.initMocks(this); 143 mContext = spy(RuntimeEnvironment.application); 144 145 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 146 mShadowAudioManager = ShadowAudioManager.getShadow(); 147 148 ShadowBluetoothUtils.sLocalBluetoothManager = mLocalManager; 149 mLocalBluetoothManager = Utils.getLocalBtManager(mContext); 150 151 doReturn(mMediaSessionManager).when(mContext).getSystemService(MediaSessionManager.class); 152 when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers); 153 when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME); 154 mPlaybackInfo = new MediaController.PlaybackInfo( 155 MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL, 156 VolumeProvider.VOLUME_CONTROL_ABSOLUTE, 157 100, 158 10, 159 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(), 160 null); 161 mPlaybackState = new PlaybackState.Builder() 162 .setState(PlaybackState.STATE_PLAYING, 0, 1) 163 .build(); 164 when(mMediaController.getPlaybackInfo()).thenReturn(mPlaybackInfo); 165 when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState); 166 mMediaControllers.add(mMediaController); 167 168 when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager); 169 when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); 170 when(mLocalBluetoothProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); 171 when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); 172 173 mBluetoothManager = new BluetoothManager(mContext); 174 mBluetoothAdapter = mBluetoothManager.getAdapter(); 175 176 mBluetoothDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_1)); 177 when(mBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME_1); 178 when(mBluetoothDevice.isConnected()).thenReturn(true); 179 180 mSecondBluetoothDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_2)); 181 when(mSecondBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME_2); 182 when(mSecondBluetoothDevice.isConnected()).thenReturn(true); 183 184 mLeftBluetoothHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_3)); 185 when(mLeftBluetoothHapDevice.getName()).thenReturn(TEST_HAP_DEVICE_NAME_1); 186 when(mLeftBluetoothHapDevice.isConnected()).thenReturn(true); 187 188 mRightBluetoothHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_4)); 189 when(mRightBluetoothHapDevice.getName()).thenReturn(TEST_HAP_DEVICE_NAME_2); 190 when(mRightBluetoothHapDevice.isConnected()).thenReturn(true); 191 192 mController = new MediaOutputPreferenceController(mContext, TEST_KEY); 193 mScreen = spy(new PreferenceScreen(mContext, null)); 194 mPreference = new Preference(mContext); 195 mProfileConnectedDevices = new ArrayList<>(); 196 mHearingAidActiveDevices = new ArrayList<>(2); 197 198 when(mScreen.getPreferenceManager()).thenReturn(mock(PreferenceManager.class)); 199 when(mScreen.getContext()).thenReturn(mContext); 200 when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); 201 mScreen.addPreference(mPreference); 202 mController.displayPreference(mScreen); 203 mController.setCallback(mAudioSwitchPreferenceCallback); 204 } 205 206 @After tearDown()207 public void tearDown() { 208 ShadowBluetoothUtils.reset(); 209 } 210 211 /** 212 * A2DP Bluetooth device(s) are connected, but no device is set as activated 213 * Preference summary should be "This device" 214 */ 215 @Test updateState_withConnectedBtDevice_withoutActiveBtDevice_setDefaultSummary()216 public void updateState_withConnectedBtDevice_withoutActiveBtDevice_setDefaultSummary() { 217 mShadowAudioManager.setOutputDevice(DEVICE_OUT_EARPIECE); 218 mAudioManager.setMode(AudioManager.MODE_NORMAL); 219 mProfileConnectedDevices.clear(); 220 mProfileConnectedDevices.add(mBluetoothDevice); 221 mProfileConnectedDevices.add(mSecondBluetoothDevice); 222 when(mA2dpProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); 223 when(mA2dpProfile.getActiveDevice()).thenReturn(null); 224 225 assertThat(mPreference.getSummary()).isNull(); 226 mController.updateState(mPreference); 227 assertThat(mPreference.getSummary()).isEqualTo( 228 mContext.getText(R.string.media_output_default_summary)); 229 } 230 231 /** 232 * A2DP Bluetooth device(s) are connected and active 233 * Preference summary should be device's name 234 */ 235 @Test 236 @Ignore updateState_withActiveBtDevice_setActivatedDeviceName()237 public void updateState_withActiveBtDevice_setActivatedDeviceName() { 238 mShadowAudioManager.setOutputDevice(DEVICE_OUT_BLUETOOTH_A2DP); 239 mAudioManager.setMode(AudioManager.MODE_NORMAL); 240 mProfileConnectedDevices.clear(); 241 mProfileConnectedDevices.add(mBluetoothDevice); 242 mProfileConnectedDevices.add(mSecondBluetoothDevice); 243 when(mA2dpProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); 244 when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice); 245 246 assertThat(mPreference.getSummary()).isNull(); 247 mController.updateState(mPreference); 248 assertThat(mPreference.getSummary()).isEqualTo(TEST_DEVICE_NAME_1); 249 } 250 251 /** 252 * Hearing Aid device(s) are connected and active 253 * Preference summary should be device's name 254 */ 255 @Test 256 @Ignore updateState_withActiveHADevice_setActivatedDeviceName()257 public void updateState_withActiveHADevice_setActivatedDeviceName() { 258 mShadowAudioManager.setOutputDevice(DEVICE_OUT_HEARING_AID); 259 mAudioManager.setMode(AudioManager.MODE_NORMAL); 260 mHearingAidActiveDevices.clear(); 261 mHearingAidActiveDevices.add(mLeftBluetoothHapDevice); 262 when(mHearingAidProfile.getConnectedDevices()).thenReturn(mHearingAidActiveDevices); 263 when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices); 264 265 assertThat(mPreference.getSummary()).isNull(); 266 mController.updateState(mPreference); 267 assertThat(mPreference.getSummary()).isEqualTo(TEST_HAP_DEVICE_NAME_1); 268 269 } 270 271 @Test updateState_noActiveLocalPlayback_noTitle()272 public void updateState_noActiveLocalPlayback_noTitle() { 273 mPlaybackState = new PlaybackState.Builder() 274 .setState(PlaybackState.STATE_NONE, 0, 1) 275 .build(); 276 when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState); 277 mController = new MediaOutputPreferenceController(mContext, TEST_KEY); 278 279 mController.updateState(mPreference); 280 281 assertThat(mPreference.getTitle()).isNull(); 282 } 283 284 @Test updateState_withActiveLocalPlayback_checkTitle()285 public void updateState_withActiveLocalPlayback_checkTitle() { 286 initPackage(); 287 mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); 288 289 mController.updateState(mPreference); 290 291 assertThat(mPreference.getTitle()).isEqualTo( 292 mContext.getString(R.string.media_output_label_title, TEST_APPLICATION_LABEL)); 293 } 294 295 @Test click_launch_outputSwitcherSlice()296 public void click_launch_outputSwitcherSlice() { 297 final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); 298 mController.handlePreferenceTreeClick(mPreference); 299 verify(mContext, never()).startActivity(intentCaptor.capture()); 300 301 mPreference.setKey(TEST_KEY); 302 mController.handlePreferenceTreeClick(mPreference); 303 verify(mContext).sendBroadcast(intentCaptor.capture()); 304 assertThat(intentCaptor.getValue().getAction()) 305 .isEqualTo(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG); 306 } 307 308 /** 309 * Default status 310 * Preference should be invisible 311 * Summary should be default summary 312 */ 313 @Test updateState_notInCall_preferenceVisible()314 public void updateState_notInCall_preferenceVisible() { 315 mAudioManager.setMode(AudioManager.MODE_NORMAL); 316 317 mController.updateState(mPreference); 318 319 assertThat(mPreference.isVisible()).isTrue(); 320 } 321 322 /** 323 * During a call 324 * Preference should be invisible 325 * Default string should be "Unavailable during calls" 326 */ 327 @Test updateState_inCall_preferenceInvisible()328 public void updateState_inCall_preferenceInvisible() { 329 mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); 330 331 mController.updateState(mPreference); 332 333 assertThat(mPreference.isVisible()).isFalse(); 334 } 335 336 @Test findActiveDevice_onlyA2dpDeviceActive_returnA2dpDevice()337 public void findActiveDevice_onlyA2dpDeviceActive_returnA2dpDevice() { 338 when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(null); 339 when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice); 340 341 assertThat(mController.findActiveDevice()).isEqualTo(mBluetoothDevice); 342 } 343 344 @Test findActiveDevice_allDevicesNotActive_returnNull()345 public void findActiveDevice_allDevicesNotActive_returnNull() { 346 when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(null); 347 when(mA2dpProfile.getActiveDevice()).thenReturn(null); 348 349 assertThat(mController.findActiveDevice()).isNull(); 350 } 351 initPackage()352 private void initPackage() { 353 mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); 354 mAppInfo = new ApplicationInfo(); 355 mAppInfo.flags = ApplicationInfo.FLAG_INSTALLED; 356 mAppInfo.packageName = TEST_PACKAGE_NAME; 357 mAppInfo.name = TEST_APPLICATION_LABEL; 358 mPackageInfo = new PackageInfo(); 359 mPackageInfo.packageName = TEST_PACKAGE_NAME; 360 mPackageInfo.applicationInfo = mAppInfo; 361 mPackageStats = new PackageStats(TEST_PACKAGE_NAME); 362 } 363 } 364