Having consistent import order will reduce chance of merge conflict between internal and external master Test: rebuild Change-Id: I0b1a170967ddcce7f388603fd521f6ed1eeba30b
465 lines
20 KiB
Java
465 lines
20 KiB
Java
/*
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package com.android.settings.bluetooth;
|
|
|
|
import static com.google.common.truth.Truth.assertThat;
|
|
|
|
import static org.junit.Assert.fail;
|
|
import static org.mockito.Matchers.any;
|
|
import static org.mockito.Mockito.doNothing;
|
|
import static org.mockito.Mockito.doReturn;
|
|
import static org.mockito.Mockito.mock;
|
|
import static org.mockito.Mockito.spy;
|
|
import static org.mockito.Mockito.times;
|
|
import static org.mockito.Mockito.verify;
|
|
import static org.mockito.Mockito.when;
|
|
|
|
import android.app.Dialog;
|
|
import android.content.Context;
|
|
import android.text.SpannableStringBuilder;
|
|
import android.text.TextUtils;
|
|
import android.view.View;
|
|
import android.view.inputmethod.InputMethodManager;
|
|
import android.widget.CheckBox;
|
|
import android.widget.TextView;
|
|
|
|
import androidx.appcompat.app.AlertDialog;
|
|
|
|
import com.android.settings.R;
|
|
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
|
import com.android.settings.testutils.shadow.SettingsShadowResourcesImpl;
|
|
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
|
|
import com.android.settingslib.testutils.FragmentTestUtils;
|
|
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
import org.mockito.Mock;
|
|
import org.mockito.MockitoAnnotations;
|
|
import org.robolectric.RuntimeEnvironment;
|
|
import org.robolectric.annotation.Config;
|
|
|
|
@RunWith(SettingsRobolectricTestRunner.class)
|
|
@Config(shadows = {ShadowAlertDialogCompat.class, SettingsShadowResourcesImpl.class})
|
|
public class BluetoothPairingDialogTest {
|
|
|
|
private static final String FILLER = "text that goes in a view";
|
|
private static final String FAKE_DEVICE_NAME = "Fake Bluetooth Device";
|
|
|
|
@Mock
|
|
private BluetoothPairingController controller;
|
|
@Mock
|
|
private BluetoothPairingDialog dialogActivity;
|
|
|
|
@Before
|
|
public void setUp() {
|
|
MockitoAnnotations.initMocks(this);
|
|
doNothing().when(dialogActivity).dismiss();
|
|
}
|
|
|
|
@Test
|
|
public void dialogUpdatesControllerWithUserInput() {
|
|
// set the correct dialog type
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
|
|
|
|
// we don't care about these for this test
|
|
when(controller.getDeviceVariantMessageId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
when(controller.getDeviceVariantMessageHintId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
|
|
// build fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// test that controller is updated on text change
|
|
frag.afterTextChanged(new SpannableStringBuilder(FILLER));
|
|
verify(controller, times(1)).updateUserInput(any());
|
|
}
|
|
|
|
@Test
|
|
public void dialogEnablesSubmitButtonOnValidationFromController() {
|
|
// set the correct dialog type
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
|
|
|
|
// we don't care about these for this test
|
|
when(controller.getDeviceVariantMessageId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
when(controller.getDeviceVariantMessageHintId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
|
|
// force the controller to say that any passkey is valid
|
|
when(controller.isPasskeyValid(any())).thenReturn(true);
|
|
|
|
// build fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// test that the positive button is enabled when passkey is valid
|
|
frag.afterTextChanged(new SpannableStringBuilder(FILLER));
|
|
View button = frag.getmDialog().getButton(AlertDialog.BUTTON_POSITIVE);
|
|
assertThat(button).isNotNull();
|
|
assertThat(button.getVisibility()).isEqualTo(View.VISIBLE);
|
|
}
|
|
|
|
@Test
|
|
public void dialogDoesNotAskForPairCodeOnConsentVariant() {
|
|
// set the dialog variant to confirmation/consent
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
|
|
|
|
// build the fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// check that the input field used by the entry dialog fragment does not exist
|
|
View view = frag.getmDialog().findViewById(R.id.text);
|
|
assertThat(view).isNull();
|
|
}
|
|
|
|
@Test
|
|
public void dialogAsksForPairCodeOnUserEntryVariant() {
|
|
// set the dialog variant to user entry
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
|
|
|
|
// we don't care about these for this test
|
|
when(controller.getDeviceVariantMessageId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
when(controller.getDeviceVariantMessageHintId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
|
|
Context context = spy(RuntimeEnvironment.application);
|
|
InputMethodManager imm = mock(InputMethodManager.class);
|
|
doReturn(imm).when(context).getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
|
|
// build the fragment
|
|
BluetoothPairingDialogFragment frag = spy(new BluetoothPairingDialogFragment());
|
|
when(frag.getContext()).thenReturn(context);
|
|
setupFragment(frag);
|
|
AlertDialog alertDialog = frag.getmDialog();
|
|
|
|
// check that the pin/passkey input field is visible to the user
|
|
View view = alertDialog.findViewById(R.id.text);
|
|
assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
|
|
|
|
// check that showSoftInput was called to make input method appear when the dialog was shown
|
|
assertThat(view.isFocused()).isTrue();
|
|
// TODO(b/73892004): Figure out why this is failing.
|
|
// assertThat(imm.isActive()).isTrue();
|
|
verify(imm).showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
|
|
}
|
|
|
|
@Test
|
|
public void dialogDisplaysPairCodeOnDisplayPasskeyVariant() {
|
|
// set the dialog variant to display passkey
|
|
when(controller.getDialogType())
|
|
.thenReturn(BluetoothPairingController.DISPLAY_PASSKEY_DIALOG);
|
|
|
|
// ensure that the controller returns good values to indicate a passkey needs to be shown
|
|
when(controller.isDisplayPairingKeyVariant()).thenReturn(true);
|
|
when(controller.hasPairingContent()).thenReturn(true);
|
|
when(controller.getPairingContent()).thenReturn(FILLER);
|
|
|
|
// build the fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// get the relevant views
|
|
View messagePairing = frag.getmDialog().findViewById(R.id.pairing_code_message);
|
|
TextView pairingViewContent = frag.getmDialog().findViewById(R.id.pairing_subhead);
|
|
View pairingViewCaption = frag.getmDialog().findViewById(R.id.pairing_caption);
|
|
|
|
// check that the relevant views are visible and that the passkey is shown
|
|
assertThat(messagePairing.getVisibility()).isEqualTo(View.VISIBLE);
|
|
assertThat(pairingViewCaption.getVisibility()).isEqualTo(View.VISIBLE);
|
|
assertThat(pairingViewContent.getVisibility()).isEqualTo(View.VISIBLE);
|
|
assertThat(TextUtils.equals(FILLER, pairingViewContent.getText())).isTrue();
|
|
}
|
|
|
|
@Test(expected = IllegalStateException.class)
|
|
public void dialogThrowsExceptionIfNoControllerSet() {
|
|
// instantiate a fragment
|
|
BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
|
|
|
|
// this should throw an error
|
|
FragmentTestUtils.startFragment(frag);
|
|
fail("Starting the fragment with no controller set should have thrown an exception.");
|
|
}
|
|
|
|
@Test
|
|
public void dialogCallsHookOnPositiveButtonPress() {
|
|
// set the dialog variant to confirmation/consent
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
|
|
|
|
// we don't care what this does, just that it is called
|
|
doNothing().when(controller).onDialogPositiveClick(any());
|
|
|
|
// build the fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// click the button and verify that the controller hook was called
|
|
frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_POSITIVE);
|
|
verify(controller, times(1)).onDialogPositiveClick(any());
|
|
}
|
|
|
|
@Test
|
|
public void dialogCallsHookOnNegativeButtonPress() {
|
|
// set the dialog variant to confirmation/consent
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
|
|
|
|
// we don't care what this does, just that it is called
|
|
doNothing().when(controller).onDialogNegativeClick(any());
|
|
|
|
// build the fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// click the button and verify that the controller hook was called
|
|
frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_NEGATIVE);
|
|
verify(controller, times(1)).onDialogNegativeClick(any());
|
|
}
|
|
|
|
@Test(expected = IllegalStateException.class)
|
|
public void dialogDoesNotAllowSwappingController() {
|
|
// instantiate a fragment
|
|
BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
|
|
frag.setPairingController(controller);
|
|
|
|
// this should throw an error
|
|
frag.setPairingController(controller);
|
|
fail("Setting the controller multiple times should throw an exception.");
|
|
}
|
|
|
|
@Test(expected = IllegalStateException.class)
|
|
public void dialogDoesNotAllowSwappingActivity() {
|
|
// instantiate a fragment
|
|
BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
|
|
frag.setPairingDialogActivity(dialogActivity);
|
|
|
|
// this should throw an error
|
|
frag.setPairingDialogActivity(dialogActivity);
|
|
fail("Setting the dialog activity multiple times should throw an exception.");
|
|
}
|
|
|
|
@Test
|
|
public void dialogPositiveButtonDisabledWhenUserInputInvalid() {
|
|
// set the correct dialog type
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
|
|
|
|
// we don't care about these for this test
|
|
when(controller.getDeviceVariantMessageId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
when(controller.getDeviceVariantMessageHintId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
|
|
// force the controller to say that any passkey is valid
|
|
when(controller.isPasskeyValid(any())).thenReturn(false);
|
|
|
|
// build fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// test that the positive button is enabled when passkey is valid
|
|
frag.afterTextChanged(new SpannableStringBuilder(FILLER));
|
|
View button = frag.getmDialog().getButton(AlertDialog.BUTTON_POSITIVE);
|
|
assertThat(button).isNotNull();
|
|
assertThat(button.isEnabled()).isFalse();
|
|
}
|
|
|
|
@Test
|
|
public void dialogShowsContactSharingCheckboxWhenBluetoothProfileNotReady() {
|
|
// set the dialog variant to confirmation/consent
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
|
|
|
|
// set a fake device name and pretend the profile has not been set up for it
|
|
when(controller.getDeviceName()).thenReturn(FAKE_DEVICE_NAME);
|
|
when(controller.isProfileReady()).thenReturn(false);
|
|
|
|
// build the fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// verify that the checkbox is visible and that the device name is correct
|
|
CheckBox sharingCheckbox =
|
|
frag.getmDialog().findViewById(R.id.phonebook_sharing_message_confirm_pin);
|
|
assertThat(sharingCheckbox.getVisibility()).isEqualTo(View.VISIBLE);
|
|
}
|
|
|
|
@Test
|
|
public void dialogHidesContactSharingCheckboxWhenBluetoothProfileIsReady() {
|
|
// set the dialog variant to confirmation/consent
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
|
|
|
|
// set a fake device name and pretend the profile has been set up for it
|
|
when(controller.getDeviceName()).thenReturn(FAKE_DEVICE_NAME);
|
|
when(controller.isProfileReady()).thenReturn(true);
|
|
|
|
// build the fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// verify that the checkbox is gone
|
|
CheckBox sharingCheckbox =
|
|
frag.getmDialog().findViewById(R.id.phonebook_sharing_message_confirm_pin);
|
|
assertThat(sharingCheckbox.getVisibility()).isEqualTo(View.GONE);
|
|
}
|
|
|
|
@Test
|
|
public void dialogShowsMessageOnPinEntryView() {
|
|
// set the correct dialog type
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
|
|
|
|
// Set the message id to something specific to verify later
|
|
when(controller.getDeviceVariantMessageId()).thenReturn(R.string.cancel);
|
|
when(controller.getDeviceVariantMessageHintId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
|
|
// build the fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// verify message is what we expect it to be and is visible
|
|
TextView message = frag.getmDialog().findViewById(R.id.message_below_pin);
|
|
assertThat(message.getVisibility()).isEqualTo(View.VISIBLE);
|
|
assertThat(TextUtils.equals(frag.getString(R.string.cancel), message.getText())).isTrue();
|
|
}
|
|
|
|
@Test
|
|
public void dialogShowsMessageHintOnPinEntryView() {
|
|
// set the correct dialog type
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
|
|
|
|
// Set the message id hint to something specific to verify later
|
|
when(controller.getDeviceVariantMessageHintId()).thenReturn(R.string.cancel);
|
|
when(controller.getDeviceVariantMessageId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
|
|
// build the fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// verify message is what we expect it to be and is visible
|
|
TextView hint = frag.getmDialog().findViewById(R.id.pin_values_hint);
|
|
assertThat(hint.getVisibility()).isEqualTo(View.VISIBLE);
|
|
assertThat(TextUtils.equals(frag.getString(R.string.cancel), hint.getText())).isTrue();
|
|
}
|
|
|
|
@Test
|
|
public void dialogHidesMessageAndHintWhenNotProvidedOnPinEntryView() {
|
|
// set the correct dialog type
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
|
|
|
|
// Set the id's to what is returned when it is not provided
|
|
when(controller.getDeviceVariantMessageHintId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
when(controller.getDeviceVariantMessageId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
|
|
// build the fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// verify message is what we expect it to be and is visible
|
|
TextView hint = frag.getmDialog().findViewById(R.id.pin_values_hint);
|
|
assertThat(hint.getVisibility()).isEqualTo(View.GONE);
|
|
TextView message = frag.getmDialog().findViewById(R.id.message_below_pin);
|
|
assertThat(message.getVisibility()).isEqualTo(View.GONE);
|
|
}
|
|
|
|
@Test
|
|
public void pairingStringIsFormattedCorrectly() {
|
|
final String device = "test_device";
|
|
final Context context = RuntimeEnvironment.application;
|
|
assertThat(context.getString(R.string.bluetooth_pb_acceptance_dialog_text, device, device))
|
|
.contains(device);
|
|
}
|
|
|
|
@Test
|
|
public void pairingDialogDismissedOnPositiveClick() {
|
|
// set the dialog variant to confirmation/consent
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
|
|
|
|
// we don't care what this does, just that it is called
|
|
doNothing().when(controller).onDialogPositiveClick(any());
|
|
|
|
// build the fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// click the button and verify that the controller hook was called
|
|
frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_POSITIVE);
|
|
|
|
verify(controller, times(1)).onDialogPositiveClick(any());
|
|
verify(dialogActivity, times(1)).dismiss();
|
|
}
|
|
|
|
@Test
|
|
public void pairingDialogDismissedOnNegativeClick() {
|
|
// set the dialog variant to confirmation/consent
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
|
|
|
|
// we don't care what this does, just that it is called
|
|
doNothing().when(controller).onDialogNegativeClick(any());
|
|
|
|
// build the fragment
|
|
BluetoothPairingDialogFragment frag = makeFragment();
|
|
|
|
// click the button and verify that the controller hook was called
|
|
frag.onClick(frag.getmDialog(), AlertDialog.BUTTON_NEGATIVE);
|
|
|
|
verify(controller, times(1)).onDialogNegativeClick(any());
|
|
verify(dialogActivity, times(1)).dismiss();
|
|
}
|
|
|
|
@Test
|
|
public void rotateDialog_nullPinText_okButtonEnabled() {
|
|
userEntryDialogExistingTextTest(null);
|
|
}
|
|
|
|
@Test
|
|
public void rotateDialog_emptyPinText_okButtonEnabled() {
|
|
userEntryDialogExistingTextTest("");
|
|
}
|
|
|
|
@Test
|
|
public void rotateDialog_nonEmptyPinText_okButtonEnabled() {
|
|
userEntryDialogExistingTextTest("test");
|
|
}
|
|
|
|
// Runs a test simulating the user entry dialog type in a situation like device rotation, where
|
|
// the dialog fragment gets created and we already have some existing text entered into the
|
|
// pin field.
|
|
private void userEntryDialogExistingTextTest(CharSequence existingText) {
|
|
when(controller.getDialogType()).thenReturn(BluetoothPairingController.USER_ENTRY_DIALOG);
|
|
when(controller.getDeviceVariantMessageHintId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
when(controller.getDeviceVariantMessageId())
|
|
.thenReturn(BluetoothPairingController.INVALID_DIALOG_TYPE);
|
|
|
|
BluetoothPairingDialogFragment fragment = spy(new BluetoothPairingDialogFragment());
|
|
when(fragment.getPairingViewText()).thenReturn(existingText);
|
|
setupFragment(fragment);
|
|
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
|
assertThat(dialog).isNotNull();
|
|
boolean expected = !TextUtils.isEmpty(existingText);
|
|
assertThat(dialog.getButton(Dialog.BUTTON_POSITIVE).isEnabled()).isEqualTo(expected);
|
|
}
|
|
|
|
private void setupFragment(BluetoothPairingDialogFragment frag) {
|
|
assertThat(frag.isPairingControllerSet()).isFalse();
|
|
frag.setPairingController(controller);
|
|
assertThat(frag.isPairingDialogActivitySet()).isFalse();
|
|
frag.setPairingDialogActivity(dialogActivity);
|
|
FragmentTestUtils.startFragment(frag);
|
|
assertThat(frag.getmDialog()).isNotNull();
|
|
assertThat(frag.isPairingControllerSet()).isTrue();
|
|
assertThat(frag.isPairingDialogActivitySet()).isTrue();
|
|
}
|
|
|
|
private BluetoothPairingDialogFragment makeFragment() {
|
|
BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
|
|
setupFragment(frag);
|
|
return frag;
|
|
}
|
|
}
|