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

Commit 8de5068c authored by Will Leshner's avatar Will Leshner
Browse files

Return to dream on power button press when occluded by glanceable hub.

This change also tracks focus for glanceable hub so the dream knows when
it is occluded.

Bug: 331798001
Test: atest DreamOverlayServiceTest
Test: atest DreamOverlayContainerViewControllerTest
Flag: ACONFIG android.service.dreams.dream_tracks_focus STAGING

Change-Id: I906b4e9d391173d80c114872a987eec578573c36
parent b193131c
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -32,7 +32,9 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.notificationShadeWindowController
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
@@ -68,8 +70,10 @@ class CommunalSceneStartableTest : SysuiTestCase() {
                        keyguardTransitionInteractor = keyguardTransitionInteractor,
                        keyguardInteractor = keyguardInteractor,
                        systemSettings = fakeSettings,
                        notificationShadeWindowController = notificationShadeWindowController,
                        applicationScope = applicationCoroutineScope,
                        bgScope = applicationCoroutineScope,
                        mainDispatcher = testDispatcher,
                    )
                    .apply { start() }

+16 −1
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.dreams;

import static kotlinx.coroutines.flow.FlowKt.emptyFlow;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -28,6 +30,7 @@ import static org.mockito.Mockito.when;
import android.content.res.Resources;
import android.graphics.Region;
import android.os.Handler;
import android.testing.TestableLooper.RunWithLooper;
import android.view.AttachedSurfaceControl;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
@@ -43,6 +46,7 @@ import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
import com.android.systemui.complication.ComplicationHostViewController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.statusbar.BlurUtils;

import org.junit.Before;
@@ -52,8 +56,11 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import kotlinx.coroutines.CoroutineDispatcher;

@SmallTest
@RunWith(AndroidJUnit4.class)
@RunWithLooper(setAsMainLooper = true)
public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
    private static final int MAX_BURN_IN_OFFSET = 20;
    private static final long BURN_IN_PROTECTION_UPDATE_INTERVAL = 10;
@@ -86,6 +93,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
    @Mock
    Handler mHandler;

    @Mock
    CoroutineDispatcher mDispatcher;

    @Mock
    BlurUtils mBlurUtils;

@@ -103,6 +113,8 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {

    @Mock
    DreamOverlayStateController mStateController;
    @Mock
    KeyguardTransitionInteractor mKeyguardTransitionInteractor;

    DreamOverlayContainerViewController mController;

@@ -115,6 +127,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
        when(mDreamOverlayContainerView.getViewRootImpl()).thenReturn(mViewRoot);
        when(mDreamOverlayContainerView.getRootSurfaceControl())
                .thenReturn(mAttachedSurfaceControl);
        when(mKeyguardTransitionInteractor.isFinishedInStateWhere(any())).thenReturn(emptyFlow());

        mController = new DreamOverlayContainerViewController(
                mDreamOverlayContainerView,
@@ -124,6 +137,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
                mLowLightTransitionCoordinator,
                mBlurUtils,
                mHandler,
                mDispatcher,
                mResources,
                MAX_BURN_IN_OFFSET,
                BURN_IN_PROTECTION_UPDATE_INTERVAL,
@@ -131,7 +145,8 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
                mPrimaryBouncerCallbackInteractor,
                mAnimationsController,
                mStateController,
                mBouncerlessScrimController);
                mBouncerlessScrimController,
                mKeyguardTransitionInteractor);
    }

    @Test
