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