1 /* 2 * Copyright 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 package com.android.settings.connecteddevice; 17 18 import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE; 19 import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.mockito.ArgumentMatchers.any; 24 import static org.mockito.ArgumentMatchers.anyString; 25 import static org.mockito.Mockito.doReturn; 26 import static org.mockito.Mockito.spy; 27 import static org.mockito.Mockito.times; 28 import static org.mockito.Mockito.verify; 29 import static org.mockito.Mockito.when; 30 import static org.robolectric.Shadows.shadowOf; 31 import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; 32 33 import android.app.settings.SettingsEnums; 34 import android.bluetooth.BluetoothAdapter; 35 import android.bluetooth.BluetoothDevice; 36 import android.bluetooth.BluetoothLeBroadcast; 37 import android.bluetooth.BluetoothLeBroadcastAssistant; 38 import android.bluetooth.BluetoothProfile; 39 import android.bluetooth.BluetoothStatusCodes; 40 import android.content.Context; 41 import android.content.pm.PackageManager; 42 import android.graphics.drawable.Drawable; 43 import android.media.AudioManager; 44 import android.os.Looper; 45 import android.platform.test.flag.junit.SetFlagsRule; 46 import android.util.Pair; 47 48 import androidx.appcompat.app.AlertDialog; 49 import androidx.fragment.app.FragmentActivity; 50 import androidx.fragment.app.FragmentManager; 51 import androidx.lifecycle.LifecycleOwner; 52 import androidx.preference.Preference; 53 import androidx.preference.PreferenceGroup; 54 import androidx.preference.PreferenceManager; 55 import androidx.preference.PreferenceScreen; 56 57 import com.android.settings.R; 58 import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater; 59 import com.android.settings.bluetooth.BluetoothDevicePreference; 60 import com.android.settings.bluetooth.Utils; 61 import com.android.settings.connecteddevice.audiosharing.AudioSharingDialogHandler; 62 import com.android.settings.testutils.FakeFeatureFactory; 63 import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; 64 import com.android.settings.testutils.shadow.ShadowAudioManager; 65 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; 66 import com.android.settings.testutils.shadow.ShadowBluetoothUtils; 67 import com.android.settingslib.bluetooth.BluetoothCallback; 68 import com.android.settingslib.bluetooth.BluetoothEventManager; 69 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 70 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; 71 import com.android.settingslib.bluetooth.HearingAidInfo; 72 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; 73 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; 74 import com.android.settingslib.bluetooth.LocalBluetoothManager; 75 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; 76 import com.android.settingslib.core.lifecycle.Lifecycle; 77 import com.android.settingslib.flags.Flags; 78 79 import org.junit.Before; 80 import org.junit.Rule; 81 import org.junit.Test; 82 import org.junit.runner.RunWith; 83 import org.mockito.Answers; 84 import org.mockito.Mock; 85 import org.mockito.junit.MockitoJUnit; 86 import org.mockito.junit.MockitoRule; 87 import org.robolectric.Robolectric; 88 import org.robolectric.RobolectricTestRunner; 89 import org.robolectric.RuntimeEnvironment; 90 import org.robolectric.annotation.Config; 91 import org.robolectric.shadow.api.Shadow; 92 93 import java.util.concurrent.Executor; 94 95 /** Tests for {@link AvailableMediaDeviceGroupController}. */ 96 @RunWith(RobolectricTestRunner.class) 97 @Config( 98 shadows = { 99 ShadowAudioManager.class, 100 ShadowBluetoothAdapter.class, 101 ShadowBluetoothUtils.class, 102 ShadowAlertDialogCompat.class, 103 }) 104 public class AvailableMediaDeviceGroupControllerTest { 105 @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); 106 @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); 107 108 private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; 109 private static final String PREFERENCE_KEY_1 = "pref_key_1"; 110 private static final String TEST_DEVICE_NAME = "test"; 111 112 @Mock private AvailableMediaBluetoothDeviceUpdater mAvailableMediaBluetoothDeviceUpdater; 113 @Mock private PreferenceScreen mPreferenceScreen; 114 115 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 116 private PreferenceManager mPreferenceManager; 117 118 @Mock private PackageManager mPackageManager; 119 @Mock private BluetoothEventManager mEventManager; 120 @Mock private LocalBluetoothManager mLocalBluetoothManager; 121 @Mock private LocalBluetoothProfileManager mLocalBtProfileManager; 122 @Mock private CachedBluetoothDeviceManager mCachedDeviceManager; 123 @Mock private LocalBluetoothLeBroadcast mBroadcast; 124 @Mock private LocalBluetoothLeBroadcastAssistant mAssistant; 125 @Mock private CachedBluetoothDevice mCachedBluetoothDevice; 126 @Mock private BluetoothDevice mDevice; 127 @Mock private Drawable mDrawable; 128 @Mock private AudioSharingDialogHandler mDialogHandler; 129 130 private PreferenceGroup mPreferenceGroup; 131 private Context mContext; 132 private FragmentManager mFragManager; 133 private FakeFeatureFactory mFeatureFactory; 134 private Preference mPreference; 135 private AvailableMediaDeviceGroupController mAvailableMediaDeviceGroupController; 136 private AudioManager mAudioManager; 137 private ShadowBluetoothAdapter mShadowBluetoothAdapter; 138 private LifecycleOwner mLifecycleOwner; 139 private Lifecycle mLifecycle; 140 141 @Before setUp()142 public void setUp() { 143 mContext = spy(RuntimeEnvironment.application); 144 mLifecycleOwner = () -> mLifecycle; 145 mLifecycle = new Lifecycle(mLifecycleOwner); 146 mPreference = new Preference(mContext); 147 mPreference.setKey(PREFERENCE_KEY_1); 148 mPreferenceGroup = spy(new PreferenceScreen(mContext, null)); 149 mFragManager = 150 Robolectric.setupActivity(FragmentActivity.class).getSupportFragmentManager(); 151 when(mPreferenceGroup.getPreferenceManager()).thenReturn(mPreferenceManager); 152 doReturn(mPackageManager).when(mContext).getPackageManager(); 153 doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); 154 155 mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); 156 mShadowBluetoothAdapter.setEnabled(true); 157 mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( 158 BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); 159 mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( 160 BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); 161 ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; 162 mLocalBluetoothManager = Utils.getLocalBtManager(mContext); 163 mFeatureFactory = FakeFeatureFactory.setupForTest(); 164 mAudioManager = mContext.getSystemService(AudioManager.class); 165 doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager(); 166 when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBtProfileManager); 167 when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); 168 when(mCachedDeviceManager.findDevice(any(BluetoothDevice.class))) 169 .thenReturn(mCachedBluetoothDevice); 170 when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); 171 172 mAvailableMediaDeviceGroupController = 173 spy(new AvailableMediaDeviceGroupController(mContext)); 174 mAvailableMediaDeviceGroupController.setBluetoothDeviceUpdater( 175 mAvailableMediaBluetoothDeviceUpdater); 176 mAvailableMediaDeviceGroupController.setDialogHandler(mDialogHandler); 177 mAvailableMediaDeviceGroupController.setFragmentManager(mFragManager); 178 mAvailableMediaDeviceGroupController.mPreferenceGroup = mPreferenceGroup; 179 } 180 181 @Test onDeviceAdded_firstAdd_becomeVisibleAndPreferenceAdded()182 public void onDeviceAdded_firstAdd_becomeVisibleAndPreferenceAdded() { 183 mAvailableMediaDeviceGroupController.onDeviceAdded(mPreference); 184 185 assertThat(mPreferenceGroup.isVisible()).isTrue(); 186 assertThat((Preference) mPreferenceGroup.findPreference(PREFERENCE_KEY_1)) 187 .isEqualTo(mPreference); 188 } 189 190 @Test onDeviceRemoved_lastRemove_becomeInvisibleAndPreferenceRemoved()191 public void onDeviceRemoved_lastRemove_becomeInvisibleAndPreferenceRemoved() { 192 mPreferenceGroup.addPreference(mPreference); 193 194 mAvailableMediaDeviceGroupController.onDeviceRemoved(mPreference); 195 196 assertThat(mPreferenceGroup.isVisible()).isFalse(); 197 assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0); 198 } 199 200 @Test onDeviceRemoved_notLastRemove_stillVisible()201 public void onDeviceRemoved_notLastRemove_stillVisible() { 202 mPreferenceGroup.setVisible(true); 203 mPreferenceGroup.addPreference(mPreference); 204 mPreferenceGroup.addPreference(new Preference(mContext)); 205 206 mAvailableMediaDeviceGroupController.onDeviceRemoved(mPreference); 207 208 assertThat(mPreferenceGroup.isVisible()).isTrue(); 209 } 210 211 @Test displayPreference_becomeInvisible()212 public void displayPreference_becomeInvisible() { 213 doReturn(mPreferenceGroup).when(mPreferenceScreen).findPreference(anyString()); 214 215 mAvailableMediaDeviceGroupController.displayPreference(mPreferenceScreen); 216 217 assertThat(mPreferenceGroup.isVisible()).isFalse(); 218 } 219 220 @Test testRegister_audioSharingFlagOff()221 public void testRegister_audioSharingFlagOff() { 222 mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); 223 setUpBroadcast(); 224 // register the callback in onStart() 225 mAvailableMediaDeviceGroupController.onStart(mLifecycleOwner); 226 shadowOf(Looper.getMainLooper()).idle(); 227 228 verify(mAvailableMediaBluetoothDeviceUpdater).registerCallback(); 229 verify(mEventManager).registerCallback(any(BluetoothCallback.class)); 230 verify(mAvailableMediaBluetoothDeviceUpdater).refreshPreference(); 231 verify(mBroadcast, times(0)) 232 .registerServiceCallBack( 233 any(Executor.class), any(BluetoothLeBroadcast.Callback.class)); 234 verify(mAssistant, times(0)) 235 .registerServiceCallBack( 236 any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class)); 237 verify(mDialogHandler, times(0)).registerCallbacks(any(Executor.class)); 238 } 239 240 @Test testRegister_audioSharingFlagOn()241 public void testRegister_audioSharingFlagOn() { 242 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); 243 setUpBroadcast(); 244 // register the callback in onStart() 245 mAvailableMediaDeviceGroupController.onStart(mLifecycleOwner); 246 shadowOf(Looper.getMainLooper()).idle(); 247 248 verify(mAvailableMediaBluetoothDeviceUpdater).registerCallback(); 249 verify(mEventManager).registerCallback(any(BluetoothCallback.class)); 250 verify(mAvailableMediaBluetoothDeviceUpdater).refreshPreference(); 251 verify(mBroadcast) 252 .registerServiceCallBack( 253 any(Executor.class), any(BluetoothLeBroadcast.Callback.class)); 254 verify(mAssistant) 255 .registerServiceCallBack( 256 any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class)); 257 verify(mDialogHandler).registerCallbacks(any(Executor.class)); 258 } 259 260 @Test testUnregister_audioSharingFlagOff()261 public void testUnregister_audioSharingFlagOff() { 262 mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); 263 setUpBroadcast(); 264 // unregister the callback in onStop() 265 mAvailableMediaDeviceGroupController.onStop(mLifecycleOwner); 266 verify(mAvailableMediaBluetoothDeviceUpdater).unregisterCallback(); 267 verify(mEventManager).unregisterCallback(any(BluetoothCallback.class)); 268 verify(mBroadcast, times(0)) 269 .unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class)); 270 verify(mAssistant, times(0)) 271 .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class)); 272 verify(mDialogHandler, times(0)).unregisterCallbacks(); 273 } 274 275 @Test testUnregister_audioSharingFlagOn()276 public void testUnregister_audioSharingFlagOn() { 277 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); 278 setUpBroadcast(); 279 // unregister the callback in onStop() 280 mAvailableMediaDeviceGroupController.onStop(mLifecycleOwner); 281 verify(mAvailableMediaBluetoothDeviceUpdater).unregisterCallback(); 282 verify(mEventManager).unregisterCallback(any(BluetoothCallback.class)); 283 verify(mBroadcast).unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class)); 284 verify(mAssistant) 285 .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class)); 286 verify(mDialogHandler).unregisterCallbacks(); 287 } 288 289 @Test testGetAvailabilityStatus_noBluetoothFeature_returnUnSupported()290 public void testGetAvailabilityStatus_noBluetoothFeature_returnUnSupported() { 291 doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); 292 293 assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus()) 294 .isEqualTo(UNSUPPORTED_ON_DEVICE); 295 } 296 297 @Test testGetAvailabilityStatus_BluetoothFeature_returnSupported()298 public void testGetAvailabilityStatus_BluetoothFeature_returnSupported() { 299 doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); 300 301 assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus()) 302 .isEqualTo(AVAILABLE_UNSEARCHABLE); 303 } 304 305 @Test setTitle_inCallState_showCallStateTitle()306 public void setTitle_inCallState_showCallStateTitle() { 307 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); 308 setUpBroadcast(); 309 mAudioManager.setMode(AudioManager.MODE_IN_CALL); 310 when(mBroadcast.isEnabled(null)).thenReturn(true); 311 mAvailableMediaDeviceGroupController.onAudioModeChanged(); 312 shadowOf(Looper.getMainLooper()).idle(); 313 314 assertThat(mPreferenceGroup.getTitle().toString()) 315 .isEqualTo(mContext.getString(R.string.connected_device_call_device_title)); 316 } 317 318 @Test setTitle_notInCallState_notInAudioSharing_showMediaStateTitle()319 public void setTitle_notInCallState_notInAudioSharing_showMediaStateTitle() { 320 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); 321 setUpBroadcast(); 322 mAudioManager.setMode(AudioManager.MODE_NORMAL); 323 when(mBroadcast.isEnabled(null)).thenReturn(false); 324 mAvailableMediaDeviceGroupController.onAudioModeChanged(); 325 shadowOf(Looper.getMainLooper()).idle(); 326 327 assertThat(mPreferenceGroup.getTitle().toString()) 328 .isEqualTo(mContext.getString(R.string.connected_device_media_device_title)); 329 } 330 331 @Test setTitle_notInCallState_audioSharingFlagOff_showMediaStateTitle()332 public void setTitle_notInCallState_audioSharingFlagOff_showMediaStateTitle() { 333 mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); 334 setUpBroadcast(); 335 mAudioManager.setMode(AudioManager.MODE_NORMAL); 336 when(mBroadcast.isEnabled(null)).thenReturn(true); 337 mAvailableMediaDeviceGroupController.onAudioModeChanged(); 338 shadowOf(Looper.getMainLooper()).idle(); 339 340 assertThat(mPreferenceGroup.getTitle().toString()) 341 .isEqualTo(mContext.getString(R.string.connected_device_media_device_title)); 342 } 343 344 @Test setTitle_notInCallState_inAudioSharing_showAudioSharingMediaStateTitle()345 public void setTitle_notInCallState_inAudioSharing_showAudioSharingMediaStateTitle() { 346 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); 347 setUpBroadcast(); 348 mAudioManager.setMode(AudioManager.MODE_NORMAL); 349 when(mBroadcast.isEnabled(null)).thenReturn(true); 350 mAvailableMediaDeviceGroupController.onAudioModeChanged(); 351 shadowOf(Looper.getMainLooper()).idle(); 352 353 assertThat(mPreferenceGroup.getTitle().toString()) 354 .isEqualTo(mContext.getString(R.string.audio_sharing_media_device_group_title)); 355 } 356 357 @Test onStart_localBluetoothManagerNull_shouldNotCrash()358 public void onStart_localBluetoothManagerNull_shouldNotCrash() { 359 mAvailableMediaDeviceGroupController.mBtManager = null; 360 361 // Shouldn't crash 362 mAvailableMediaDeviceGroupController.onStart(mLifecycleOwner); 363 } 364 365 @Test onStop_localBluetoothManagerNull_shouldNotCrash()366 public void onStop_localBluetoothManagerNull_shouldNotCrash() { 367 mAvailableMediaDeviceGroupController.mBtManager = null; 368 369 // Shouldn't crash 370 mAvailableMediaDeviceGroupController.onStop(mLifecycleOwner); 371 } 372 373 @Test onActiveDeviceChanged_hearingAidProfile_launchHearingAidPairingDialog()374 public void onActiveDeviceChanged_hearingAidProfile_launchHearingAidPairingDialog() { 375 when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true); 376 when(mCachedBluetoothDevice.getDeviceMode()) 377 .thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL); 378 when(mCachedBluetoothDevice.getDeviceSide()) 379 .thenReturn(HearingAidInfo.DeviceSide.SIDE_LEFT); 380 381 mAvailableMediaDeviceGroupController.onActiveDeviceChanged( 382 mCachedBluetoothDevice, BluetoothProfile.HEARING_AID); 383 shadowMainLooper().idle(); 384 385 final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); 386 assertThat(dialog.isShowing()).isTrue(); 387 } 388 389 @Test onDeviceClick_audioSharingOff_setActive()390 public void onDeviceClick_audioSharingOff_setActive() { 391 mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); 392 when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice); 393 Pair<Drawable, String> pair = new Pair<>(mDrawable, TEST_DEVICE_NAME); 394 when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pair); 395 BluetoothDevicePreference preference = 396 new BluetoothDevicePreference( 397 mContext, 398 mCachedBluetoothDevice, 399 true, 400 BluetoothDevicePreference.SortType.TYPE_NO_SORT); 401 mAvailableMediaDeviceGroupController.onDeviceClick(preference); 402 verify(mCachedBluetoothDevice).setActive(); 403 } 404 405 @Test onDeviceClick_audioSharingOn_dialogHandler()406 public void onDeviceClick_audioSharingOn_dialogHandler() { 407 mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); 408 setUpBroadcast(); 409 when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice); 410 Pair<Drawable, String> pair = new Pair<>(mDrawable, TEST_DEVICE_NAME); 411 when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pair); 412 BluetoothDevicePreference preference = 413 new BluetoothDevicePreference( 414 mContext, 415 mCachedBluetoothDevice, 416 true, 417 BluetoothDevicePreference.SortType.TYPE_NO_SORT); 418 mAvailableMediaDeviceGroupController.onDeviceClick(preference); 419 verify(mDialogHandler) 420 .handleDeviceConnected(mCachedBluetoothDevice, /* userTriggered= */ true); 421 verify(mFeatureFactory.metricsFeatureProvider) 422 .action(mContext, SettingsEnums.ACTION_MEDIA_DEVICE_CLICK); 423 } 424 setUpBroadcast()425 private void setUpBroadcast() { 426 mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( 427 BluetoothStatusCodes.FEATURE_SUPPORTED); 428 mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( 429 BluetoothStatusCodes.FEATURE_SUPPORTED); 430 when(mLocalBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast); 431 when(mLocalBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant); 432 mAvailableMediaDeviceGroupController = 433 spy(new AvailableMediaDeviceGroupController(mContext)); 434 mAvailableMediaDeviceGroupController.setBluetoothDeviceUpdater( 435 mAvailableMediaBluetoothDeviceUpdater); 436 mAvailableMediaDeviceGroupController.setDialogHandler(mDialogHandler); 437 mAvailableMediaDeviceGroupController.setFragmentManager(mFragManager); 438 mAvailableMediaDeviceGroupController.mPreferenceGroup = mPreferenceGroup; 439 } 440 } 441