+76 −0
Original line number Diff line number Diff line
@@ -35,6 +35,10 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
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.ambient.touch.scrim.ScrimManager
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.complication.ComplicationHostViewController
import com.android.systemui.complication.ComplicationLayoutEngine
import com.android.systemui.complication.dagger.ComplicationComponent
@@ -43,6 +47,8 @@ import com.android.systemui.dreams.dagger.DreamOverlayComponent
import com.android.systemui.touch.TouchInsetManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth
@@ -114,6 +120,14 @@ class DreamOverlayServiceTest : SysuiTestCase() {

    @Mock lateinit var mUiEventLogger: UiEventLogger

    @Mock lateinit var mScrimManager: ScrimManager

    @Mock lateinit var mScrimController: ScrimController

    @Mock lateinit var mCommunalInteractor: CommunalInteractor

    @Mock lateinit var mSystemDialogsCloser: SystemDialogsCloser

    @Mock lateinit var mDreamOverlayCallbackController: DreamOverlayCallbackController

    @Captor var mViewCaptor: ArgumentCaptor<View>? = null
@@ -141,6 +155,7 @@ class DreamOverlayServiceTest : SysuiTestCase() {
        whenever(mAmbientTouchComponent.getTouchMonitor()).thenReturn(mTouchMonitor)
        whenever(mDreamOverlayContainerViewController.containerView)
            .thenReturn(mDreamOverlayContainerView)
        whenever(mScrimManager.getCurrentController()).thenReturn(mScrimController)
        mWindowParams = WindowManager.LayoutParams()
        mService =
            DreamOverlayService(
@@ -154,6 +169,9 @@ class DreamOverlayServiceTest : SysuiTestCase() {
                mAmbientTouchComponentFactory,
                mStateController,
                mKeyguardUpdateMonitor,
                mScrimManager,
                mCommunalInteractor,
                mSystemDialogsCloser,
                mUiEventLogger,
                mTouchInsetManager,
                LOW_LIGHT_COMPONENT,
@@ -563,6 +581,64 @@ class DreamOverlayServiceTest : SysuiTestCase() {
            .isTrue()
    }

    // Tests that the bouncer closes when DreamOverlayService is told that the dream is coming to
    // the front.
    @Test
    fun testBouncerRetractedWhenDreamComesToFront() {
        val client = client

        // Inform the overlay service of dream starting.
        client.startDream(
            mWindowParams,
            mDreamOverlayCallback,
            DREAM_COMPONENT,
            true /*shouldShowComplication*/
        )
        mMainExecutor.runAllReady()

        whenever(mDreamOverlayContainerViewController.isBouncerShowing()).thenReturn(true)
        mService!!.onComeToFront()
        Mockito.verify(mScrimController).expand(any())
    }

    // Tests that glanceable hub is hidden when DreamOverlayService is told that the dream is
    // coming to the front.
    @Test
    fun testGlanceableHubHiddenWhenDreamComesToFront() {
        val client = client

        // Inform the overlay service of dream starting.
        client.startDream(
            mWindowParams,
            mDreamOverlayCallback,
            DREAM_COMPONENT,
            true /*shouldShowComplication*/
        )
        mMainExecutor.runAllReady()

        mService!!.onComeToFront()
        Mockito.verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Blank), nullable())
    }

    // Tests that system dialogs (e.g. notification shade) closes when DreamOverlayService is told
    // that the dream is coming to the front.
    @Test
    fun testSystemDialogsClosedWhenDreamComesToFront() {
        val client = client

        // Inform the overlay service of dream starting.
        client.startDream(
            mWindowParams,
            mDreamOverlayCallback,
            DREAM_COMPONENT,
            true /*shouldShowComplication*/
        )
        mMainExecutor.runAllReady()

        mService!!.onComeToFront()
        Mockito.verify(mSystemDialogsCloser).closeSystemDialogs()
    }

    companion object {
        private val LOW_LIGHT_COMPONENT = ComponentName("package", "lowlight")
        private val HOME_CONTROL_PANEL_DREAM_COMPONENT =
+18 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.communal

import android.provider.Settings
import android.service.dreams.Flags.dreamTracksFocus
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.CoreStartable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -25,11 +26,13 @@ import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dock.DockManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.util.kotlin.emitOnStart
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -37,15 +40,18 @@ import com.android.systemui.util.settings.SystemSettings
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * A [CoreStartable] responsible for automatically navigating between communal scenes when certain
@@ -61,8 +67,10 @@ constructor(
    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
    private val keyguardInteractor: KeyguardInteractor,
    private val systemSettings: SystemSettings,
    private val notificationShadeWindowController: NotificationShadeWindowController,
    @Application private val applicationScope: CoroutineScope,
    @Background private val bgScope: CoroutineScope,
    @Main private val mainDispatcher: CoroutineDispatcher,
) : CoreStartable {
    private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT

@@ -134,6 +142,16 @@ constructor(
                    }
                }
        }

        if (dreamTracksFocus()) {
            bgScope.launch {
                communalInteractor.isIdleOnCommunal.collectLatest {
                    withContext(mainDispatcher) {
                        notificationShadeWindowController.setGlanceableHubShowing(it)
                    }
                }
            }
        }
    }

    private suspend fun determineSceneAfterTransition(
+24 −1
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamYPosi
import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM;
import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;

import android.animation.Animator;
import android.content.res.Resources;
@@ -40,6 +41,8 @@ import com.android.systemui.complication.ComplicationHostViewController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.dagger.DreamOverlayModule;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.statusbar.BlurUtils;
@@ -50,6 +53,8 @@ import java.util.Arrays;
import javax.inject.Inject;
import javax.inject.Named;

import kotlinx.coroutines.CoroutineDispatcher;

/**
 * View controller for {@link DreamOverlayContainerView}.
 */
@@ -62,6 +67,7 @@ public class DreamOverlayContainerViewController extends
    private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
    private final DreamOverlayStateController mStateController;
    private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;

    private final ComplicationHostViewController mComplicationHostViewController;

@@ -81,6 +87,7 @@ public class DreamOverlayContainerViewController extends

    // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
    private final Handler mHandler;
    private final CoroutineDispatcher mMainDispatcher;
    private final int mDreamOverlayMaxTranslationY;
    private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;

@@ -88,6 +95,7 @@ public class DreamOverlayContainerViewController extends

    private boolean mBouncerAnimating;
    private boolean mWakingUpFromSwipe;
    private boolean mAnyBouncerShowing;

    private final BouncerlessScrimController mBouncerlessScrimController;

@@ -170,6 +178,7 @@ public class DreamOverlayContainerViewController extends
            LowLightTransitionCoordinator lowLightTransitionCoordinator,
            BlurUtils blurUtils,
            @Main Handler handler,
            @Main CoroutineDispatcher mainDispatcher,
            @Main Resources resources,
            @Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset,
            @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
@@ -178,7 +187,8 @@ public class DreamOverlayContainerViewController extends
            PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
            DreamOverlayAnimationsController animationsController,
            DreamOverlayStateController stateController,
            BouncerlessScrimController bouncerlessScrimController) {
            BouncerlessScrimController bouncerlessScrimController,
            KeyguardTransitionInteractor keyguardTransitionInteractor) {
        super(containerView);
        mDreamOverlayContentView = contentView;
        mStatusBarViewController = statusBarViewController;
@@ -190,6 +200,8 @@ public class DreamOverlayContainerViewController extends
        mBouncerlessScrimController = bouncerlessScrimController;
        mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);

        mKeyguardTransitionInteractor = keyguardTransitionInteractor;

        mComplicationHostViewController = complicationHostViewController;
        mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
                R.dimen.dream_overlay_y_offset);
@@ -200,6 +212,7 @@ public class DreamOverlayContainerViewController extends
                        ViewGroup.LayoutParams.MATCH_PARENT));

        mHandler = handler;
        mMainDispatcher = mainDispatcher;
        mMaxBurnInOffset = maxBurnInOffset;
        mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
        mMillisUntilFullJitter = millisUntilFullJitter;
@@ -225,6 +238,12 @@ public class DreamOverlayContainerViewController extends
        mView.getRootSurfaceControl().setTouchableRegion(emptyRegion);
        emptyRegion.recycle();

        collectFlow(
                mView,
                mKeyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState),
                isFinished -> mAnyBouncerShowing = isFinished,
                mMainDispatcher);

        // Start dream entry animations. Skip animations for low light clock.
        if (!mStateController.isLowLightActive()) {
            // If this is transitioning from the low light dream to the user dream, the overlay
@@ -246,6 +265,10 @@ public class DreamOverlayContainerViewController extends
        return mView;
    }

    boolean isBouncerShowing() {
        return mAnyBouncerShowing;
    }

    private void updateBurnInOffsets() {
        // Make sure the offset starts at zero, to avoid a big jump in the overlay when it first
        // appears.
Loading