2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.settings.bluetooth;
18 import static com.google.common.truth.Truth.assertThat;
20 import static org.junit.Assert.fail;
21 import static org.mockito.Matchers.any;
22 import static org.mockito.Mockito.doNothing;
23 import static org.mockito.Mockito.doReturn;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.spy;
26 import static org.mockito.Mockito.times;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.when;
30 import android.app.AlertDialog;
31 import android.app.Dialog;
32 import android.content.Context;
33 import android.text.SpannableStringBuilder;
34 import android.text.TextUtils;
35 import android.view.View;
36 import android.view.inputmethod.InputMethodManager;
37 import android.widget.CheckBox;
38 import android.widget.TextView;
40 import com.android.settings.R;
41 import com.android.settings.TestConfig;
42 import com.android.settings.testutils.SettingsRobolectricTestRunner;
43 import com.android.settings.testutils.shadow.ShadowEventLogWriter;
45 import org.junit.Before;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 import org.mockito.Mock;
49 import org.mockito.MockitoAnnotations;
50 import org.robolectric.annotation.Config;
51 import org.robolectric.shadows.ShadowAlertDialog;
52 import org.robolectric.shadows.ShadowApplication;
53 import org.robolectric.util.FragmentTestUtil;
55 @RunWith(SettingsRobolectricTestRunner.class)
56 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
57 shadows=ShadowEventLogWriter.class)
58 public class BluetoothPairingDialogTest {
60 private static final String FILLER = "text that goes in a view";
61 private static final String FAKE_DEVICE_NAME = "Fake Bluetooth Device";
64 private BluetoothPairingController controller;
67 private BluetoothPairingDialog dialogActivity;
71 MockitoAnnotations.initMocks(this);
72 doNothing().when(dialogActivity).dismiss();
76 public void dialogUpdatesControllerWithUserInput() {
77 // set the correct dialog type
78 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
80 // we don't care about these for this test
81 when(controller.getDeviceVariantMessageId())
82 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
83 when(controller.getDeviceVariantMessageHintId())
84 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
87 BluetoothPairingDialogFragment frag = makeFragment();
89 // test that controller is updated on text change
90 frag.afterTextChanged(new SpannableStringBuilder(FILLER));
91 verify(controller, times(1)).updateUserInput(any());
95 public void dialogEnablesSubmitButtonOnValidationFromController() {
96 // set the correct dialog type
97 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
99 // we don't care about these for this test
100 when(controller.getDeviceVariantMessageId())
101 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
102 when(controller.getDeviceVariantMessageHintId())
103 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
105 // force the controller to say that any passkey is valid
106 when(controller.isPasskeyValid(any())).thenReturn(true);
109 BluetoothPairingDialogFragment frag = makeFragment();
111 // test that the positive button is enabled when passkey is valid
112 frag.afterTextChanged(new SpannableStringBuilder(FILLER));
113 View button = frag.getmDialog().getButton(AlertDialog.BUTTON_POSITIVE);
114 assertThat(button).isNotNull();
115 assertThat(button.getVisibility()).isEqualTo(View.VISIBLE);
119 public void dialogDoesNotAskForPairCodeOnConsentVariant() {
120 // set the dialog variant to confirmation/consent
121 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
123 // build the fragment
124 BluetoothPairingDialogFragment frag = makeFragment();
126 // check that the input field used by the entry dialog fragment does not exist
127 View view = frag.getmDialog().findViewById(R.id.text);
128 assertThat(view).isNull();
132 public void dialogAsksForPairCodeOnUserEntryVariant() {
133 // set the dialog variant to user entry
134 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
136 // we don't care about these for this test
137 when(controller.getDeviceVariantMessageId())
138 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
139 when(controller.getDeviceVariantMessageHintId())
140 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
142 Context context = spy(ShadowApplication.getInstance().getApplicationContext());
143 InputMethodManager imm = mock(InputMethodManager.class);
144 doReturn(imm).when(context).getSystemService(Context.INPUT_METHOD_SERVICE);
146 // build the fragment
147 BluetoothPairingDialogFragment frag = spy(new BluetoothPairingDialogFragment());
148 when(frag.getContext()).thenReturn(context);
150 AlertDialog alertDialog = frag.getmDialog();
152 // check that the pin/passkey input field is visible to the user
153 View view = alertDialog.findViewById(R.id.text);
154 assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
156 // check that showSoftInput was called to make input method appear when the dialog was shown
157 assertThat(view.isFocused()).isTrue();
158 assertThat(imm.isActive());
159 verify(imm).showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
163 public void dialogDisplaysPairCodeOnDisplayPasskeyVariant() {
164 // set the dialog variant to display passkey
165 when(controller.getDialogType())
166 .thenReturn(BluetoothPairingController.DISPLAY_PASSKEY_DIALOG);
168 // ensure that the controller returns good values to indicate a passkey needs to be shown
169 when(controller.isDisplayPairingKeyVariant()).thenReturn(true);
170 when(controller.hasPairingContent()).thenReturn(true);
171 when(controller.getPairingContent()).thenReturn(FILLER);
173 // build the fragment
174 BluetoothPairingDialogFragment frag = makeFragment();
176 // get the relevant views
177 View messagePairing = frag.getmDialog().findViewById(R.id.pairing_code_message);
178 TextView pairingViewContent =
179 (TextView) frag.getmDialog().findViewById(R.id.pairing_subhead);
180 View pairingViewCaption = frag.getmDialog().findViewById(R.id.pairing_caption);
182 // check that the relevant views are visible and that the passkey is shown
183 assertThat(messagePairing.getVisibility()).isEqualTo(View.VISIBLE);
184 assertThat(pairingViewCaption.getVisibility()).isEqualTo(View.VISIBLE);
185 assertThat(pairingViewContent.getVisibility()).isEqualTo(View.VISIBLE);
186 assertThat(TextUtils.equals(FILLER, pairingViewContent.getText())).isTrue();
189 @Test(expected = IllegalStateException.class)
190 public void dialogThrowsExceptionIfNoControllerSet() {
191 // instantiate a fragment
192 BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
194 // this should throw an error
195 FragmentTestUtil.startFragment(frag);
196 fail("Starting the fragment with no controller set should have thrown an exception.");
200 public void dialogCallsHookOnPositiveButtonPress() {
201 // set the dialog variant to confirmation/consent
202 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
204 // we don't care what this does, just that it is called
205 doNothing().when(controller).onDialogPositiveClick(any());
207 // build the fragment
208 BluetoothPairingDialogFragment frag = makeFragment();
210 // click the button and verify that the controller hook was called
211 frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_POSITIVE);
212 verify(controller, times(1)).onDialogPositiveClick(any());
216 public void dialogCallsHookOnNegativeButtonPress() {
217 // set the dialog variant to confirmation/consent
218 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
220 // we don't care what this does, just that it is called
221 doNothing().when(controller).onDialogNegativeClick(any());
223 // build the fragment
224 BluetoothPairingDialogFragment frag = makeFragment();
226 // click the button and verify that the controller hook was called
227 frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_NEGATIVE);
228 verify(controller, times(1)).onDialogNegativeClick(any());
231 @Test(expected = IllegalStateException.class)
232 public void dialogDoesNotAllowSwappingController() {
233 // instantiate a fragment
234 BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
235 frag.setPairingController(controller);
237 // this should throw an error
238 frag.setPairingController(controller);
239 fail("Setting the controller multiple times should throw an exception.");
242 @Test(expected = IllegalStateException.class)
243 public void dialogDoesNotAllowSwappingActivity() {
244 // instantiate a fragment
245 BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
246 frag.setPairingDialogActivity(dialogActivity);
248 // this should throw an error
249 frag.setPairingDialogActivity(dialogActivity);
250 fail("Setting the dialog activity multiple times should throw an exception.");
254 public void dialogPositiveButtonDisabledWhenUserInputInvalid() {
255 // set the correct dialog type
256 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
258 // we don't care about these for this test
259 when(controller.getDeviceVariantMessageId())
260 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
261 when(controller.getDeviceVariantMessageHintId())
262 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
264 // force the controller to say that any passkey is valid
265 when(controller.isPasskeyValid(any())).thenReturn(false);
268 BluetoothPairingDialogFragment frag = makeFragment();
270 // test that the positive button is enabled when passkey is valid
271 frag.afterTextChanged(new SpannableStringBuilder(FILLER));
272 View button = frag.getmDialog().getButton(AlertDialog.BUTTON_POSITIVE);
273 assertThat(button).isNotNull();
274 assertThat(button.isEnabled()).isFalse();
278 public void dialogShowsContactSharingCheckboxWhenBluetoothProfileNotReady() {
279 // set the dialog variant to confirmation/consent
280 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
282 // set a fake device name and pretend the profile has not been set up for it
283 when(controller.getDeviceName()).thenReturn(FAKE_DEVICE_NAME);
284 when(controller.isProfileReady()).thenReturn(false);
286 // build the fragment
287 BluetoothPairingDialogFragment frag = makeFragment();
289 // verify that the checkbox is visible and that the device name is correct
290 CheckBox sharingCheckbox = (CheckBox) frag.getmDialog()
291 .findViewById(R.id.phonebook_sharing_message_confirm_pin);
292 assertThat(sharingCheckbox.getVisibility()).isEqualTo(View.VISIBLE);
293 assertThat(sharingCheckbox.getText().toString().contains(FAKE_DEVICE_NAME)).isTrue();
297 public void dialogHidesContactSharingCheckboxWhenBluetoothProfileIsReady() {
298 // set the dialog variant to confirmation/consent
299 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
301 // set a fake device name and pretend the profile has been set up for it
302 when(controller.getDeviceName()).thenReturn(FAKE_DEVICE_NAME);
303 when(controller.isProfileReady()).thenReturn(true);
305 // build the fragment
306 BluetoothPairingDialogFragment frag = makeFragment();
308 // verify that the checkbox is gone
309 CheckBox sharingCheckbox = (CheckBox) frag.getmDialog()
310 .findViewById(R.id.phonebook_sharing_message_confirm_pin);
311 assertThat(sharingCheckbox.getVisibility()).isEqualTo(View.GONE);
315 public void dialogShowsMessageOnPinEntryView() {
316 // set the correct dialog type
317 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
319 // Set the message id to something specific to verify later
320 when(controller.getDeviceVariantMessageId()).thenReturn(R.string.cancel);
321 when(controller.getDeviceVariantMessageHintId())
322 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
324 // build the fragment
325 BluetoothPairingDialogFragment frag = makeFragment();
327 // verify message is what we expect it to be and is visible
328 TextView message = (TextView) frag.getmDialog().findViewById(R.id.message_below_pin);
329 assertThat(message.getVisibility()).isEqualTo(View.VISIBLE);
330 assertThat(TextUtils.equals(frag.getString(R.string.cancel), message.getText())).isTrue();
334 public void dialogShowsMessageHintOnPinEntryView() {
335 // set the correct dialog type
336 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
338 // Set the message id hint to something specific to verify later
339 when(controller.getDeviceVariantMessageHintId()).thenReturn(R.string.cancel);
340 when(controller.getDeviceVariantMessageId())
341 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
343 // build the fragment
344 BluetoothPairingDialogFragment frag = makeFragment();
346 // verify message is what we expect it to be and is visible
347 TextView hint = (TextView) frag.getmDialog().findViewById(R.id.pin_values_hint);
348 assertThat(hint.getVisibility()).isEqualTo(View.VISIBLE);
349 assertThat(TextUtils.equals(frag.getString(R.string.cancel), hint.getText())).isTrue();
353 public void dialogHidesMessageAndHintWhenNotProvidedOnPinEntryView() {
354 // set the correct dialog type
355 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
357 // Set the id's to what is returned when it is not provided
358 when(controller.getDeviceVariantMessageHintId())
359 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
360 when(controller.getDeviceVariantMessageId())
361 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
363 // build the fragment
364 BluetoothPairingDialogFragment frag = makeFragment();
366 // verify message is what we expect it to be and is visible
367 TextView hint = (TextView) frag.getmDialog().findViewById(R.id.pin_values_hint);
368 assertThat(hint.getVisibility()).isEqualTo(View.GONE);
369 TextView message = (TextView) frag.getmDialog().findViewById(R.id.message_below_pin);
370 assertThat(message.getVisibility()).isEqualTo(View.GONE);
374 public void pairingStringIsFormattedCorrectly() {
375 final String device = "test_device";
376 final Context context = ShadowApplication.getInstance().getApplicationContext();
377 assertThat(context.getString(R.string.bluetooth_pb_acceptance_dialog_text, device, device))
382 public void pairingDialogDismissedOnPositiveClick() {
383 // set the dialog variant to confirmation/consent
384 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
386 // we don't care what this does, just that it is called
387 doNothing().when(controller).onDialogPositiveClick(any());
389 // build the fragment
390 BluetoothPairingDialogFragment frag = makeFragment();
392 // click the button and verify that the controller hook was called
393 frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_POSITIVE);
395 verify(controller, times(1)).onDialogPositiveClick(any());
396 verify(dialogActivity, times(1)).dismiss();
400 public void pairingDialogDismissedOnNegativeClick() {
401 // set the dialog variant to confirmation/consent
402 when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
404 // we don't care what this does, just that it is called
405 doNothing().when(controller).onDialogNegativeClick(any());
407 // build the fragment
408 BluetoothPairingDialogFragment frag = makeFragment();
410 // click the button and verify that the controller hook was called
411 frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_NEGATIVE);
413 verify(controller, times(1)).onDialogNegativeClick(any());
414 verify(dialogActivity, times(1)).dismiss();
418 public void rotateDialog_nullPinText_okButtonEnabled() {
419 userEntryDialogExistingTextTest(null);
423 public void rotateDialog_emptyPinText_okButtonEnabled() {
424 userEntryDialogExistingTextTest("");
428 public void rotateDialog_nonEmptyPinText_okButtonEnabled() {
429 userEntryDialogExistingTextTest("test");
432 // Runs a test simulating the user entry dialog type in a situation like device rotation, where
433 // the dialog fragment gets created and we already have some existing text entered into the
435 private void userEntryDialogExistingTextTest(CharSequence existingText) {
436 when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
437 when(controller.getDeviceVariantMessageHintId())
438 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
439 when(controller.getDeviceVariantMessageId())
440 .thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
442 BluetoothPairingDialogFragment fragment = spy(new BluetoothPairingDialogFragment());
443 when(fragment.getPairingViewText()).thenReturn(existingText);
444 setupFragment(fragment);
445 AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
446 boolean expected = !TextUtils.isEmpty(existingText);
447 assertThat(dialog.getButton(Dialog.BUTTON_POSITIVE).isEnabled()).isEqualTo(expected);
450 private void setupFragment(BluetoothPairingDialogFragment frag) {
451 assertThat(frag.isPairingControllerSet()).isFalse();
452 frag.setPairingController(controller);
453 assertThat(frag.isPairingDialogActivitySet()).isFalse();
454 frag.setPairingDialogActivity(dialogActivity);
455 FragmentTestUtil.startFragment(frag);
456 assertThat(frag.getmDialog()).isNotNull();
457 assertThat(frag.isPairingControllerSet()).isTrue();
458 assertThat(frag.isPairingDialogActivitySet()).isTrue();
461 private BluetoothPairingDialogFragment makeFragment() {
462 BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();