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

Commit 04b02def authored by William Xiao's avatar William Xiao Committed by Android (Google) Code Review
Browse files

Merge "Turn off dream back gesture and gesture handling when biometric prompt is showing" into main

parents 532b14d0 6ad87f46
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -1285,6 +1285,16 @@ flag {
    is_fixed_read_only: true
}

flag {
  name: "dream_biometric_prompt_fixes"
  namespace: "systemui"
  description: "Flags the behavior of  turning off dream back gesture disablement and gesture handling when the biometric prompt is showing"
  bug: "413511537"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}

flag {
  name: "app_clips_backlinks"
  namespace: "systemui"
+277 −125
Original line number Diff line number Diff line
@@ -36,12 +36,12 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.test.filters.SmallTest
import com.android.app.viewcapture.ViewCaptureFactory
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.logging.UiEventLogger
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_DREAM_BIOMETRIC_PROMPT_FIXES
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
@@ -49,6 +49,8 @@ import com.android.systemui.ambient.touch.TouchHandler
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
import com.android.systemui.ambient.touch.scrim.ScrimController
import com.android.systemui.biometrics.data.repository.promptRepository
import com.android.systemui.biometrics.domain.interactor.promptCredentialInteractor
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -72,9 +74,8 @@ import com.android.systemui.keyguard.gesture.domain.gestureInteractor
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
import com.android.systemui.navigationbar.gestural.data.gestureRepository
import com.android.systemui.navigationbar.gestural.domain.TaskInfo
import com.android.systemui.navigationbar.gestural.domain.TaskMatcher
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -149,8 +150,6 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
    private val lifecycleRegistry = FakeLifecycleRegistry(mLifecycleOwner)
    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
    private val communalRepository = kosmos.fakeCommunalSceneRepository
    private var viewCaptureSpy = spy(ViewCaptureFactory.getInstance(context))
    private val gestureInteractor = spy(kosmos.gestureInteractor)

    private lateinit var mCommunalInteractor: CommunalInteractor
    private lateinit var environmentComponents: EnvironmentComponents
@@ -245,6 +244,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
        // it to testDispatcher.
        Dispatchers.setMain(kosmos.testDispatcher)
        onTeardown { Dispatchers.resetMain() }
        with(kosmos) {
            mService =
                DreamOverlayService(
                    mContext,
@@ -259,21 +259,23 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
                    mKeyguardUpdateMonitor,
                    mScrimController,
                    mCommunalInteractor,
                kosmos.communalSettingsInteractor,
                kosmos.sceneInteractor,
                    promptCredentialInteractor,
                    communalSettingsInteractor,
                    sceneInteractor,
                    mSystemDialogsCloser,
                    mUiEventLogger,
                    mTouchInsetManager,
                    LOW_LIGHT_COMPONENT,
                    HOME_CONTROL_PANEL_DREAM_COMPONENT,
                    mDreamOverlayCallbackController,
                kosmos.keyguardInteractor,
                    keyguardInteractor,
                    gestureInteractor,
                kosmos.wakeGestureMonitor,
                kosmos.powerInteractor,
                    wakeGestureMonitor,
                    powerInteractor,
                    WINDOW_NAME,
                )
        }
    }

    private val client: IDreamOverlayClient
        get() {
@@ -1035,7 +1037,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
            bouncerRepository.setPrimaryShow(true)
            mMainExecutor.runAllReady()

            // Lifecycle state goes from resumed back to started when the notification shade shows.
            // Lifecycle state goes from resumed back to started when the bouncer shows.
            assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.STARTED)

            // Bouncer closes.
