Don't show multiple wipe warning dialogs

Stacking them on top of each other like that does not count as defence
in depth and we should not do it.

Bug: 32934848
Test: make RunSettingsRoboTests # added one specifically for this
Change-Id: I9490652c05a630e7c3f9164a9144fadfc0f41ea1
diff --git a/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
index cabc805..a5f9830 100644
--- a/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
+++ b/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
@@ -14,18 +14,21 @@
  * limitations under the License
  */
 
+// TODO (b/35202196): move this class out of the root of the package.
 package com.android.settings;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.graphics.Point;
@@ -289,12 +292,13 @@
                 // Last try
                 final String title = getActivity().getString(
                         R.string.lock_profile_wipe_warning_title);
-                final String message = getActivity().getString(getLastTryErrorMessage());
-                showDialog(title, message, android.R.string.ok, false /* dismiss */);
+                LastTryDialog.show(getFragmentManager(), title, getLastTryErrorMessage(),
+                        android.R.string.ok, false /* dismiss */);
             } else if (remainingAttempts <= 0) {
                 // Profile is wiped
-                final String message = getActivity().getString(R.string.lock_profile_wipe_content);
-                showDialog(null, message, R.string.lock_profile_wipe_dismiss, true /* dismiss */);
+                LastTryDialog.show(getFragmentManager(), null /* title */,
+                        R.string.lock_profile_wipe_content, R.string.lock_profile_wipe_dismiss,
+                        true /* dismiss */);
             }
             if (mErrorTextView != null) {
                 final String message = getActivity().getString(R.string.lock_profile_wipe_attempts,
@@ -328,19 +332,55 @@
         showError(getText(msg), timeout);
     }
 
-    private void showDialog(String title, String message, int buttonString, final boolean dismiss) {
-        final AlertDialog dialog = new AlertDialog.Builder(getActivity())
-            .setTitle(title)
-            .setMessage(message)
-            .setPositiveButton(buttonString, new OnClickListener() {
-                @Override
-                public void onClick(DialogInterface dialog, int which) {
-                    if (dismiss) {
-                        getActivity().finish();
-                    }
-                }
-            })
-            .create();
-        dialog.show();
+    public static class LastTryDialog extends DialogFragment {
+        private static final String TAG = LastTryDialog.class.getSimpleName();
+
+        private static final String ARG_TITLE = "title";
+        private static final String ARG_MESSAGE = "message";
+        private static final String ARG_BUTTON = "button";
+        private static final String ARG_DISMISS = "dismiss";
+
+        static boolean show(FragmentManager from, String title, int message, int button,
+                boolean dismiss) {
+            LastTryDialog existent = (LastTryDialog) from.findFragmentByTag(TAG);
+            if (existent != null && !existent.isRemoving()) {
+                return false;
+            }
+            Bundle args = new Bundle();
+            args.putString(ARG_TITLE, title);
+            args.putInt(ARG_MESSAGE, message);
+            args.putInt(ARG_BUTTON, button);
+            args.putBoolean(ARG_DISMISS, dismiss);
+
+            DialogFragment dialog = new LastTryDialog();
+            dialog.setArguments(args);
+            dialog.show(from, TAG);
+            return true;
+        }
+
+        static void hide(FragmentManager from) {
+            LastTryDialog dialog = (LastTryDialog) from.findFragmentByTag(TAG);
+            if (dialog != null) {
+                dialog.dismissAllowingStateLoss();
+                from.executePendingTransactions();
+            }
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            return new AlertDialog.Builder(getActivity())
+                    .setTitle(getArguments().getString(ARG_TITLE))
+                    .setMessage(getArguments().getInt(ARG_MESSAGE))
+                    .setPositiveButton(getArguments().getInt(ARG_BUTTON), null)
+                    .create();
+        }
+
+        @Override
+        public void onDismiss(final DialogInterface dialog) {
+            super.onDismiss(dialog);
+            if (getActivity() != null && getArguments().getBoolean(ARG_DISMISS)) {
+                getActivity().finish();
+            }
+        }
     }
 }
diff --git a/tests/robotests/src/com/android/settings/ConfirmCredentialTest.java b/tests/robotests/src/com/android/settings/ConfirmCredentialTest.java
new file mode 100644
index 0000000..f771a1f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/ConfirmCredentialTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 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;
+
+import static com.android.settings.ConfirmDeviceCredentialBaseFragment.LastTryDialog;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.R;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(
+        manifest = TestConfig.MANIFEST_PATH,
+        sdk = TestConfig.SDK_VERSION,
+        shadows = {
+                SettingsShadowResources.class,
+                SettingsShadowResources.SettingsShadowTheme.class,
+        })
+public class ConfirmCredentialTest {
+    @Test
+    public void testLastTryDialogShownExactlyOnce() {
+        FragmentManager fm = Robolectric.buildActivity(Activity.class).get().getFragmentManager();
+
+        // Launch only one instance at a time.
+        assertThat(LastTryDialog.show(fm, "title", R.string.yes, R.string.ok, false)).isTrue();
+        assertThat(LastTryDialog.show(fm, "title", R.string.yes, R.string.ok, false)).isFalse();
+
+        // After cancelling, the dialog should be re-shown when asked for.
+        LastTryDialog.hide(fm);
+        assertThat(LastTryDialog.show(fm, "title", R.string.yes, R.string.ok, false)).isTrue();
+    }
+}