1 package com.android.systemui.biometrics.ui.binder 2 3 import android.os.UserHandle 4 import android.view.KeyEvent 5 import android.view.View 6 import android.view.inputmethod.EditorInfo 7 import android.view.inputmethod.InputMethodManager 8 import android.widget.ImeAwareEditText 9 import android.widget.TextView 10 import android.window.OnBackInvokedCallback 11 import android.window.OnBackInvokedDispatcher 12 import androidx.lifecycle.Lifecycle 13 import androidx.lifecycle.lifecycleScope 14 import androidx.lifecycle.repeatOnLifecycle 15 import com.android.systemui.R 16 import com.android.systemui.biometrics.ui.CredentialPasswordView 17 import com.android.systemui.biometrics.ui.CredentialView 18 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel 19 import com.android.systemui.lifecycle.repeatWhenAttached 20 import kotlinx.coroutines.awaitCancellation 21 import kotlinx.coroutines.flow.first 22 import kotlinx.coroutines.flow.firstOrNull 23 import kotlinx.coroutines.launch 24 25 /** Sub-binder for the [CredentialPasswordView]. */ 26 object CredentialPasswordViewBinder { 27 28 /** Bind the view. */ 29 fun bind( 30 view: CredentialPasswordView, 31 host: CredentialView.Host, 32 viewModel: CredentialViewModel, 33 requestFocusForInput: Boolean, 34 ) { 35 val imeManager = view.context.getSystemService(InputMethodManager::class.java)!! 36 37 val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword) 38 39 val onBackInvokedCallback = OnBackInvokedCallback { host.onCredentialAborted() } 40 41 view.repeatWhenAttached { 42 // the header info never changes - do it early 43 val header = viewModel.header.first() 44 passwordField.setTextOperationUser(UserHandle.of(header.user.deviceCredentialOwnerId)) 45 viewModel.inputFlags.firstOrNull()?.let { flags -> passwordField.inputType = flags } 46 if (requestFocusForInput) { 47 passwordField.requestFocus() 48 passwordField.scheduleShowSoftInput() 49 } 50 passwordField.setOnEditorActionListener( 51 OnImeSubmitListener { text -> 52 lifecycleScope.launch { viewModel.checkCredential(text, header) } 53 } 54 ) 55 passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback)) 56 57 repeatOnLifecycle(Lifecycle.State.STARTED) { 58 // dismiss on a valid credential check 59 launch { 60 viewModel.validatedAttestation.collect { attestation -> 61 if (attestation != null) { 62 imeManager.hideSoftInputFromWindow(view.windowToken, 0 /* flags */) 63 host.onCredentialMatched(attestation) 64 } else { 65 passwordField.setText("") 66 } 67 } 68 } 69 70 val onBackInvokedDispatcher = view.findOnBackInvokedDispatcher() 71 if (onBackInvokedDispatcher != null) { 72 launch { 73 onBackInvokedDispatcher.registerOnBackInvokedCallback( 74 OnBackInvokedDispatcher.PRIORITY_DEFAULT, 75 onBackInvokedCallback 76 ) 77 awaitCancellation() 78 } 79 .invokeOnCompletion { 80 onBackInvokedDispatcher.unregisterOnBackInvokedCallback( 81 onBackInvokedCallback 82 ) 83 } 84 } 85 } 86 } 87 } 88 } 89 90 private class OnBackButtonListener(private val onBackInvokedCallback: OnBackInvokedCallback) : 91 View.OnKeyListener { 92 override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean { 93 if (keyCode != KeyEvent.KEYCODE_BACK) { 94 return false 95 } 96 if (event.action == KeyEvent.ACTION_UP) { 97 onBackInvokedCallback.onBackInvoked() 98 } 99 return true 100 } 101 } 102 103 private class OnImeSubmitListener(private val onSubmit: (text: CharSequence) -> Unit) : 104 TextView.OnEditorActionListener { 105 override fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent?): Boolean { 106 val isSoftImeEvent = 107 event == null && 108 (actionId == EditorInfo.IME_NULL || 109 actionId == EditorInfo.IME_ACTION_DONE || 110 actionId == EditorInfo.IME_ACTION_NEXT) 111 val isKeyboardEnterKey = 112 event != null && 113 KeyEvent.isConfirmKey(event.keyCode) && 114 event.action == KeyEvent.ACTION_DOWN 115 if (isSoftImeEvent || isKeyboardEnterKey) { 116 onSubmit(v.text) 117 return true 118 } 119 return false 120 } 121 } 122