OSDN Git Service

Merge "Add fingerprint illustration image" into oc-mr1-dev
[android-x86/packages-apps-Settings.git] / tests / robotests / src / com / android / settings / bluetooth / BluetoothPairingDialogTest.java
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
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;
29
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;
39
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;
44
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;
54
55 @RunWith(SettingsRobolectricTestRunner.class)
56 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
57         shadows=ShadowEventLogWriter.class)
58 public class BluetoothPairingDialogTest {
59
60     private static final String FILLER = "text that goes in a view";
61     private static final String FAKE_DEVICE_NAME = "Fake Bluetooth Device";
62
63     @Mock
64     private BluetoothPairingController controller;
65
66     @Mock
67     private BluetoothPairingDialog dialogActivity;
68
69     @Before
70     public void setUp() {
71         MockitoAnnotations.initMocks(this);
72         doNothing().when(dialogActivity).dismiss();
73     }
74
75     @Test
76     public void dialogUpdatesControllerWithUserInput() {
77         // set the correct dialog type
78         when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
79
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);
85
86         // build fragment
87         BluetoothPairingDialogFragment frag = makeFragment();
88
89         // test that controller is updated on text change
90         frag.afterTextChanged(new SpannableStringBuilder(FILLER));
91         verify(controller, times(1)).updateUserInput(any());
92     }
93
94     @Test
95     public void dialogEnablesSubmitButtonOnValidationFromController() {
96         // set the correct dialog type
97         when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
98
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);
104
105         // force the controller to say that any passkey is valid
106         when(controller.isPasskeyValid(any())).thenReturn(true);
107
108         // build fragment
109         BluetoothPairingDialogFragment frag = makeFragment();
110
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);
116     }
117
118     @Test
119     public void dialogDoesNotAskForPairCodeOnConsentVariant() {
120         // set the dialog variant to confirmation/consent
121         when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
122
123         // build the fragment
124         BluetoothPairingDialogFragment frag = makeFragment();
125
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();
129     }
130
131     @Test
132     public void dialogAsksForPairCodeOnUserEntryVariant() {
133         // set the dialog variant to user entry
134         when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
135
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);
141
142         Context context = spy(ShadowApplication.getInstance().getApplicationContext());
143         InputMethodManager imm = mock(InputMethodManager.class);
144         doReturn(imm).when(context).getSystemService(Context.INPUT_METHOD_SERVICE);
145
146         // build the fragment
147         BluetoothPairingDialogFragment frag = spy(new BluetoothPairingDialogFragment());
148         when(frag.getContext()).thenReturn(context);
149         setupFragment(frag);
150         AlertDialog alertDialog = frag.getmDialog();
151
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);
155
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);
160     }
161
162     @Test
163     public void dialogDisplaysPairCodeOnDisplayPasskeyVariant() {
164         // set the dialog variant to display passkey
165         when(controller.getDialogType())
166                 .thenReturn(BluetoothPairingController.DISPLAY_PASSKEY_DIALOG);
167
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);
172
173         // build the fragment
174         BluetoothPairingDialogFragment frag = makeFragment();
175
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);
181
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();
187     }
188
189     @Test(expected = IllegalStateException.class)
190     public void dialogThrowsExceptionIfNoControllerSet() {
191         // instantiate a fragment
192         BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
193
194         // this should throw an error
195         FragmentTestUtil.startFragment(frag);
196         fail("Starting the fragment with no controller set should have thrown an exception.");
197     }
198
199     @Test
200     public void dialogCallsHookOnPositiveButtonPress() {
201         // set the dialog variant to confirmation/consent
202         when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
203
204         // we don't care what this does, just that it is called
205         doNothing().when(controller).onDialogPositiveClick(any());
206
207         // build the fragment
208         BluetoothPairingDialogFragment frag = makeFragment();
209
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());
213     }
214
215     @Test
216     public void dialogCallsHookOnNegativeButtonPress() {
217         // set the dialog variant to confirmation/consent
218         when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
219
220         // we don't care what this does, just that it is called
221         doNothing().when(controller).onDialogNegativeClick(any());
222
223         // build the fragment
224         BluetoothPairingDialogFragment frag = makeFragment();
225
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());
229     }
230
231     @Test(expected = IllegalStateException.class)
232     public void dialogDoesNotAllowSwappingController() {
233         // instantiate a fragment
234         BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
235         frag.setPairingController(controller);
236
237         // this should throw an error
238         frag.setPairingController(controller);
239         fail("Setting the controller multiple times should throw an exception.");
240     }
241
242     @Test(expected = IllegalStateException.class)
243     public void dialogDoesNotAllowSwappingActivity() {
244         // instantiate a fragment
245         BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
246         frag.setPairingDialogActivity(dialogActivity);
247
248         // this should throw an error
249         frag.setPairingDialogActivity(dialogActivity);
250         fail("Setting the dialog activity multiple times should throw an exception.");
251     }
252
253     @Test
254     public void dialogPositiveButtonDisabledWhenUserInputInvalid() {
255         // set the correct dialog type
256         when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
257
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);
263
264         // force the controller to say that any passkey is valid
265         when(controller.isPasskeyValid(any())).thenReturn(false);
266
267         // build fragment
268         BluetoothPairingDialogFragment frag = makeFragment();
269
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();
275     }
276
277     @Test
278     public void dialogShowsContactSharingCheckboxWhenBluetoothProfileNotReady() {
279         // set the dialog variant to confirmation/consent
280         when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
281
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);
285
286         // build the fragment
287         BluetoothPairingDialogFragment frag = makeFragment();
288
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();
294     }
295
296     @Test
297     public void dialogHidesContactSharingCheckboxWhenBluetoothProfileIsReady() {
298         // set the dialog variant to confirmation/consent
299         when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
300
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);
304
305         // build the fragment
306         BluetoothPairingDialogFragment frag = makeFragment();
307
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);
312     }
313
314     @Test
315     public void dialogShowsMessageOnPinEntryView() {
316         // set the correct dialog type
317         when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
318
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);
323
324         // build the fragment
325         BluetoothPairingDialogFragment frag = makeFragment();
326
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();
331     }
332
333     @Test
334     public void dialogShowsMessageHintOnPinEntryView() {
335         // set the correct dialog type
336         when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
337
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);
342
343         // build the fragment
344         BluetoothPairingDialogFragment frag = makeFragment();
345
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();
350     }
351
352     @Test
353     public void dialogHidesMessageAndHintWhenNotProvidedOnPinEntryView() {
354         // set the correct dialog type
355         when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
356
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);
362
363         // build the fragment
364         BluetoothPairingDialogFragment frag = makeFragment();
365
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);
371     }
372
373     @Test
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))
378                 .contains(device);
379     }
380
381     @Test
382     public void pairingDialogDismissedOnPositiveClick() {
383         // set the dialog variant to confirmation/consent
384         when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
385
386         // we don't care what this does, just that it is called
387         doNothing().when(controller).onDialogPositiveClick(any());
388
389         // build the fragment
390         BluetoothPairingDialogFragment frag = makeFragment();
391
392         // click the button and verify that the controller hook was called
393         frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_POSITIVE);
394
395         verify(controller, times(1)).onDialogPositiveClick(any());
396         verify(dialogActivity, times(1)).dismiss();
397     }
398
399     @Test
400     public void pairingDialogDismissedOnNegativeClick() {
401         // set the dialog variant to confirmation/consent
402         when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
403
404         // we don't care what this does, just that it is called
405         doNothing().when(controller).onDialogNegativeClick(any());
406
407         // build the fragment
408         BluetoothPairingDialogFragment frag = makeFragment();
409
410         // click the button and verify that the controller hook was called
411         frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_NEGATIVE);
412
413         verify(controller, times(1)).onDialogNegativeClick(any());
414         verify(dialogActivity, times(1)).dismiss();
415     }
416
417     @Test
418     public void rotateDialog_nullPinText_okButtonEnabled() {
419         userEntryDialogExistingTextTest(null);
420     }
421
422     @Test
423     public void rotateDialog_emptyPinText_okButtonEnabled() {
424         userEntryDialogExistingTextTest("");
425     }
426
427     @Test
428     public void rotateDialog_nonEmptyPinText_okButtonEnabled() {
429         userEntryDialogExistingTextTest("test");
430     }
431
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
434     // pin field.
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);
441
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);
448     }
449
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();
459     }
460
461     private BluetoothPairingDialogFragment makeFragment() {
462         BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
463         setupFragment(frag);
464         return frag;
465     }
466 }