Merge "Bluetooth: Dismiss pairing dialog on user click" into oc-dev
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
index ed63fcb..97382c3 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
@@ -24,16 +24,17 @@
 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 @@
     @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 @@
         }
     }
 
-    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 3d786fb..7b8fc6c 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java
@@ -47,6 +47,7 @@
     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 @@
 
     @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 @@
         } else if (which == DialogInterface.BUTTON_NEGATIVE) {
             mPairingController.onDialogNegativeClick(this);
         }
+        mPairingDialogActivity.dismiss();
     }
 
     @Override
@@ -119,8 +125,8 @@
      * 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");
         }
@@ -128,6 +134,33 @@
     }
 
     /**
+     * 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.
      */
     private AlertDialog setupDialog() {
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
index 96aace9..4d02fd5 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
@@ -52,7 +52,7 @@
     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 90f2106..73f6b84 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 @@
     @Mock
     private BluetoothPairingController controller;
 
+    @Mock
+    private BluetoothPairingDialog dialogActivity;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        doNothing().when(dialogActivity).dismiss();
     }
 
     @Test
@@ -214,6 +218,17 @@
         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 @@
                 .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;
     }
 }