@@ -1080,6 +1082,88 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
            assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.RESUMED)
        }

    @DisableFlags(FLAG_SCENE_CONTAINER)
    @Test
    fun testBouncerShown_stopsGestureBlocking() =
        kosmos.runTest {
            val client = client

            // Inform the overlay service of dream starting. Do not show dream complications.
            client.startDream(
                mWindowParams,
                mDreamOverlayCallback,
                DREAM_COMPONENT,
                false, /*isPreview*/
                false, /*shouldShowComplication*/
            )
            mMainExecutor.runAllReady()

            // GestureBlockedMatcher added when overlay starts.
            assertThat(gestureRepository.gestureBlockedMatchers.value).hasSize(1)
            val matcher = gestureRepository.gestureBlockedMatchers.value.first()

            // Matcher matches dream activity.
            val dreamTaskInfo =
                TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM)
            assertThat(matcher.matches(dreamTaskInfo)).isTrue()

            // Bouncer shows.
            bouncerRepository.setPrimaryShow(true)
            mMainExecutor.runAllReady()

            // Matcher is removed.
            assertThat(gestureRepository.gestureBlockedMatchers.value).isEmpty()

            // Bouncer closes.
            bouncerRepository.setPrimaryShow(false)
            mMainExecutor.runAllReady()

            // Matcher is added again.
            assertThat(gestureRepository.gestureBlockedMatchers.value).hasSize(1)
        }

    @EnableFlags(FLAG_SCENE_CONTAINER)
    @Test
    fun testBouncerShown_withSceneContainer_stopsGestureBlocking() =
        kosmos.runTest {
            val client = client

            // Inform the overlay service of dream starting. Do not show dream complications.
            client.startDream(
                mWindowParams,
                mDreamOverlayCallback,
                DREAM_COMPONENT,
                false, /*isPreview*/
                false, /*shouldShowComplication*/
            )
            mMainExecutor.runAllReady()

            // GestureBlockedMatcher added when overlay starts.
            assertThat(gestureRepository.gestureBlockedMatchers.value).hasSize(1)
            val matcher = gestureRepository.gestureBlockedMatchers.value.first()

            // Matcher matches dream activity.
            val dreamTaskInfo =
                TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM)
            assertThat(matcher.matches(dreamTaskInfo)).isTrue()

            // Bouncer shows.
            kosmos.sceneInteractor.snapToScene(Scenes.Lockscreen, "test")
            kosmos.sceneInteractor.showOverlay(Overlays.Bouncer, "test")
            mMainExecutor.runAllReady()

            // Matcher is removed.
            assertThat(gestureRepository.gestureBlockedMatchers.value).isEmpty()

            // Bouncer closes.
            kosmos.sceneInteractor.changeScene(Scenes.Dream, "test")
            kosmos.sceneInteractor.hideOverlay(Overlays.Bouncer, "test")
            mMainExecutor.runAllReady()

            // Matcher is added again.
            assertThat(gestureRepository.gestureBlockedMatchers.value).hasSize(1)
        }

    @Test
    @DisableFlags(FLAG_SCENE_CONTAINER)
    fun testCommunalVisible_setsLifecycleState() =
@@ -1104,7 +1188,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
            transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal)
            mMainExecutor.runAllReady()

            // Lifecycle state goes from resumed back to started when the notification shade shows.
            // Lifecycle state goes from resumed back to started when communal shows.
            assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.STARTED)

            // Communal closes.
@@ -1140,7 +1224,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
            transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal)
            mMainExecutor.runAllReady()

            // Lifecycle state goes from resumed back to started when the notification shade shows.
            // Lifecycle state goes from resumed back to started when the communal shows.
            assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.STARTED)

            // Communal closes.
@@ -1184,7 +1268,8 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
        }

    @Test
    fun testDreamActivityGesturesBlockedWhenDreaming() {
    fun testDreamActivityGesturesBlockedWhenDreaming() =
        kosmos.runTest {
            val client = client

            // Inform the overlay service of dream starting.
@@ -1197,23 +1282,22 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
            )
            mMainExecutor.runAllReady()

        val matcherCaptor = argumentCaptor<TaskMatcher>()
        verify(gestureInteractor)
            .addGestureBlockedMatcher(matcherCaptor.capture(), eq(GestureInteractor.Scope.Global))
        val matcher = matcherCaptor.firstValue
            assertThat(gestureRepository.gestureBlockedMatchers.value).hasSize(1)
            val matcher = gestureRepository.gestureBlockedMatchers.value.first()

        val dreamTaskInfo = TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM)
            val dreamTaskInfo =
                TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM)
            assertThat(matcher.matches(dreamTaskInfo)).isTrue()

            client.endDream()
            mMainExecutor.runAllReady()

        verify(gestureInteractor)
            .removeGestureBlockedMatcher(eq(matcher), eq(GestureInteractor.Scope.Global))
            assertThat(gestureRepository.gestureBlockedMatchers.value).isEmpty()
        }

    @Test
    fun testDreamActivityGesturesNotBlockedWhenPreview() {
    fun testDreamActivityGesturesNotBlockedWhenPreview() =
        kosmos.runTest {
            val client = client

            // Inform the overlay service of dream starting.
@@ -1226,12 +1310,12 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
            )
            mMainExecutor.runAllReady()

        verify(gestureInteractor, never())
            .addGestureBlockedMatcher(any(), eq(GestureInteractor.Scope.Global))
            assertThat(gestureRepository.gestureBlockedMatchers.value).isEmpty()
        }

    @Test
    fun testDreamActivityGesturesNotBlockedWhenNotificationShadeShowing() {
    fun testDreamActivityGesturesNotBlockedWhenNotificationShadeShowing() =
        kosmos.runTest {
            val client = client

            // Inform the overlay service of dream starting.
@@ -1244,12 +1328,11 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
            )
            mMainExecutor.runAllReady()

        val matcherCaptor = argumentCaptor<TaskMatcher>()
        verify(gestureInteractor)
            .addGestureBlockedMatcher(matcherCaptor.capture(), eq(GestureInteractor.Scope.Global))
        val matcher = matcherCaptor.firstValue
            assertThat(gestureRepository.gestureBlockedMatchers.value).hasSize(1)
            val matcher = gestureRepository.gestureBlockedMatchers.value.first()

        val dreamTaskInfo = TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM)
            val dreamTaskInfo =
                TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM)
            assertThat(matcher.matches(dreamTaskInfo)).isTrue()

            val callbackCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
