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

Commit c57b81f5 authored by Chandru S's avatar Chandru S
Browse files

Update flags on the window to allow the window to be focusable.

This fixes the issue of IME (soft keyboard) not showing up when the compose password bouncer is enabled.
InputMethodManager doesn't show the IME when the window containing the text field has the FLAG_NOT_FOCUSABLE flag.

Fixes: 361793239
Test: manually,
 1. set password auth method
 2. open the bouncer
 3. keyboard should show up
Flag: com.android.systemui.compose_bouncer
Change-Id: I665d57c80f9ea9922803f81a987ec5f61e0bd6c4
parent 02aebb42
Loading
Loading
Loading
Loading
+0 −35
Original line number Original line Diff line number Diff line
@@ -1191,41 +1191,6 @@ class SceneContainerStartableTest : SysuiTestCase() {
            verify(notificationShadeWindowController, times(1)).setKeyguardShowing(true)
            verify(notificationShadeWindowController, times(1)).setKeyguardShowing(true)
        }
        }


    @Test
    fun hydrateWindowController_setBouncerShowing() =
        testScope.runTest {
            underTest.start()
            val notificationShadeWindowController = kosmos.notificationShadeWindowController
            val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen)
            val currentScene by collectLastValue(sceneInteractor.currentScene)
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
            verify(notificationShadeWindowController, never()).setBouncerShowing(true)
            verify(notificationShadeWindowController, times(1)).setBouncerShowing(false)

            emulateSceneTransition(transitionStateFlow, Scenes.Bouncer)
            verify(notificationShadeWindowController, times(1)).setBouncerShowing(true)
            verify(notificationShadeWindowController, times(1)).setBouncerShowing(false)

            emulateSceneTransition(transitionStateFlow, Scenes.Lockscreen)
            verify(notificationShadeWindowController, times(1)).setBouncerShowing(true)
            verify(notificationShadeWindowController, times(2)).setBouncerShowing(false)

            kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
            assertThat(currentScene).isEqualTo(Scenes.Gone)
            verify(notificationShadeWindowController, times(1)).setBouncerShowing(true)
            verify(notificationShadeWindowController, times(2)).setBouncerShowing(false)

            emulateSceneTransition(transitionStateFlow, Scenes.Lockscreen)
            verify(notificationShadeWindowController, times(1)).setBouncerShowing(true)
            verify(notificationShadeWindowController, times(2)).setBouncerShowing(false)

            emulateSceneTransition(transitionStateFlow, Scenes.Bouncer)
            verify(notificationShadeWindowController, times(2)).setBouncerShowing(true)
            verify(notificationShadeWindowController, times(2)).setBouncerShowing(false)
        }

    @Test
    @Test
    fun hydrateWindowController_setKeyguardOccluded() =
    fun hydrateWindowController_setKeyguardOccluded() =
        testScope.runTest {
        testScope.runTest {
+96 −0
Original line number Original line Diff line number Diff line
@@ -16,18 +16,28 @@


package com.android.systemui.shade.ui.viewmodel
package com.android.systemui.shade.ui.viewmodel


import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
@@ -150,4 +160,90 @@ class NotificationShadeWindowModelTest : SysuiTestCase() {
            )
            )
            assertThat(isKeyguardOccluded).isTrue()
            assertThat(isKeyguardOccluded).isTrue()
        }
        }

    @Test
    @EnableSceneContainer
    fun withSceneContainer_bouncerShowing_providesTheCorrectState() =
        testScope.runTest {
            val bouncerShowing by collectLastValue(underTest.isBouncerShowing)

            val transitionState =
                MutableStateFlow<ObservableTransitionState>(
                    ObservableTransitionState.Idle(Scenes.Lockscreen)
                )
            kosmos.sceneInteractor.setTransitionState(transitionState)
            runCurrent()
            assertThat(bouncerShowing).isFalse()

            transitionState.value = ObservableTransitionState.Idle(Scenes.Bouncer)
            runCurrent()
            assertThat(bouncerShowing).isTrue()
        }

    @Test
    @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER)
    fun withComposeBouncer_bouncerShowing_providesTheCorrectState() =
        testScope.runTest {
            val bouncerShowing by collectLastValue(underTest.isBouncerShowing)

            kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = false)
            runCurrent()
            assertThat(bouncerShowing).isFalse()

            kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = true)
            runCurrent()
            assertThat(bouncerShowing).isTrue()
        }

    @Test
    @EnableSceneContainer
    fun withSceneContainer_doesBouncerRequireIme_providesTheCorrectState() =
        testScope.runTest {
            val bouncerRequiresIme by collectLastValue(underTest.doesBouncerRequireIme)
            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.Pin
            )

            val transitionState =
                MutableStateFlow<ObservableTransitionState>(
                    ObservableTransitionState.Idle(Scenes.Bouncer)
                )
            kosmos.sceneInteractor.setTransitionState(transitionState)
            runCurrent()
            assertThat(bouncerRequiresIme).isFalse()

            // go back to lockscreen
            transitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
            runCurrent()

            // change auth method
            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.Password
            )
            // go back to bouncer
            transitionState.value = ObservableTransitionState.Idle(Scenes.Bouncer)
            runCurrent()
            assertThat(bouncerRequiresIme).isTrue()
        }

    @Test
    @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER)
    fun withComposeBouncer_doesBouncerRequireIme_providesTheCorrectState() =
        testScope.runTest {
            val bouncerRequiresIme by collectLastValue(underTest.doesBouncerRequireIme)
            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.Pin
            )

            kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = true)
            runCurrent()
            assertThat(bouncerRequiresIme).isFalse()

            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.Password
            )
            kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = true)
            runCurrent()
            assertThat(bouncerRequiresIme).isFalse()
        }
}
}
+0 −9
Original line number Original line Diff line number Diff line
@@ -570,15 +570,6 @@ constructor(
            }
            }
        }
        }


        applicationScope.launch {
            sceneInteractor.currentScene
                .map { it == Scenes.Bouncer }
                .distinctUntilChanged()
                .collect { isBouncerShowing ->
                    windowController.setBouncerShowing(isBouncerShowing)
                }
        }

        applicationScope.launch {
        applicationScope.launch {
            occlusionInteractor.invisibleDueToOcclusion.collect { invisibleDueToOcclusion ->
            occlusionInteractor.invisibleDueToOcclusion.collect { invisibleDueToOcclusion ->
                windowController.setKeyguardOccluded(invisibleDueToOcclusion)
                windowController.setKeyguardOccluded(invisibleDueToOcclusion)
+7 −0
Original line number Original line Diff line number Diff line
@@ -49,6 +49,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
import com.android.systemui.Dumpable;
import com.android.systemui.Flags;
import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.SysUISingleton;
@@ -342,6 +343,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
                    this::setKeyguardOccluded
                    this::setKeyguardOccluded
            );
            );
        }
        }
        if (ComposeBouncerFlags.INSTANCE.isComposeBouncerOrSceneContainerEnabled()) {
            collectFlow(mWindowRootView, mNotificationShadeWindowModel.isBouncerShowing(),
                    this::setBouncerShowing);
            collectFlow(mWindowRootView, mNotificationShadeWindowModel.getDoesBouncerRequireIme(),
                    this::setKeyguardNeedsInput);
        }
    }
    }


    @Override
    @Override
