diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java index ed63fcbb4a2..97382c3c480 100644 --- a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java @@ -24,16 +24,17 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; +import android.support.annotation.VisibleForTesting; /** * 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. */ -public final class BluetoothPairingDialog extends Activity { +public class BluetoothPairingDialog extends Activity { public static final String FRAGMENT_TAG = "bluetooth.pairing.fragment"; private BluetoothPairingController mBluetoothPairingController; - private boolean mReceiverRegistered; + private boolean mReceiverRegistered = false; /** * Dismiss the dialog if the bond state changes to bonded or none, @@ -62,23 +63,26 @@ public final class BluetoothPairingDialog extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - boolean fragmentFound = true; - - BluetoothPairingDialogFragment bluetoothFragment = - (BluetoothPairingDialogFragment) getFragmentManager() - .findFragmentByTag(FRAGMENT_TAG); Intent intent = getIntent(); mBluetoothPairingController = new BluetoothPairingController(intent, this); - - // check if the fragment exists already + // build the dialog fragment + 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) { fragmentFound = false; bluetoothFragment = new BluetoothPairingDialogFragment(); } - - // set the controller bluetoothFragment.setPairingController(mBluetoothPairingController); - + bluetoothFragment.setPairingDialogActivity(this); // pass the fragment to the manager when it is created from scratch if (!fragmentFound) { bluetoothFragment.show(getFragmentManager(), FRAGMENT_TAG); @@ -101,8 +105,15 @@ public final class BluetoothPairingDialog extends Activity { } } - private void dismiss() { + @VisibleForTesting + void dismiss() { if (!isFinishing()) { + BluetoothPairingDialogFragment bluetoothFragment = + (BluetoothPairingDialogFragment) getFragmentManager() + .findFragmentByTag(FRAGMENT_TAG); + if (bluetoothFragment != null) { + bluetoothFragment.dismiss(); + } finish(); } } diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java b/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java index 3d786fbfa0b..7b8fc6c8387 100644 --- a/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java @@ -47,6 +47,7 @@ public class BluetoothPairingDialogFragment extends InstrumentedDialogFragment i private AlertDialog.Builder mBuilder; private AlertDialog mDialog; private BluetoothPairingController mPairingController; + private BluetoothPairingDialog mPairingDialogActivity; private EditText mPairingView; /** * The interface we expect a listener to implement. Typically this should be done by @@ -61,9 +62,13 @@ public class BluetoothPairingDialogFragment extends InstrumentedDialogFragment i @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - if (mPairingController == null) { + if (!isPairingControllerSet()) { 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()); mDialog = setupDialog(); @@ -97,6 +102,7 @@ public class BluetoothPairingDialogFragment extends InstrumentedDialogFragment i } else if (which == DialogInterface.BUTTON_NEGATIVE) { mPairingController.onDialogNegativeClick(this); } + mPairingDialogActivity.dismiss(); } @Override @@ -119,14 +125,41 @@ public class BluetoothPairingDialogFragment extends InstrumentedDialogFragment i * controller may not be substituted once it is assigned. Forcibly switching a * controller for a new one will lead to undefined behavior. */ - public void setPairingController(BluetoothPairingController pairingController) { - if (mPairingController != null) { + void setPairingController(BluetoothPairingController pairingController) { + if (isPairingControllerSet()) { throw new IllegalStateException("The controller can only be set once. " + "Forcibly replacing it will lead to undefined behavior"); } 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. */ diff --git a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java index 96aace9eac5..4d02fd50c31 100644 --- a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java @@ -52,7 +52,7 @@ public final class BluetoothPairingRequest extends BroadcastReceiver { if (powerManager.isInteractive() && shouldShowDialog) { // Since the screen is on and the BT-related activity is in the foreground, // just open the dialog - context.startActivity(pairingIntent); + context.startActivityAsUser(pairingIntent, UserHandle.CURRENT); } else { // Put up a notification that leads to the dialog intent.setClass(context, BluetoothPairingService.class); diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDialogTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDialogTest.java index 90f21063f13..73f6b848ac5 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDialogTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDialogTest.java @@ -54,9 +54,13 @@ public class BluetoothPairingDialogTest { @Mock private BluetoothPairingController controller; + @Mock + private BluetoothPairingDialog dialogActivity; + @Before public void setUp() { MockitoAnnotations.initMocks(this); + doNothing().when(dialogActivity).dismiss(); } @Test @@ -214,6 +218,17 @@ public class BluetoothPairingDialogTest { 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 @@ -342,11 +357,52 @@ public class BluetoothPairingDialogTest { .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() { BluetoothPairingDialogFragment frag = new BluetoothPairingDialogFragment(); + assertThat(frag.isPairingControllerSet()).isFalse(); frag.setPairingController(controller); + assertThat(frag.isPairingDialogActivitySet()).isFalse(); + frag.setPairingDialogActivity(dialogActivity); FragmentTestUtil.startFragment(frag); assertThat(frag.getmDialog()).isNotNull(); + assertThat(frag.isPairingControllerSet()).isTrue(); + assertThat(frag.isPairingDialogActivitySet()).isTrue(); return frag; } }