1 /* 2 * Copyright (C) 2016 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.bluetooth; 17 18 import android.app.AlertDialog; 19 import android.content.Context; 20 import android.text.SpannableStringBuilder; 21 import android.text.TextUtils; 22 import android.view.View; 23 import android.widget.CheckBox; 24 import android.widget.TextView; 25 26 import com.android.settings.R; 27 import com.android.settings.SettingsRobolectricTestRunner; 28 import com.android.settings.TestConfig; 29 30 import org.junit.Before; 31 import org.junit.Test; 32 import org.junit.runner.RunWith; 33 import org.mockito.Mock; 34 import org.mockito.MockitoAnnotations; 35 import org.robolectric.annotation.Config; 36 import org.robolectric.shadows.ShadowApplication; 37 import org.robolectric.util.FragmentTestUtil; 38 39 import static com.google.common.truth.Truth.assertThat; 40 import static org.junit.Assert.fail; 41 import static org.mockito.Matchers.any; 42 import static org.mockito.Mockito.doNothing; 43 import static org.mockito.Mockito.times; 44 import static org.mockito.Mockito.verify; 45 import static org.mockito.Mockito.when; 46 47 @RunWith(SettingsRobolectricTestRunner.class) 48 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) 49 public class BluetoothPairingDialogTest { 50 51 private static final String FILLER = "text that goes in a view"; 52 private static final String FAKE_DEVICE_NAME = "Fake Bluetooth Device"; 53 54 @Mock 55 private BluetoothPairingController controller; 56 57 @Mock 58 private BluetoothPairingDialog dialogActivity; 59 60 @Before setUp()61 public void setUp() { 62 MockitoAnnotations.initMocks(this); 63 doNothing().when(dialogActivity).dismiss(); 64 } 65 66 @Test dialogUpdatesControllerWithUserInput()67 public void dialogUpdatesControllerWithUserInput() { 68 // set the correct dialog type 69 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG); 70 71 // we don't care about these for this test 72 when(controller.getDeviceVariantMessageId()) 73 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE); 74 when(controller.getDeviceVariantMessageHintId()) 75 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE); 76 77 // build fragment 78 BluetoothPairingDialogFragment frag = makeFragment(); 79 80 // test that controller is updated on text change 81 frag.afterTextChanged(new SpannableStringBuilder(FILLER)); 82 verify(controller, times(1)).updateUserInput(any()); 83 } 84 85 @Test dialogEnablesSubmitButtonOnValidationFromController()86 public void dialogEnablesSubmitButtonOnValidationFromController() { 87 // set the correct dialog type 88 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG); 89 90 // we don't care about these for this test 91 when(controller.getDeviceVariantMessageId()) 92 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE); 93 when(controller.getDeviceVariantMessageHintId()) 94 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE); 95 96 // force the controller to say that any passkey is valid 97 when(controller.isPasskeyValid(any())).thenReturn(true); 98 99 // build fragment 100 BluetoothPairingDialogFragment frag = makeFragment(); 101 102 // test that the positive button is enabled when passkey is valid 103 frag.afterTextChanged(new SpannableStringBuilder(FILLER)); 104 View button = frag.getmDialog().getButton(AlertDialog.BUTTON_POSITIVE); 105 assertThat(button).isNotNull(); 106 assertThat(button.getVisibility()).isEqualTo(View.VISIBLE); 107 } 108 109 @Test dialogDoesNotAskForPairCodeOnConsentVariant()110 public void dialogDoesNotAskForPairCodeOnConsentVariant() { 111 // set the dialog variant to confirmation/consent 112 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG); 113 114 // build the fragment 115 BluetoothPairingDialogFragment frag = makeFragment(); 116 117 // check that the input field used by the entry dialog fragment does not exist 118 View view = frag.getmDialog().findViewById(R.id.text); 119 assertThat(view).isNull(); 120 } 121 122 @Test dialogAsksForPairCodeOnUserEntryVariant()123 public void dialogAsksForPairCodeOnUserEntryVariant() { 124 // set the dialog variant to user entry 125 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG); 126 127 // we don't care about these for this test 128 when(controller.getDeviceVariantMessageId()) 129 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE); 130 when(controller.getDeviceVariantMessageHintId()) 131 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE); 132 133 // build the fragment 134 BluetoothPairingDialogFragment frag = makeFragment(); 135 136 // check that the pin/passkey input field is visible to the user 137 View view = frag.getmDialog().findViewById(R.id.text); 138 assertThat(view.getVisibility()).isEqualTo(View.VISIBLE); 139 } 140 141 @Test dialogDisplaysPairCodeOnDisplayPasskeyVariant()142 public void dialogDisplaysPairCodeOnDisplayPasskeyVariant() { 143 // set the dialog variant to display passkey 144 when(controller.getDialogType()) 145 .thenReturn(BluetoothPairingController.DISPLAY_PASSKEY_DIALOG); 146 147 // ensure that the controller returns good values to indicate a passkey needs to be shown 148 when(controller.isDisplayPairingKeyVariant()).thenReturn(true); 149 when(controller.hasPairingContent()).thenReturn(true); 150 when(controller.getPairingContent()).thenReturn(FILLER); 151 152 // build the fragment 153 BluetoothPairingDialogFragment frag = makeFragment(); 154 155 // get the relevant views 156 View messagePairing = frag.getmDialog().findViewById(R.id.pairing_code_message); 157 TextView pairingViewContent = 158 (TextView) frag.getmDialog().findViewById(R.id.pairing_subhead); 159 View pairingViewCaption = frag.getmDialog().findViewById(R.id.pairing_caption); 160 161 // check that the relevant views are visible and that the passkey is shown 162 assertThat(messagePairing.getVisibility()).isEqualTo(View.VISIBLE); 163 assertThat(pairingViewCaption.getVisibility()).isEqualTo(View.VISIBLE); 164 assertThat(pairingViewContent.getVisibility()).isEqualTo(View.VISIBLE); 165 assertThat(TextUtils.equals(FILLER, pairingViewContent.getText())).isTrue(); 166 } 167 168 @Test(expected = IllegalStateException.class) dialogThrowsExceptionIfNoControllerSet()169 public void dialogThrowsExceptionIfNoControllerSet() { 170 // instantiate a fragment 171 BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment(); 172 173 // this should throw an error 174 FragmentTestUtil.startFragment(frag); 175 fail("Starting the fragment with no controller set should have thrown an exception."); 176 } 177 178 @Test dialogCallsHookOnPositiveButtonPress()179 public void dialogCallsHookOnPositiveButtonPress() { 180 // set the dialog variant to confirmation/consent 181 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG); 182 183 // we don't care what this does, just that it is called 184 doNothing().when(controller).onDialogPositiveClick(any()); 185 186 // build the fragment 187 BluetoothPairingDialogFragment frag = makeFragment(); 188 189 // click the button and verify that the controller hook was called 190 frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_POSITIVE); 191 verify(controller, times(1)).onDialogPositiveClick(any()); 192 } 193 194 @Test dialogCallsHookOnNegativeButtonPress()195 public void dialogCallsHookOnNegativeButtonPress() { 196 // set the dialog variant to confirmation/consent 197 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG); 198 199 // we don't care what this does, just that it is called 200 doNothing().when(controller).onDialogNegativeClick(any()); 201 202 // build the fragment 203 BluetoothPairingDialogFragment frag = makeFragment(); 204 205 // click the button and verify that the controller hook was called 206 frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_NEGATIVE); 207 verify(controller, times(1)).onDialogNegativeClick(any()); 208 } 209 210 @Test(expected = IllegalStateException.class) dialogDoesNotAllowSwappingController()211 public void dialogDoesNotAllowSwappingController() { 212 // instantiate a fragment 213 BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment(); 214 frag.setPairingController(controller); 215 216 // this should throw an error 217 frag.setPairingController(controller); 218 fail("Setting the controller multiple times should throw an exception."); 219 } 220 221 @Test(expected = IllegalStateException.class) dialogDoesNotAllowSwappingActivity()222 public void dialogDoesNotAllowSwappingActivity() { 223 // instantiate a fragment 224 BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment(); 225 frag.setPairingDialogActivity(dialogActivity); 226 227 // this should throw an error 228 frag.setPairingDialogActivity(dialogActivity); 229 fail("Setting the dialog activity multiple times should throw an exception."); 230 } 231 232 @Test dialogPositiveButtonDisabledWhenUserInputInvalid()233 public void dialogPositiveButtonDisabledWhenUserInputInvalid() { 234 // set the correct dialog type 235 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG); 236 237 // we don't care about these for this test 238 when(controller.getDeviceVariantMessageId()) 239 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE); 240 when(controller.getDeviceVariantMessageHintId()) 241 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE); 242 243 // force the controller to say that any passkey is valid 244 when(controller.isPasskeyValid(any())).thenReturn(false); 245 246 // build fragment 247 BluetoothPairingDialogFragment frag = makeFragment(); 248 249 // test that the positive button is enabled when passkey is valid 250 frag.afterTextChanged(new SpannableStringBuilder(FILLER)); 251 View button = frag.getmDialog().getButton(AlertDialog.BUTTON_POSITIVE); 252 assertThat(button).isNotNull(); 253 assertThat(button.isEnabled()).isFalse(); 254 } 255 256 @Test dialogShowsContactSharingCheckboxWhenBluetoothProfileNotReady()257 public void dialogShowsContactSharingCheckboxWhenBluetoothProfileNotReady() { 258 // set the dialog variant to confirmation/consent 259 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG); 260 261 // set a fake device name and pretend the profile has not been set up for it 262 when(controller.getDeviceName()).thenReturn(FAKE_DEVICE_NAME); 263 when(controller.isProfileReady()).thenReturn(false); 264 265 // build the fragment 266 BluetoothPairingDialogFragment frag = makeFragment(); 267 268 // verify that the checkbox is visible and that the device name is correct 269 CheckBox sharingCheckbox = (CheckBox) frag.getmDialog() 270 .findViewById(R.id.phonebook_sharing_message_confirm_pin); 271 assertThat(sharingCheckbox.getVisibility()).isEqualTo(View.VISIBLE); 272 assertThat(sharingCheckbox.getText().toString().contains(FAKE_DEVICE_NAME)).isTrue(); 273 } 274 275 @Test dialogHidesContactSharingCheckboxWhenBluetoothProfileIsReady()276 public void dialogHidesContactSharingCheckboxWhenBluetoothProfileIsReady() { 277 // set the dialog variant to confirmation/consent 278 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG); 279 280 // set a fake device name and pretend the profile has been set up for it 281 when(controller.getDeviceName()).thenReturn(FAKE_DEVICE_NAME); 282 when(controller.isProfileReady()).thenReturn(true); 283 284 // build the fragment 285 BluetoothPairingDialogFragment frag = makeFragment(); 286 287 // verify that the checkbox is gone 288 CheckBox sharingCheckbox = (CheckBox) frag.getmDialog() 289 .findViewById(R.id.phonebook_sharing_message_confirm_pin); 290 assertThat(sharingCheckbox.getVisibility()).isEqualTo(View.GONE); 291 } 292 293 @Test dialogShowsMessageOnPinEntryView()294 public void dialogShowsMessageOnPinEntryView() { 295 // set the correct dialog type 296 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG); 297 298 // Set the message id to something specific to verify later 299 when(controller.getDeviceVariantMessageId()).thenReturn(R.string.cancel); 300 when(controller.getDeviceVariantMessageHintId()) 301 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE); 302 303 // build the fragment 304 BluetoothPairingDialogFragment frag = makeFragment(); 305 306 // verify message is what we expect it to be and is visible 307 TextView message = (TextView) frag.getmDialog().findViewById(R.id.message_below_pin); 308 assertThat(message.getVisibility()).isEqualTo(View.VISIBLE); 309 assertThat(TextUtils.equals(frag.getString(R.string.cancel), message.getText())).isTrue(); 310 } 311 312 @Test dialogShowsMessageHintOnPinEntryView()313 public void dialogShowsMessageHintOnPinEntryView() { 314 // set the correct dialog type 315 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG); 316 317 // Set the message id hint to something specific to verify later 318 when(controller.getDeviceVariantMessageHintId()).thenReturn(R.string.cancel); 319 when(controller.getDeviceVariantMessageId()) 320 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE); 321 322 // build the fragment 323 BluetoothPairingDialogFragment frag = makeFragment(); 324 325 // verify message is what we expect it to be and is visible 326 TextView hint = (TextView) frag.getmDialog().findViewById(R.id.pin_values_hint); 327 assertThat(hint.getVisibility()).isEqualTo(View.VISIBLE); 328 assertThat(TextUtils.equals(frag.getString(R.string.cancel), hint.getText())).isTrue(); 329 } 330 331 @Test dialogHidesMessageAndHintWhenNotProvidedOnPinEntryView()332 public void dialogHidesMessageAndHintWhenNotProvidedOnPinEntryView() { 333 // set the correct dialog type 334 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG); 335 336 // Set the id's to what is returned when it is not provided 337 when(controller.getDeviceVariantMessageHintId()) 338 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE); 339 when(controller.getDeviceVariantMessageId()) 340 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE); 341 342 // build the fragment 343 BluetoothPairingDialogFragment frag = makeFragment(); 344 345 // verify message is what we expect it to be and is visible 346 TextView hint = (TextView) frag.getmDialog().findViewById(R.id.pin_values_hint); 347 assertThat(hint.getVisibility()).isEqualTo(View.GONE); 348 TextView message = (TextView) frag.getmDialog().findViewById(R.id.message_below_pin); 349 assertThat(message.getVisibility()).isEqualTo(View.GONE); 350 } 351 352 @Test pairingStringIsFormattedCorrectly()353 public void pairingStringIsFormattedCorrectly() { 354 final String device = "test_device"; 355 final Context context = ShadowApplication.getInstance().getApplicationContext(); 356 assertThat(context.getString(R.string.bluetooth_pb_acceptance_dialog_text, device, device)) 357 .contains(device); 358 } 359 360 @Test pairingDialogDismissedOnPositiveClick()361 public void pairingDialogDismissedOnPositiveClick() { 362 // set the dialog variant to confirmation/consent 363 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG); 364 365 // we don't care what this does, just that it is called 366 doNothing().when(controller).onDialogPositiveClick(any()); 367 368 // build the fragment 369 BluetoothPairingDialogFragment frag = makeFragment(); 370 371 // click the button and verify that the controller hook was called 372 frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_POSITIVE); 373 374 verify(controller, times(1)).onDialogPositiveClick(any()); 375 verify(dialogActivity, times(1)).dismiss(); 376 } 377 378 @Test pairingDialogDismissedOnNegativeClick()379 public void pairingDialogDismissedOnNegativeClick() { 380 // set the dialog variant to confirmation/consent 381 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG); 382 383 // we don't care what this does, just that it is called 384 doNothing().when(controller).onDialogNegativeClick(any()); 385 386 // build the fragment 387 BluetoothPairingDialogFragment frag = makeFragment(); 388 389 // click the button and verify that the controller hook was called 390 frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_NEGATIVE); 391 392 verify(controller, times(1)).onDialogNegativeClick(any()); 393 verify(dialogActivity, times(1)).dismiss(); 394 } 395 makeFragment()396 private BluetoothPairingDialogFragment makeFragment() { 397 BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment(); 398 assertThat(frag.isPairingControllerSet()).isFalse(); 399 frag.setPairingController(controller); 400 assertThat(frag.isPairingDialogActivitySet()).isFalse(); 401 frag.setPairingDialogActivity(dialogActivity); 402 FragmentTestUtil.startFragment(frag); 403 assertThat(frag.getmDialog()).isNotNull(); 404 assertThat(frag.isPairingControllerSet()).isTrue(); 405 assertThat(frag.isPairingDialogActivitySet()).isTrue(); 406 return frag; 407 } 408 } 409