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

Commit cb313ad9 authored by Josh Tsuji's avatar Josh Tsuji
Browse files

Overhaul keyguard occlusion and camera launch transitions.

This CL introduces the KeyguardOcclusionRepository, which keeps track of
a single piece of state - whether there is a SHOW_WHEN_LOCKED activity
on top of the task stack, regardless of whether we're locked or not.
This is the information we actually receive from WM (either via
RemoteAnimation start/cancel calls, or from
KeyguardService#setOccluded).

This state, fused with PowerInteractor's camera launch gesture
information, is all we need to correctly start any occlusion/camera
launch transition:

1. On waking from isAsleep(currentState):
  a. If there's a SHOW_WHEN_LOCKED activity on top (Maps Navigation,
     etc.), transition to OCCLUDED.
  b. If the camera gesture was triggered, transition to OCCLUDED unless
     we were just GONE, or the keyguard is dismissable, in which case
     transition to GONE. The first power button press *always* puts us
     to sleep, so we are guaranteed to be waking whenever the second tap
     comes in and the gesture is triggered.
2. When a SHOW_WHEN_LOCKED activity is launched (timer goes off, phone
   call arrives, etc.):
  a. If we're GONE, do nothing. If this activity remains on top by the
     next time we're locked, we'll transition to OCCLUDED via (1a).
  b. Otherwise, transition to OCCLUDED.
3. When a SHOW_WHEN_LOCKED activity is no longer on top:
  a. If we're OCCLUDED, transition to LOCKSCREEN.

To avoid an already too-large CL getting even larger, I've filed follow
up bugs to move the remote animation state into ViewModels. We'll also
need to continue moving StatusBarKeyguardViewManager calls into
interactors that collect KeyguardTransitionInteractor flows, so we can
delete StatusBarKeyguardViewManagerInteractor.

Bug: 307976454
Flag: ACONFIG com.android.systemui.keyguard_wm_state_refactor DEVELOPMENT
Flag: ACONFIG com.android.systemui.migrate_clocks_to_blueprint TEAMFOOD
Test: atest SystemUITests
Change-Id: Ib469f85881dc64d31b45bb0a5c5c3e64fc210570
parent 8e28c69b
Loading
Loading
Loading
Loading
+12 −28
Original line number Diff line number Diff line
@@ -45,32 +45,32 @@ import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayView
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -102,6 +102,7 @@ private const val SENSOR_HEIGHT = 60
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
class UdfpsControllerOverlayTest : SysuiTestCase() {
    private val kosmos = testKosmos()

