diff options
| author | 2024-03-05 21:16:08 +0000 | |
|---|---|---|
| committer | 2024-03-05 21:16:08 +0000 | |
| commit | d2ee9cd19856c152f200977f4c52068f95d74943 (patch) | |
| tree | 14336f7dff10b30e11ba8e05aa55248bcfae9772 | |
| parent | 7f82226121b1b537392267de728aab8250f90378 (diff) | |
| parent | b261db129e58d779a7dcc3fc26eb97b76733ce68 (diff) | |
Merge "[flexiglass] Set user on password bouncer TextField." into main
3 files changed, 132 insertions, 40 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index bd539a740e81..2a13d4931b69 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -51,6 +51,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.android.compose.PlatformIconButton import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel +import com.android.systemui.common.ui.compose.SelectedUserAwareInputConnection import com.android.systemui.res.R /** UI for the input part of a password-requiring version of the bouncer. */ @@ -71,6 +72,7 @@ internal fun PasswordBouncer( val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() val animateFailure: Boolean by viewModel.animateFailure.collectAsState() val isImeSwitcherButtonVisible by viewModel.isImeSwitcherButtonVisible.collectAsState() + val selectedUserId by viewModel.selectedUserId.collectAsState() DisposableEffect(Unit) { viewModel.onShown() @@ -87,47 +89,51 @@ internal fun PasswordBouncer( val color = MaterialTheme.colorScheme.onSurfaceVariant val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() } - TextField( - value = password, - onValueChange = viewModel::onPasswordInputChanged, - enabled = isInputEnabled, - visualTransformation = PasswordVisualTransformation(), - singleLine = true, - textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), - keyboardOptions = - KeyboardOptions( - keyboardType = KeyboardType.Password, - imeAction = ImeAction.Done, - ), - keyboardActions = - KeyboardActions( - onDone = { viewModel.onAuthenticateKeyPressed() }, - ), - modifier = - modifier - .focusRequester(focusRequester) - .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) } - .drawBehind { - drawLine( - color = color, - start = Offset(x = 0f, y = size.height - lineWidthPx), - end = Offset(size.width, y = size.height - lineWidthPx), - strokeWidth = lineWidthPx, - ) - } - .onInterceptKeyBeforeSoftKeyboard { keyEvent -> - if (keyEvent.key == Key.Back) { - viewModel.onImeDismissed() - true - } else { - false + SelectedUserAwareInputConnection(selectedUserId) { + TextField( + value = password, + onValueChange = viewModel::onPasswordInputChanged, + enabled = isInputEnabled, + visualTransformation = PasswordVisualTransformation(), + singleLine = true, + textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), + keyboardOptions = + KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done, + ), + keyboardActions = + KeyboardActions( + onDone = { viewModel.onAuthenticateKeyPressed() }, + ), + modifier = + modifier + .focusRequester(focusRequester) + .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) } + .drawBehind { + drawLine( + color = color, + start = Offset(x = 0f, y = size.height - lineWidthPx), + end = Offset(size.width, y = size.height - lineWidthPx), + strokeWidth = lineWidthPx, + ) } - }, - trailingIcon = - if (isImeSwitcherButtonVisible) { - { ImeSwitcherButton(viewModel, color) } - } else null - ) + .onInterceptKeyBeforeSoftKeyboard { keyEvent -> + if (keyEvent.key == Key.Back) { + viewModel.onImeDismissed() + true + } else { + false + } + }, + trailingIcon = + if (isImeSwitcherButtonVisible) { + { ImeSwitcherButton(viewModel, color) } + } else { + null + } + ) + } } /** Button for changing the password input method (IME). */ diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt new file mode 100644 index 000000000000..c8e145034551 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 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. + */ + +@file:OptIn(ExperimentalComposeUiApi::class) + +package com.android.systemui.common.ui.compose + +import android.annotation.UserIdInt +import android.os.UserHandle +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputConnection +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.platform.InterceptPlatformTextInput +import androidx.compose.ui.platform.PlatformTextInputMethodRequest + +/** + * Wrapper for input connection composables that need to be aware of the selected user to connect to + * the correct instance of on-device services like autofill, autocorrect, etc. + * + * Usage: + * ``` + * @Composable + * fun YourFunction(viewModel: YourViewModel) { + * val selectedUserId by viewModel.selectedUserId.collectAsState() + * + * SelectedUserAwareInputConnection(selectedUserId) { + * TextField(...) + * } + * } + * ``` + */ +@Composable +fun SelectedUserAwareInputConnection( + @UserIdInt selectedUserId: Int, + content: @Composable () -> Unit, +) { + InterceptPlatformTextInput( + interceptor = { request, nextHandler -> + // Create a new request to wrap the incoming one with some custom logic. + val modifiedRequest = + object : PlatformTextInputMethodRequest { + override fun createInputConnection(outAttributes: EditorInfo): InputConnection { + val inputConnection = request.createInputConnection(outAttributes) + // After the original request finishes initializing the EditorInfo we can + // customize it. If we needed to we could also wrap the InputConnection + // before + // returning it. + updateEditorInfo(outAttributes) + return inputConnection + } + + fun updateEditorInfo(outAttributes: EditorInfo) { + outAttributes.targetInputMethodUser = UserHandle.of(selectedUserId) + } + } + + // Send our wrapping request to the next handler, which could be the system or another + // interceptor up the tree. + nextHandler.startInputMethod(modifiedRequest) + } + ) { + content() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index 1c8b84d82a56..b42eda108d54 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -73,6 +73,14 @@ class PasswordBouncerViewModel( initialValue = isInputEnabled.value && !isTextFieldFocused.value, ) + /** The ID of the currently-selected user. */ + val selectedUserId: StateFlow<Int> = + selectedUserInteractor.selectedUser.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(), + initialValue = selectedUserInteractor.getSelectedUserId(), + ) + override fun onHidden() { super.onHidden() isTextFieldFocused.value = false |