Bluetooth: Dismiss pairing dialog on user click
* Existing pairing dialog should be dismissed when user clicks on Yes/No * In a pairing session with multiple pairing dialogs, this is necessary as otherwise the second pairing dialog will not be shown * Modified unit test to test this behavior * Launch pairing dialog as UserHandle.CURRENT to avoid Context warnings Bug: 35833536 Test: make, unit test, pair with Bluetooth devices Change-Id: I1823b78d287134505f59eab7caca2329ecc3a36f
This commit is contained in:
@@ -24,16 +24,17 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
|
* BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
|
||||||
* for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
|
* for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
|
||||||
*/
|
*/
|
||||||
public final class BluetoothPairingDialog extends Activity {
|
public class BluetoothPairingDialog extends Activity {
|
||||||
public static final String FRAGMENT_TAG = "bluetooth.pairing.fragment";
|
public static final String FRAGMENT_TAG = "bluetooth.pairing.fragment";
|
||||||
|
|
||||||
private BluetoothPairingController mBluetoothPairingController;
|
private BluetoothPairingController mBluetoothPairingController;
|
||||||
private boolean mReceiverRegistered;
|
private boolean mReceiverRegistered = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dismiss the dialog if the bond state changes to bonded or none,
|
* Dismiss the dialog if the bond state changes to bonded or none,
|
||||||
@@ -62,23 +63,26 @@ public final class BluetoothPairingDialog extends Activity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
boolean fragmentFound = true;
|
|
||||||
|
|
||||||
BluetoothPairingDialogFragment bluetoothFragment =
|
|
||||||
(BluetoothPairingDialogFragment) getFragmentManager()
|
|
||||||
.findFragmentByTag(FRAGMENT_TAG);
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
mBluetoothPairingController = new BluetoothPairingController(intent, this);
|
mBluetoothPairingController = new BluetoothPairingController(intent, this);
|
||||||
|
// build the dialog fragment
|
||||||
// check if the fragment exists already
|
boolean fragmentFound = true;
|
||||||
|
// check if the fragment has been preloaded
|
||||||
|
BluetoothPairingDialogFragment bluetoothFragment =
|
||||||
|
(BluetoothPairingDialogFragment) getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
|
||||||
|
// dismiss the fragment if it is already used
|
||||||
|
if (bluetoothFragment != null && (bluetoothFragment.isPairingControllerSet()
|
||||||
|
|| bluetoothFragment.isPairingDialogActivitySet())) {
|
||||||
|
bluetoothFragment.dismiss();
|
||||||
|
bluetoothFragment = null;
|
||||||
|
}
|
||||||
|
// build a new fragment if it is null
|
||||||
if (bluetoothFragment == null) {
|
if (bluetoothFragment == null) {
|
||||||
fragmentFound = false;
|
fragmentFound = false;
|
||||||
bluetoothFragment = new BluetoothPairingDialogFragment();
|
bluetoothFragment = new BluetoothPairingDialogFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the controller
|
|
||||||
bluetoothFragment.setPairingController(mBluetoothPairingController);
|
bluetoothFragment.setPairingController(mBluetoothPairingController);
|
||||||
|
bluetoothFragment.setPairingDialogActivity(this);
|
||||||
// pass the fragment to the manager when it is created from scratch
|
// pass the fragment to the manager when it is created from scratch
|
||||||
if (!fragmentFound) {
|
if (!fragmentFound) {
|
||||||
bluetoothFragment.show(getFragmentManager(), FRAGMENT_TAG);
|
bluetoothFragment.show(getFragmentManager(), FRAGMENT_TAG);
|
||||||
@@ -101,8 +105,15 @@ public final class BluetoothPairingDialog extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dismiss() {
|
@VisibleForTesting
|
||||||
|
void dismiss() {
|
||||||
if (!isFinishing()) {
|
if (!isFinishing()) {
|
||||||
|
BluetoothPairingDialogFragment bluetoothFragment =
|
||||||
|
(BluetoothPairingDialogFragment) getFragmentManager()
|
||||||
|
.findFragmentByTag(FRAGMENT_TAG);
|
||||||
|
if (bluetoothFragment != null) {
|
||||||
|
bluetoothFragment.dismiss();
|
||||||
|
}
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -47,6 +47,7 @@ public class BluetoothPairingDialogFragment extends InstrumentedDialogFragment i
|
|||||||
private AlertDialog.Builder mBuilder;
|
private AlertDialog.Builder mBuilder;
|
||||||
private AlertDialog mDialog;
|
private AlertDialog mDialog;
|
||||||
private BluetoothPairingController mPairingController;
|
private BluetoothPairingController mPairingController;
|
||||||
|
private BluetoothPairingDialog mPairingDialogActivity;
|
||||||
private EditText mPairingView;
|
private EditText mPairingView;
|
||||||
/**
|
/**
|
||||||
* The interface we expect a listener to implement. Typically this should be done by
|
* The interface we expect a listener to implement. Typically this should be done by
|
||||||
@@ -61,10 +62,14 @@ public class BluetoothPairingDialogFragment extends InstrumentedDialogFragment i
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
if (mPairingController == null) {
|
if (!isPairingControllerSet()) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Must call setPairingController() before showing dialog");
|
"Must call setPairingController() before showing dialog");
|
||||||
}
|
}
|
||||||
|
if (!isPairingDialogActivitySet()) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Must call setPairingDialogActivity() before showing dialog");
|
||||||
|
}
|
||||||
mBuilder = new AlertDialog.Builder(getActivity());
|
mBuilder = new AlertDialog.Builder(getActivity());
|
||||||
mDialog = setupDialog();
|
mDialog = setupDialog();
|
||||||
mDialog.setCanceledOnTouchOutside(false);
|
mDialog.setCanceledOnTouchOutside(false);
|
||||||
@@ -97,6 +102,7 @@ public class BluetoothPairingDialogFragment extends InstrumentedDialogFragment i
|
|||||||
} else if (which == DialogInterface.BUTTON_NEGATIVE) {
|
} else if (which == DialogInterface.BUTTON_NEGATIVE) {
|
||||||
mPairingController.onDialogNegativeClick(this);
|
mPairingController.onDialogNegativeClick(this);
|
||||||
}
|
}
|
||||||
|
mPairingDialogActivity.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -119,14 +125,41 @@ public class BluetoothPairingDialogFragment extends InstrumentedDialogFragment i
|
|||||||
* controller may not be substituted once it is assigned. Forcibly switching a
|
* controller may not be substituted once it is assigned. Forcibly switching a
|
||||||
* controller for a new one will lead to undefined behavior.
|
* controller for a new one will lead to undefined behavior.
|
||||||
*/
|
*/
|
||||||
public void setPairingController(BluetoothPairingController pairingController) {
|
void setPairingController(BluetoothPairingController pairingController) {
|
||||||
if (mPairingController != null) {
|
if (isPairingControllerSet()) {
|
||||||
throw new IllegalStateException("The controller can only be set once. "
|
throw new IllegalStateException("The controller can only be set once. "
|
||||||
+ "Forcibly replacing it will lead to undefined behavior");
|
+ "Forcibly replacing it will lead to undefined behavior");
|
||||||
}
|
}
|
||||||
mPairingController = pairingController;
|
mPairingController = pairingController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether mPairingController is set
|
||||||
|
* @return True when mPairingController is set, False otherwise
|
||||||
|
*/
|
||||||
|
boolean isPairingControllerSet() {
|
||||||
|
return mPairingController != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the BluetoothPairingDialog activity that started this fragment
|
||||||
|
* @param pairingDialogActivity The pairing dialog activty that started this fragment
|
||||||
|
*/
|
||||||
|
void setPairingDialogActivity(BluetoothPairingDialog pairingDialogActivity) {
|
||||||
|
if (isPairingDialogActivitySet()) {
|
||||||
|
throw new IllegalStateException("The pairing dialog activity can only be set once");
|
||||||
|
}
|
||||||
|
mPairingDialogActivity = pairingDialogActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether mPairingDialogActivity is set
|
||||||
|
* @return True when mPairingDialogActivity is set, False otherwise
|
||||||
|
*/
|
||||||
|
boolean isPairingDialogActivitySet() {
|
||||||
|
return mPairingDialogActivity != null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the appropriate type of dialog and returns it.
|
* Creates the appropriate type of dialog and returns it.
|
||||||
*/
|
*/
|
||||||
|
@@ -52,7 +52,7 @@ public final class BluetoothPairingRequest extends BroadcastReceiver {
|
|||||||
if (powerManager.isInteractive() && shouldShowDialog) {
|
if (powerManager.isInteractive() && shouldShowDialog) {
|
||||||
// Since the screen is on and the BT-related activity is in the foreground,
|
// Since the screen is on and the BT-related activity is in the foreground,
|
||||||
// just open the dialog
|
// just open the dialog
|
||||||
context.startActivity(pairingIntent);
|
context.startActivityAsUser(pairingIntent, UserHandle.CURRENT);
|
||||||
} else {
|
} else {
|
||||||
// Put up a notification that leads to the dialog
|
// Put up a notification that leads to the dialog
|
||||||
intent.setClass(context, BluetoothPairingService.class);
|
intent.setClass(context, BluetoothPairingService.class);
|
||||||
|
@@ -54,9 +54,13 @@ public class BluetoothPairingDialogTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private BluetoothPairingController controller;
|
private BluetoothPairingController controller;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private BluetoothPairingDialog dialogActivity;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
doNothing().when(dialogActivity).dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -214,6 +218,17 @@ public class BluetoothPairingDialogTest {
|
|||||||
fail("Setting the controller multiple times should throw an exception.");
|
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
|
@Test
|
||||||
public void dialogPositiveButtonDisabledWhenUserInputInvalid() {
|
public void dialogPositiveButtonDisabledWhenUserInputInvalid() {
|
||||||
// set the correct dialog type
|
// set the correct dialog type
|
||||||
@@ -342,11 +357,52 @@ public class BluetoothPairingDialogTest {
|
|||||||
.contains(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();
|
||||||
|
}
|
||||||
|
|
||||||
private BluetoothPairingDialogFragment makeFragment() {
|
private BluetoothPairingDialogFragment makeFragment() {
|
||||||
BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
|
BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment();
|
||||||
|
assertThat(frag.isPairingControllerSet()).isFalse();
|
||||||
frag.setPairingController(controller);
|
frag.setPairingController(controller);
|
||||||
|
assertThat(frag.isPairingDialogActivitySet()).isFalse();
|
||||||
|
frag.setPairingDialogActivity(dialogActivity);
|
||||||
FragmentTestUtil.startFragment(frag);
|
FragmentTestUtil.startFragment(frag);
|
||||||
assertThat(frag.getmDialog()).isNotNull();
|
assertThat(frag.getmDialog()).isNotNull();
|
||||||
|
assertThat(frag.isPairingControllerSet()).isTrue();
|
||||||
|
assertThat(frag.isPairingDialogActivitySet()).isTrue();
|
||||||
return frag;
|
return frag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user