Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d2ee9cd1 authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Set user on password bouncer TextField." into main

parents 7f822261 b261db12
Loading
Loading
Loading
Loading
+46 −40
Original line number Diff line number Diff line
@@ -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,6 +89,7 @@ internal fun PasswordBouncer(
    val color = MaterialTheme.colorScheme.onSurfaceVariant
    val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }

    SelectedUserAwareInputConnection(selectedUserId) {
        TextField(
            value = password,
            onValueChange = viewModel::onPasswordInputChanged,
@@ -126,9 +129,12 @@ internal fun PasswordBouncer(
            trailingIcon =
                if (isImeSwitcherButtonVisible) {
                    { ImeSwitcherButton(viewModel, color) }
            } else null
                } else {
                    null
                }
        )
    }
}

/** Button for changing the password input method (IME). */
@Composable
+78 −0
Original line number Diff line number Diff line
/*
 * 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()
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -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