Controls UI - Add PIN dialog

Support 'ControlAction.RESPONSE_CHALLENGE_PIN' response types. When
detected, prompt the user for the PIN, copy the action with the user's
response, and send it all over again.

Bug: 150849062
Test: visual

Change-Id: I5cf8391125a3dfe235895192b2663851f37eed11
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
new file mode 100644
index 0000000..2494fd1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 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.systemui.controls.ui
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.content.DialogInterface
+import android.service.controls.actions.BooleanAction
+import android.service.controls.actions.CommandAction
+import android.service.controls.actions.ControlAction
+import android.service.controls.actions.FloatAction
+import android.service.controls.actions.ModeAction
+import android.text.InputType
+import android.util.Log
+import android.view.WindowManager
+import android.widget.CheckBox
+import android.widget.EditText
+
+import com.android.systemui.R
+
+/**
+ * Creates all dialogs for challengeValues that can occur from a call to
+ * {@link ControlsProviderService#performControlAction}. The types of challenge
+ * responses are listed in {@link ControlAction.ResponseResult}.
+ */
+object ChallengeDialogs {
+
+    fun createPinDialog(cvh: ControlViewHolder): Dialog? {
+        val lastAction = cvh.lastAction
+        if (lastAction == null) {
+            Log.e(ControlsUiController.TAG,
+                "PIN Dialog attempted but no last action is set. Will not show")
+            return null
+        }
+        val builder = AlertDialog.Builder(
+            cvh.context,
+            android.R.style.Theme_DeviceDefault_Dialog_Alert
+        ).apply {
+            setTitle(R.string.controls_pin_verify)
+            setView(R.layout.controls_dialog_pin)
+            setPositiveButton(
+                android.R.string.ok,
+                DialogInterface.OnClickListener { dialog, _ ->
+                    if (dialog is Dialog) {
+                        dialog.requireViewById<EditText>(R.id.controls_pin_input)
+                        val pin = dialog.requireViewById<EditText>(R.id.controls_pin_input)
+                            .getText().toString()
+                        cvh.action(addChallengeValue(lastAction, pin))
+                        dialog.dismiss()
+                    }
+            })
+            setNegativeButton(
+                android.R.string.cancel,
+                DialogInterface.OnClickListener { dialog, _ -> dialog.cancel() }
+            )
+        }
+        return builder.create().apply {
+            getWindow().apply {
+                setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+                setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
+            }
+            setOnShowListener(DialogInterface.OnShowListener { _ ->
+                val editText = requireViewById<EditText>(R.id.controls_pin_input)
+                requireViewById<CheckBox>(R.id.controls_pin_use_alpha).setOnClickListener { v ->
+                    if ((v as CheckBox).isChecked) {
+                        editText.setInputType(
+                            InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD)
+                    } else {
+                        editText.setInputType(
+                            InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD)
+                    }
+                }
+                editText.requestFocus()
+            })
+        }
+    }
+
+    private fun addChallengeValue(action: ControlAction, challengeValue: String): ControlAction {
+        val id = action.getTemplateId()
+        return when (action) {
+            is BooleanAction -> BooleanAction(id, action.getNewState(), challengeValue)
+            is FloatAction -> FloatAction(id, action.getNewValue(), challengeValue)
+            is CommandAction -> CommandAction(id, challengeValue)
+            is ModeAction -> ModeAction(id, action.getNewMode(), challengeValue)
+            else -> throw IllegalStateException("'action' is not a known type: $action")
+        }
+    }
+}