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

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

Merge changes from topic "flexiglass-sysuistate-overrides" into main

* changes:
  [flexiglass] Fixes issue where bouncer exited upon orientation change.
  [flexiglass] Overrides some SysUiState flags.
parents a5d07622 deb07988
Loading
Loading
Loading
Loading
+14 −29
Original line number Diff line number Diff line
@@ -14,9 +14,10 @@
 * limitations under the License.
 */

@file:OptIn(ExperimentalComposeUiApi::class)

package com.android.systemui.bouncer.ui.composable

import android.view.ViewTreeObserver
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.LocalTextStyle
@@ -25,25 +26,25 @@ import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onInterceptKeyBeforeSoftKeyboard
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowInsetsCompat
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel

/** UI for the input part of a password-requiring version of the bouncer. */
@@ -64,9 +65,6 @@ internal fun PasswordBouncer(
    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
    val animateFailure: Boolean by viewModel.animateFailure.collectAsState()

    val isImeVisible by isSoftwareKeyboardVisible()
    LaunchedEffect(isImeVisible) { viewModel.onImeVisibilityChanged(isImeVisible) }

    DisposableEffect(Unit) {
        viewModel.onShown()
        onDispose { viewModel.onHidden() }
@@ -109,27 +107,14 @@ internal fun PasswordBouncer(
                        end = Offset(size.width, y = size.height - lineWidthPx),
                        strokeWidth = lineWidthPx,
                    )
                },
    )
                }

/** Returns a [State] with `true` when the IME/keyboard is visible and `false` when it's not. */
@Composable
fun isSoftwareKeyboardVisible(): State<Boolean> {
    val view = LocalView.current
    val viewTreeObserver = view.viewTreeObserver

    return produceState(
        initialValue = false,
        key1 = viewTreeObserver,
    ) {
        val listener =
            ViewTreeObserver.OnGlobalLayoutListener {
                value = view.rootWindowInsets?.isVisible(WindowInsetsCompat.Type.ime()) ?: false
            }

        viewTreeObserver.addOnGlobalLayoutListener(listener)

        awaitDispose { viewTreeObserver.removeOnGlobalLayoutListener(listener) }
                .onInterceptKeyBeforeSoftKeyboard { keyEvent ->
                    if (keyEvent.key == Key.Back) {
                        viewModel.onImeDismissed()
                        true
                    } else {
                        false
                    }
                },
    )
}
+2 −34
Original line number Diff line number Diff line
@@ -208,45 +208,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
        }

    @Test
    fun onImeVisibilityChanged_false_doesNothing() =
    fun onImeDismissed() =
        testScope.runTest {
            val events by collectValues(bouncerInteractor.onImeHiddenByUser)
            assertThat(events).isEmpty()

            underTest.onImeVisibilityChanged(isVisible = false)
            assertThat(events).isEmpty()
        }

    @Test
    fun onImeVisibilityChanged_falseAfterTrue_emitsOnImeHiddenByUserEvent() =
        testScope.runTest {
            val events by collectValues(bouncerInteractor.onImeHiddenByUser)
            assertThat(events).isEmpty()

            underTest.onImeVisibilityChanged(isVisible = true)
            assertThat(events).isEmpty()

            underTest.onImeVisibilityChanged(isVisible = false)
            assertThat(events).hasSize(1)

            underTest.onImeVisibilityChanged(isVisible = true)
            underTest.onImeDismissed()
            assertThat(events).hasSize(1)

            underTest.onImeVisibilityChanged(isVisible = false)
            assertThat(events).hasSize(2)
        }

    @Test
    fun onImeVisibilityChanged_falseAfterTrue_whileLockedOut_doesNothing() =
        testScope.runTest {
            val events by collectValues(bouncerInteractor.onImeHiddenByUser)
            assertThat(events).isEmpty()
            underTest.onImeVisibilityChanged(isVisible = true)
            setLockout(true)

            underTest.onImeVisibilityChanged(isVisible = false)

            assertThat(events).isEmpty()
        }

    @Test
+4 −8
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.model.SysUiState
import com.android.systemui.model.sceneContainerPlugin
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -244,7 +245,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
        kosmos.fakeDeviceEntryRepository.setUnlocked(false)

        val displayTracker = FakeDisplayTracker(context)
        val sysUiState = SysUiState(displayTracker)
        val sysUiState = SysUiState(displayTracker, kosmos.sceneContainerPlugin)
        val startable =
            SceneContainerStartable(
                applicationScope = testScope.backgroundScope,
@@ -775,14 +776,9 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
    }

    /** Emulates the dismissal of the IME (soft keyboard). */
    private suspend fun TestScope.dismissIme(
        showImeBeforeDismissing: Boolean = true,
    ) {
    private fun TestScope.dismissIme() {
        (bouncerViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let {
            if (showImeBeforeDismissing) {
                it.onImeVisibilityChanged(true)
            }
            it.onImeVisibilityChanged(false)
            it.onImeDismissed()
            runCurrent()
        }
    }
+4 −14
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

/** Holds UI state and handles user input for the password bouncer UI. */
class PasswordBouncerViewModel(
@@ -48,9 +49,6 @@ class PasswordBouncerViewModel(

    override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message

    /** Whether the input method editor (for example, the software keyboard) is visible. */
    private var isImeVisible: Boolean = false

    /** Whether the text field element currently has focus. */
    private val isTextFieldFocused = MutableStateFlow(false)

@@ -65,7 +63,6 @@ class PasswordBouncerViewModel(

    override fun onHidden() {
        super.onHidden()
        isImeVisible = false
        isTextFieldFocused.value = false
    }

@@ -97,16 +94,9 @@ class PasswordBouncerViewModel(
        }
    }

    /**
     * Notifies that the input method editor (for example, the software keyboard) has been shown or
     * hidden.
     */
    suspend fun onImeVisibilityChanged(isVisible: Boolean) {
        if (isImeVisible && !isVisible && isInputEnabled.value) {
            interactor.onImeHiddenByUser()
        }

        isImeVisible = isVisible
    /** Notifies that the user has dismissed the software keyboard (IME). */
    fun onImeDismissed() {
        viewModelScope.launch { interactor.onImeHiddenByUser() }
    }

    /** Notifies that the password text field has gained or lost focus. */
+6 −2
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ import com.android.systemui.log.dagger.MonitorLog;
import com.android.systemui.log.table.TableLogBuffer;
import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
import com.android.systemui.model.SceneContainerPlugin;
import com.android.systemui.model.SysUiState;
import com.android.systemui.motiontool.MotionToolModule;
import com.android.systemui.navigationbar.NavigationBarComponent;
@@ -268,8 +269,11 @@ public abstract class SystemUIModule {

    @SysUISingleton
    @Provides
    static SysUiState provideSysUiState(DisplayTracker displayTracker, DumpManager dumpManager) {
        final SysUiState state = new SysUiState(displayTracker);
    static SysUiState provideSysUiState(
            DisplayTracker displayTracker,
            DumpManager dumpManager,
            SceneContainerPlugin sceneContainerPlugin) {
        final SysUiState state = new SysUiState(displayTracker, sceneContainerPlugin);
        dumpManager.registerDumpable(state);
        return state;
    }
Loading