+61 −0
Original line number Original line Diff line number Diff line
@@ -16,16 +16,25 @@


package com.android.systemui.shade.ui.viewmodel
package com.android.systemui.shade.ui.viewmodel


import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.util.kotlin.BooleanFlowOperators.any
import com.android.systemui.util.kotlin.BooleanFlowOperators.any
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map


/** Models UI state for the shade window. */
/** Models UI state for the shade window. */
@@ -34,6 +43,9 @@ class NotificationShadeWindowModel
@Inject
@Inject
constructor(
constructor(
    keyguardTransitionInteractor: KeyguardTransitionInteractor,
    keyguardTransitionInteractor: KeyguardTransitionInteractor,
    sceneInteractor: dagger.Lazy<SceneInteractor>,
    authenticationInteractor: dagger.Lazy<AuthenticationInteractor>,
    primaryBouncerInteractor: PrimaryBouncerInteractor,
) {
) {
    /**
    /**
     * Considered to be occluded if in OCCLUDED, DREAMING, GLANCEABLE_HUB/Communal, or transitioning
     * Considered to be occluded if in OCCLUDED, DREAMING, GLANCEABLE_HUB/Communal, or transitioning
@@ -70,4 +82,53 @@ constructor(
                ),
                ),
            )
            )
            .any()
            .any()

    /**
     * Whether bouncer is currently showing or not.
     *
     * Applicable only when either [SceneContainerFlag] or [ComposeBouncerFlags] are enabled,
     * otherwise it throws an error.
     */
    val isBouncerShowing: Flow<Boolean> =
        when {
            SceneContainerFlag.isEnabled -> {
                sceneInteractor.get().transitionState.map { it.isIdle(Scenes.Bouncer) }
            }
            ComposeBouncerFlags.isOnlyComposeBouncerEnabled() -> primaryBouncerInteractor.isShowing
            else ->
                flow {
                    error(
                        "Consume this flow only when SceneContainerFlag " +
                            "or ComposeBouncerFlags are enabled"
                    )
                }
        }.distinctUntilChanged()

    /**
     * Whether the bouncer currently require IME for device entry.
     *
     * This emits true when the authentication method is set to password and the bouncer is
     * currently showing. Throws an error when this is used without either [SceneContainerFlag] or
     * [ComposeBouncerFlags]
     */
    val doesBouncerRequireIme: Flow<Boolean> =
        if (ComposeBouncerFlags.isComposeBouncerOrSceneContainerEnabled()) {
                // This is required to make the window, where the bouncer resides,
                // focusable. InputMethodManager allows IME to be shown only for views
                // in windows that do not have the FLAG_NOT_FOCUSABLE flag.

                isBouncerShowing
                    .sample(authenticationInteractor.get().authenticationMethod, ::Pair)
                    .map { (showing, authMethod) ->
                        showing && authMethod == AuthenticationMethodModel.Password
                    }
            } else {
                flow {
                    error(
                        "Consume this flow only when SceneContainerFlag " +
                            "or ComposeBouncerFlags are enabled"
                    )
                }
            }
            .distinctUntilChanged()
}
}
Loading