@@ -1259,12 +1342,12 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
            callbackCaptor.lastValue.onShadeExpandedChanged(true)
            mMainExecutor.runAllReady()

        verify(gestureInteractor)
            .removeGestureBlockedMatcher(eq(matcher), eq(GestureInteractor.Scope.Global))
            assertThat(gestureRepository.gestureBlockedMatchers.value).isEmpty()
        }

    @Test
    fun testDreamActivityGesturesNotBlockedDreamEndedBeforeKeyguardStateChanged() {
    fun testDreamActivityGesturesNotBlockedDreamEndedBeforeKeyguardStateChanged() =
        kosmos.runTest {
            val client = client

            // Inform the overlay service of dream starting.
@@ -1277,17 +1360,15 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
            )
            mMainExecutor.runAllReady()

        val matcherCaptor = argumentCaptor<TaskMatcher>()
        verify(gestureInteractor)
            .addGestureBlockedMatcher(matcherCaptor.capture(), eq(GestureInteractor.Scope.Global))
        val matcher = matcherCaptor.firstValue
            assertThat(gestureRepository.gestureBlockedMatchers.value).hasSize(1)
            val matcher = gestureRepository.gestureBlockedMatchers.value.first()

        val dreamTaskInfo = TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM)
            val dreamTaskInfo =
                TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM)
            assertThat(matcher.matches(dreamTaskInfo)).isTrue()

            client.endDream()
            mMainExecutor.runAllReady()
        clearInvocations(gestureInteractor)

            val callbackCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
            verify(mKeyguardUpdateMonitor).registerCallback(callbackCaptor.capture())
@@ -1296,8 +1377,79 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
            callbackCaptor.lastValue.onShadeExpandedChanged(true)
            mMainExecutor.runAllReady()

        verify(gestureInteractor)
            .removeGestureBlockedMatcher(eq(matcher), eq(GestureInteractor.Scope.Global))
            assertThat(gestureRepository.gestureBlockedMatchers.value).isEmpty()
        }

    @EnableFlags(FLAG_DREAM_BIOMETRIC_PROMPT_FIXES)
    @Test
    fun testBiometricPromptShowing_setsLifecycleState() =
        kosmos.runTest {
            val client = client

            // Inform the overlay service of dream starting. Do not show dream complications.
            client.startDream(
                mWindowParams,
                mDreamOverlayCallback,
                DREAM_COMPONENT,
                false /*isPreview*/,
                false, /*shouldShowComplication*/
            )
            mMainExecutor.runAllReady()
            assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.RESUMED)

            // Biometric prompt shows.
            promptRepository.setIsShowing(true)
            mMainExecutor.runAllReady()

            // Lifecycle state goes from resumed back to started when the biometric prompt shows.
            assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.STARTED)

            // Biometric prompt closes.
            promptRepository.setIsShowing(false)
            mMainExecutor.runAllReady()

            // Lifecycle state goes back to RESUMED.
            assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.RESUMED)
        }

    @EnableFlags(FLAG_DREAM_BIOMETRIC_PROMPT_FIXES)
    @Test
    fun testBiometricPromptShowing_stopsGestureBlocking() =
        kosmos.runTest {
            val client = client

            // Inform the overlay service of dream starting. Do not show dream complications.
            client.startDream(
                mWindowParams,
                mDreamOverlayCallback,
                DREAM_COMPONENT,
                false /*isPreview*/,
                false, /*shouldShowComplication*/
            )
            mMainExecutor.runAllReady()

            // GestureBlockedMatcher added when overlay starts.
            assertThat(gestureRepository.gestureBlockedMatchers.value).hasSize(1)
            val matcher = gestureRepository.gestureBlockedMatchers.value.first()

            // Matcher matches dream activity.
            val dreamTaskInfo =
                TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM)
            assertThat(matcher.matches(dreamTaskInfo)).isTrue()

            // Biometric prompt shows.
            promptRepository.setIsShowing(true)
            mMainExecutor.runAllReady()

            // Matcher is removed.
            assertThat(gestureRepository.gestureBlockedMatchers.value).isEmpty()

            // Biometric prompt closes.
            promptRepository.setIsShowing(false)
            mMainExecutor.runAllReady()

            // Matcher is added again.
            assertThat(gestureRepository.gestureBlockedMatchers.value).hasSize(1)
        }

    @Test
