1 /* 2 * 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.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 getWindow().apply { 110 setType(WINDOW_TYPE) 111 setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) 112 } 113 setOnShowListener(DialogInterface.OnShowListener { _ -> 114 val editText = requireViewById<EditText>(R.id.controls_pin_input) 115 editText.setHint(instructions) 116 val useAlphaCheckBox = requireViewById<CheckBox>(R.id.controls_pin_use_alpha) 117 useAlphaCheckBox.setChecked(useAlphaNumeric) 118 setInputType(editText, useAlphaCheckBox.isChecked()) 119 requireViewById<CheckBox>(R.id.controls_pin_use_alpha).setOnClickListener { _ -> 120 setInputType(editText, useAlphaCheckBox.isChecked()) 121 } 122 editText.requestFocus() 123 }) 124 } 125 } 126 127 /** 128 * AlertDialogs to handle [ControlAction#RESPONSE_CHALLENGE_ACK] response type. 129 */ 130 fun createConfirmationDialog(cvh: ControlViewHolder, onCancel: () -> Unit): Dialog? { 131 val lastAction = cvh.lastAction 132 if (lastAction == null) { 133 Log.e(ControlsUiController.TAG, 134 "Confirmation Dialog attempted but no last action is set. Will not show") 135 return null 136 } 137 val builder = AlertDialog.Builder(cvh.context, STYLE).apply { 138 val res = cvh.context.resources 139 setTitle(res.getString( 140 R.string.controls_confirmation_message, cvh.title.getText())) 141 setPositiveButton( 142 android.R.string.ok, 143 DialogInterface.OnClickListener { dialog, _ -> 144 cvh.action(addChallengeValue(lastAction, "true")) 145 dialog.dismiss() 146 }) 147 setNegativeButton( 148 android.R.string.cancel, 149 DialogInterface.OnClickListener { dialog, _ -> 150 onCancel.invoke() 151 dialog.cancel() 152 } 153 ) 154 } 155 return builder.create().apply { 156 getWindow().apply { 157 setType(WINDOW_TYPE) 158 } 159 } 160 } 161 162 private fun setInputType(editText: EditText, useTextInput: Boolean) { 163 if (useTextInput) { 164 editText.setInputType( 165 InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD) 166 } else { 167 editText.setInputType( 168 InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD) 169 } 170 } 171 172 private fun addChallengeValue(action: ControlAction, challengeValue: String): ControlAction { 173 val id = action.getTemplateId() 174 return when (action) { 175 is BooleanAction -> BooleanAction(id, action.getNewState(), challengeValue) 176 is FloatAction -> FloatAction(id, action.getNewValue(), challengeValue) 177 is CommandAction -> CommandAction(id, challengeValue) 178 is ModeAction -> ModeAction(id, action.getNewMode(), challengeValue) 179 else -> throw IllegalStateException("'action' is not a known type: $action") 180 } 181 } 182 } 183