1 /*
<lambda>null2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.controls.ui
18 
19 import android.app.AlertDialog
20 import android.app.Dialog
21 import android.content.DialogInterface
22 import android.service.controls.actions.BooleanAction
23 import android.service.controls.actions.CommandAction
24 import android.service.controls.actions.ControlAction
25 import android.service.controls.actions.FloatAction
26 import android.service.controls.actions.ModeAction
27 import android.text.InputType
28 import android.util.Log
29 import android.view.LayoutInflater
30 import android.view.WindowManager
31 import android.view.inputmethod.InputMethodManager
32 import android.widget.CheckBox
33 import android.widget.EditText
34 
35 import com.android.systemui.res.R
36 
37 /**
38  * Creates all dialogs for challengeValues that can occur from a call to
39  * [ControlsProviderService#performControlAction]. The types of challenge responses are listed in
40  * [ControlAction.ResponseResult].
41  */
42 object ChallengeDialogs {
43 
44     private const val WINDOW_TYPE = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
45     private const val STYLE = android.R.style.Theme_DeviceDefault_Dialog_Alert
46 
47     /**
48      * AlertDialogs to handle [ControlAction#RESPONSE_CHALLENGE_PIN] and
49      * [ControlAction#RESPONSE_CHALLENGE_PIN] responses, decided by the useAlphaNumeric
50      * parameter.
51      */
52     fun createPinDialog(
53         cvh: ControlViewHolder,
54         useAlphaNumeric: Boolean,
55         useRetryStrings: Boolean,
56         onCancel: () -> Unit
57     ): Dialog? {
58         val lastAction = cvh.lastAction
59         if (lastAction == null) {
60             Log.e(ControlsUiController.TAG,
61                 "PIN Dialog attempted but no last action is set. Will not show")
62             return null
63         }
64         val res = cvh.context.resources
65         val (title, instructions) = if (useRetryStrings) {
66             Pair(
67                 res.getString(R.string.controls_pin_wrong),
68                 R.string.controls_pin_instructions_retry
69             )
70         } else {
71             Pair(
72                 res.getString(R.string.controls_pin_verify, cvh.title.getText()),
73                 R.string.controls_pin_instructions
74             )
75         }
76         return object : AlertDialog(cvh.context, STYLE) {
77             override fun dismiss() {
78                 window?.decorView?.let {
79                     // workaround for b/159309083
80                     it.context.getSystemService(InputMethodManager::class.java)
81                             ?.hideSoftInputFromWindow(it.windowToken, 0)
82                 }
83                 super.dismiss()
84             }
85         }.apply {
86             setTitle(title)
87             setView(LayoutInflater.from(context).inflate(R.layout.controls_dialog_pin, null))
88             setButton(
89                 DialogInterface.BUTTON_POSITIVE,
90                 context.getText(android.R.string.ok),
91                 DialogInterface.OnClickListener { dialog, _ ->
92                     if (dialog is Dialog) {
93                         dialog.requireViewById<EditText>(R.id.controls_pin_input)
94                         val pin = dialog.requireViewById<EditText>(R.id.controls_pin_input)
95                             .getText().toString()
96                         cvh.action(addChallengeValue(lastAction, pin))
97                         dialog.dismiss()
98                     }
99             })
100             setButton(
101                 DialogInterface.BUTTON_NEGATIVE,
102                 context.getText(android.R.string.cancel),
103                 DialogInterface.OnClickListener { dialog, _ ->
104                     onCancel.invoke()
105                     dialog.cancel()
106                 }
107             )
108 
109             window?.setType(WINDOW_TYPE)
110             window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
111             setOnShowListener(DialogInterface.OnShowListener { _ ->
112                 val editText = requireViewById<EditText>(R.id.controls_pin_input)
113                 editText.setHint(instructions)
114                 val useAlphaCheckBox = requireViewById<CheckBox>(R.id.controls_pin_use_alpha)
115                 useAlphaCheckBox.setChecked(useAlphaNumeric)
116                 setInputType(editText, useAlphaCheckBox.isChecked())
117                 requireViewById<CheckBox>(R.id.controls_pin_use_alpha).setOnClickListener { _ ->
118                     setInputType(editText, useAlphaCheckBox.isChecked())
119                 }
120                 editText.requestFocus()
121             })
122         }
123     }
124 
125     /**
126      * AlertDialogs to handle [ControlAction#RESPONSE_CHALLENGE_ACK] response type.
127      */
128     fun createConfirmationDialog(cvh: ControlViewHolder, onCancel: () -> Unit): Dialog? {
129         val lastAction = cvh.lastAction
130         if (lastAction == null) {
131             Log.e(ControlsUiController.TAG,
132                 "Confirmation Dialog attempted but no last action is set. Will not show")
133             return null
134         }
135         val builder = AlertDialog.Builder(cvh.context, STYLE).apply {
136             val res = cvh.context.resources
137             setTitle(res.getString(
138                 R.string.controls_confirmation_message, cvh.title.getText()))
139             setPositiveButton(
140                 android.R.string.ok,
141                 DialogInterface.OnClickListener { dialog, _ ->
142                     cvh.action(addChallengeValue(lastAction, "true"))
143                     dialog.dismiss()
144             })
145             setNegativeButton(
146                 android.R.string.cancel,
147                 DialogInterface.OnClickListener { dialog, _ ->
148                     onCancel.invoke()
149                     dialog.cancel()
150                 }
151             )
152         }
153         return builder.create().apply {
154             window?.setType(WINDOW_TYPE)
155         }
156     }
157 
158     private fun setInputType(editText: EditText, useTextInput: Boolean) {
159         if (useTextInput) {
160             editText.setInputType(
161                 InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD)
162         } else {
163             editText.setInputType(
164                 InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD)
165         }
166     }
167 
168     private fun addChallengeValue(action: ControlAction, challengeValue: String): ControlAction {
169         val id = action.getTemplateId()
170         return when (action) {
171             is BooleanAction -> BooleanAction(id, action.getNewState(), challengeValue)
172             is FloatAction -> FloatAction(id, action.getNewValue(), challengeValue)
173             is CommandAction -> CommandAction(id, challengeValue)
174             is ModeAction -> ModeAction(id, action.getNewMode(), challengeValue)
175             else -> throw IllegalStateException("'action' is not a known type: $action")
176         }
177     }
178 }
179