+36 −2
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.dreams;
import static android.service.dreams.Flags.dreamWakeRedirect;
import static android.service.dreams.Flags.dreamsV2;

import static com.android.systemui.Flags.dreamBiometricPromptFixes;
import static com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming;
import static com.android.systemui.ambient.touch.TouchSurfaceKt.SURFACE_DREAM;
import static com.android.systemui.ambient.touch.scrim.dagger.ScrimModule.BOUNCER_SCRIM_CONTROLLER;
@@ -60,6 +61,7 @@ import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.ambient.touch.TouchMonitor;
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
import com.android.systemui.ambient.touch.scrim.ScrimController;
import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
import com.android.systemui.communal.shared.log.CommunalUiEvent;
@@ -152,6 +154,14 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
     */
    private boolean mBouncerShowing = false;

    /**
     * True if the biometric prompt is showing.
     *
     * The biometric prompt is a window that shows up on top of an activity that can be used to
     * request authentication for a sensitive action.
     */
    private boolean mBiometricPromptShowing = false;

    private final DreamComplicationComponent.Factory mDreamComplicationComponentFactory;
    private final ComplicationComponent.Factory mComplicationComponentFactory;
    private final DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
@@ -248,6 +258,13 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
        }
    };

    private final Consumer<Boolean> mBiometricPromptShowingConsumer = new Consumer<>() {
        @Override
        public void accept(Boolean showing) {
            mExecutor.execute(() -> updateBiometricPromptShowingLocked(showing));
        }
    };

    /**
     * {@link ResetHandler} protects resetting {@link DreamOverlayService} by making sure reset
     * requests are processed before subsequent actions proceed. Requests themselves are also
@@ -404,6 +421,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
            KeyguardUpdateMonitor keyguardUpdateMonitor,
            @Named(BOUNCER_SCRIM_CONTROLLER) ScrimController bouncerScrimController,
            CommunalInteractor communalInteractor,
            PromptCredentialInteractor promptCredentialInteractor,
            CommunalSettingsInteractor communalSettingsInteractor,
            SceneInteractor sceneInteractor,
            SystemDialogsCloser systemDialogsCloser,
@@ -466,6 +484,10 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
            mFlows.add(collectFlow(getLifecycle(), wakeGestureMonitor.getWakeUpDetected(),
                    mPickupConsumer));
        }
        if (dreamBiometricPromptFixes()) {
            mFlows.add(collectFlow(getLifecycle(), promptCredentialInteractor.isShowing(),
                    mBiometricPromptShowingConsumer));
        }
    }

    @NonNull
@@ -622,7 +644,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ

    private void updateGestureBlockingLocked() {
        final boolean shouldBlock = mStarted && !mShadeExpanded && !mBouncerShowing
                && !isDreamInPreviewMode();
                && !isDreamInPreviewMode() && !mBiometricPromptShowing;

        if (shouldBlock) {
            mGestureInteractor.addGestureBlockedMatcher(DREAM_TYPE_MATCHER,
@@ -648,7 +670,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
        }

        // If anything is on top of the dream, we should stop touch handling.
        boolean shouldPause = mShadeExpanded || mCommunalVisible || mBouncerShowing;
        boolean shouldPause =
                mShadeExpanded || mCommunalVisible || mBouncerShowing || mBiometricPromptShowing;

        setLifecycleStateLocked(
                shouldPause ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED);
@@ -770,4 +793,15 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
        updateLifecycleStateLocked();
        updateGestureBlockingLocked();
    }

    private void updateBiometricPromptShowingLocked(boolean biometricPromptShowing) {
        if (mBiometricPromptShowing == biometricPromptShowing) {
            return;
        }

        mBiometricPromptShowing = biometricPromptShowing;

        updateLifecycleStateLocked();
        updateGestureBlockingLocked();
    }
}