    @JvmField @Rule var rule = MockitoJUnit.rule()

@@ -148,28 +149,11 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {

    @Before
    fun setup() {
        testScope = TestScope(StandardTestDispatcher())
        powerRepository = FakePowerRepository()
        powerInteractor =
            PowerInteractor(
                powerRepository,
                mock(FalsingCollector::class.java),
                mock(ScreenOffAnimationController::class.java),
                statusBarStateController,
            )
        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
        keyguardTransitionInteractor =
            KeyguardTransitionInteractor(
                scope = testScope.backgroundScope,
                repository = keyguardTransitionRepository,
                fromLockscreenTransitionInteractor = {
                    mock(FromLockscreenTransitionInteractor::class.java)
                },
                fromPrimaryBouncerTransitionInteractor = {
                    mock(FromPrimaryBouncerTransitionInteractor::class.java)
                },
                fromAodTransitionInteractor = { mock(FromAodTransitionInteractor::class.java) },
            )
        testScope = kosmos.testScope
        powerRepository = kosmos.fakePowerRepository
        powerInteractor = kosmos.powerInteractor
        keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
        keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
        whenever(inflater.inflate(R.layout.udfps_view, null, false)).thenReturn(udfpsView)
        whenever(inflater.inflate(R.layout.udfps_bp_view, null))
            .thenReturn(mock(UdfpsBpView::class.java))
+23 −19
Original line number Diff line number Diff line
@@ -25,18 +25,17 @@ import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -49,9 +48,10 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class LightRevealScrimRepositoryTest : SysuiTestCase() {
    private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
    private lateinit var powerRepository: FakePowerRepository
    private lateinit var powerInteractor: PowerInteractor
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
    private val powerInteractor = kosmos.powerInteractor
    private lateinit var underTest: LightRevealScrimRepositoryImpl

    @get:Rule val animatorTestRule = AnimatorTestRule(this)
@@ -59,13 +59,13 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        fakeKeyguardRepository = FakeKeyguardRepository()
        powerRepository = FakePowerRepository()
        powerInteractor =
            PowerInteractorFactory.create(repository = powerRepository).powerInteractor

        underTest =
            LightRevealScrimRepositoryImpl(fakeKeyguardRepository, context, powerInteractor, mock())
            LightRevealScrimRepositoryImpl(
                kosmos.fakeKeyguardRepository,
                context,
                kosmos.powerInteractor,
                mock()
            )
    }

    @Test
@@ -168,8 +168,9 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
    @Test
    @TestableLooper.RunWithLooper(setAsMainLooper = true)
    fun revealAmount_emitsTo1AfterAnimationStarted() =
        runTest(UnconfinedTestDispatcher()) {
        testScope.runTest {
            val value by collectLastValue(underTest.revealAmount)
            runCurrent()
            underTest.startRevealAmountAnimator(true)
            assertEquals(0.0f, value)
            animatorTestRule.advanceTimeBy(500L)
@@ -179,8 +180,9 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
    @Test
    @TestableLooper.RunWithLooper(setAsMainLooper = true)
    fun revealAmount_startingRevealTwiceWontRerunAnimator() =
        runTest(UnconfinedTestDispatcher()) {
        testScope.runTest {
            val value by collectLastValue(underTest.revealAmount)
            runCurrent()
            underTest.startRevealAmountAnimator(true)
            assertEquals(0.0f, value)
            animatorTestRule.advanceTimeBy(250L)
@@ -193,12 +195,14 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
    @Test
    @TestableLooper.RunWithLooper(setAsMainLooper = true)
    fun revealAmount_emitsTo0AfterAnimationStartedReversed() =
        runTest(UnconfinedTestDispatcher()) {
            val value by collectLastValue(underTest.revealAmount)
        testScope.runTest {
            val lastValue by collectLastValue(underTest.revealAmount)
            runCurrent()
            underTest.startRevealAmountAnimator(true)
            animatorTestRule.advanceTimeBy(500L)
            underTest.startRevealAmountAnimator(false)
            assertEquals(1.0f, value)
            animatorTestRule.advanceTimeBy(500L)
            assertEquals(0.0f, value)
            assertEquals(0.0f, lastValue)
        }

    /**
+5 −0
Original line number Diff line number Diff line
@@ -18,9 +18,11 @@
package com.android.systemui.keyguard.domain.interactor

import android.app.StatusBarManager
import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
@@ -120,6 +122,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
    fun testKeyguardGuardVisibilityStopsSecureCamera() =
        testScope.runTest {
            val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
@@ -144,6 +147,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
    fun testBouncerShowingResetsSecureCameraState() =
        testScope.runTest {
            val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
@@ -166,6 +170,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
    fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest {
        val isVisible = collectLastValue(underTest.isKeyguardVisible)
        repository.setKeyguardShowing(true)
+18 −9
Original line number Diff line number Diff line
@@ -24,9 +24,12 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.model.BiometricMessage
import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -60,8 +63,12 @@ constructor(
    private val context: Context,
    activityStarter: ActivityStarter,
    powerInteractor: PowerInteractor,
    keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
    private val keyguardOccludedByApp: Flow<Boolean> =
        if (KeyguardWmStateRefactor.isEnabled) {
            keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.OCCLUDED }
        } else {
            combine(
                    keyguardInteractor.isKeyguardOccluded,
                    keyguardInteractor.isKeyguardShowing,
@@ -71,6 +78,8 @@ constructor(
                    occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible
                }
                .distinctUntilChanged()
        }

    private val fingerprintUnlockSuccessEvents: Flow<Unit> =
        fingerprintAuthRepository.authenticationStatus
            .ifKeyguardOccludedByApp()
+11 −2
Original line number Diff line number Diff line
@@ -289,6 +289,8 @@ public class KeyguardService extends Service {
        };
    }

    private final WindowManagerOcclusionManager mWmOcclusionManager;

    @Inject
    public KeyguardService(
            KeyguardViewMediator keyguardViewMediator,
@@ -302,7 +304,8 @@ public class KeyguardService extends Service {
            KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
            @Application CoroutineScope scope,
            FeatureFlags featureFlags,
            PowerInteractor powerInteractor) {
            PowerInteractor powerInteractor,
            WindowManagerOcclusionManager windowManagerOcclusionManager) {
        super();
        mKeyguardViewMediator = keyguardViewMediator;
        mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -323,6 +326,8 @@ public class KeyguardService extends Service {
                    keyguardSurfaceBehindAnimator,
                    scope);
        }

        mWmOcclusionManager = windowManagerOcclusionManager;
    }

    @Override
@@ -414,7 +419,11 @@ public class KeyguardService extends Service {

            Trace.beginSection("KeyguardService.mBinder#setOccluded");
            checkPermission();
            if (!KeyguardWmStateRefactor.isEnabled()) {
                mKeyguardViewMediator.setOccluded(isOccluded, animate);
            } else {
                mWmOcclusionManager.onKeyguardServiceSetOccluded(isOccluded);
            }
            Trace.endSection();
        }

Loading