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
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 76f15d2..b4b812d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -32,7 +32,9 @@
 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 @@
                         keyguardTransitionInteractor = keyguardTransitionInteractor,
                         keyguardInteractor = keyguardInteractor,
                         systemSettings = fakeSettings,
+                        notificationShadeWindowController = notificationShadeWindowController,
                         applicationScope = applicationCoroutineScope,
                         bgScope = applicationCoroutineScope,
+                        mainDispatcher = testDispatcher,
                     )
                     .apply { start() }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 41bc1dc..e2e5169 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -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 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.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.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;
@@ -87,6 +94,9 @@
     Handler mHandler;
 
     @Mock
+    CoroutineDispatcher mDispatcher;
+
+    @Mock
     BlurUtils mBlurUtils;
 
     @Mock
@@ -103,6 +113,8 @@
 
     @Mock
     DreamOverlayStateController mStateController;
+    @Mock
+    KeyguardTransitionInteractor mKeyguardTransitionInteractor;
 
     DreamOverlayContainerViewController mController;
 
@@ -115,6 +127,7 @@
         when(mDreamOverlayContainerView.getViewRootImpl()).thenReturn(mViewRoot);
         when(mDreamOverlayContainerView.getRootSurfaceControl())
                 .thenReturn(mAttachedSurfaceControl);
+        when(mKeyguardTransitionInteractor.isFinishedInStateWhere(any())).thenReturn(emptyFlow());
 
         mController = new DreamOverlayContainerViewController(
                 mDreamOverlayContainerView,
@@ -124,6 +137,7 @@
                 mLowLightTransitionCoordinator,
                 mBlurUtils,
                 mHandler,
+                mDispatcher,
                 mResources,
                 MAX_BURN_IN_OFFSET,
                 BURN_IN_PROTECTION_UPDATE_INTERVAL,
@@ -131,7 +145,8 @@
                 mPrimaryBouncerCallbackInteractor,
                 mAnimationsController,
                 mStateController,
-                mBouncerlessScrimController);
+                mBouncerlessScrimController,
+                mKeyguardTransitionInteractor);
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index b18a8ec..6a8ab39 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -35,6 +35,10 @@
 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.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 @@
 
     @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 @@
         whenever(mAmbientTouchComponent.getTouchMonitor()).thenReturn(mTouchMonitor)
         whenever(mDreamOverlayContainerViewController.containerView)
             .thenReturn(mDreamOverlayContainerView)
+        whenever(mScrimManager.getCurrentController()).thenReturn(mScrimController)
         mWindowParams = WindowManager.LayoutParams()
         mService =
             DreamOverlayService(
@@ -154,6 +169,9 @@
                 mAmbientTouchComponentFactory,
                 mStateController,
                 mKeyguardUpdateMonitor,
+                mScrimManager,
+                mCommunalInteractor,
+                mSystemDialogsCloser,
                 mUiEventLogger,
                 mTouchInsetManager,
                 LOW_LIGHT_COMPONENT,
@@ -563,6 +581,64 @@
             .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 =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index 5a174b9..f437032 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -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.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 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 @@
     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 @@
                     }
                 }
         }
+
+        if (dreamTracksFocus()) {
+            bgScope.launch {
+                communalInteractor.isIdleOnCommunal.collectLatest {
+                    withContext(mainDispatcher) {
+                        notificationShadeWindowController.setGlanceableHubShowing(it)
+                    }
+                }
+            }
+        }
     }
 
     private suspend fun determineSceneAfterTransition(
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 5a036b1..0c2709e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -22,6 +22,7 @@
 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.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 javax.inject.Inject;
 import javax.inject.Named;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+
 /**
  * View controller for {@link DreamOverlayContainerView}.
  */
@@ -62,6 +67,7 @@
     private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
     private final DreamOverlayStateController mStateController;
     private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
+    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
 
     private final ComplicationHostViewController mComplicationHostViewController;
 
@@ -81,6 +87,7 @@
 
     // 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 @@
 
     private boolean mBouncerAnimating;
     private boolean mWakingUpFromSwipe;
+    private boolean mAnyBouncerShowing;
 
     private final BouncerlessScrimController mBouncerlessScrimController;
 
@@ -170,6 +178,7 @@
             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 @@
             PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
             DreamOverlayAnimationsController animationsController,
             DreamOverlayStateController stateController,
-            BouncerlessScrimController bouncerlessScrimController) {
+            BouncerlessScrimController bouncerlessScrimController,
+            KeyguardTransitionInteractor keyguardTransitionInteractor) {
         super(containerView);
         mDreamOverlayContentView = contentView;
         mStatusBarViewController = statusBarViewController;
@@ -190,6 +200,8 @@
         mBouncerlessScrimController = bouncerlessScrimController;
         mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
 
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+
         mComplicationHostViewController = complicationHostViewController;
         mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
                 R.dimen.dream_overlay_y_offset);
@@ -200,6 +212,7 @@
                         ViewGroup.LayoutParams.MATCH_PARENT));
 
         mHandler = handler;
+        mMainDispatcher = mainDispatcher;
         mMaxBurnInOffset = maxBurnInOffset;
         mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
         mMillisUntilFullJitter = millisUntilFullJitter;
@@ -225,6 +238,12 @@
         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 @@
         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.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 1135afe..3d52bcd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -45,10 +45,14 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.ambient.touch.TouchMonitor;
 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
+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.Complication;
 import com.android.systemui.complication.dagger.ComplicationComponent;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -76,6 +80,7 @@
     private DreamOverlayContainerViewController mDreamOverlayContainerViewController;
     private final DreamOverlayCallbackController mDreamOverlayCallbackController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final ScrimManager mScrimManager;
     @Nullable
     private final ComponentName mLowLightDreamComponent;
     @Nullable
@@ -107,6 +112,10 @@
 
     private TouchMonitor mTouchMonitor;
 
+    private final CommunalInteractor mCommunalInteractor;
+
+    private final SystemDialogsCloser mSystemDialogsCloser;
+
     private final KeyguardUpdateMonitorCallback mKeyguardCallback =
             new KeyguardUpdateMonitorCallback() {
                 @Override
@@ -168,6 +177,9 @@
             AmbientTouchComponent.Factory ambientTouchComponentFactory,
             DreamOverlayStateController stateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
+            ScrimManager scrimManager,
+            CommunalInteractor communalInteractor,
+            SystemDialogsCloser systemDialogsCloser,
             UiEventLogger uiEventLogger,
             @Named(DREAM_TOUCH_INSET_MANAGER) TouchInsetManager touchInsetManager,
             @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
@@ -181,6 +193,7 @@
         mExecutor = executor;
         mWindowManager = windowManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mScrimManager = scrimManager;
         mLowLightDreamComponent = lowLightDreamComponent;
         mHomeControlPanelDreamComponent = homeControlPanelDreamComponent;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
@@ -188,6 +201,8 @@
         mUiEventLogger = uiEventLogger;
         mDreamOverlayCallbackController = dreamOverlayCallbackController;
         mWindowTitle = windowTitle;
+        mCommunalInteractor = communalInteractor;
+        mSystemDialogsCloser = systemDialogsCloser;
 
         final ViewModelStore viewModelStore = new ViewModelStore();
         final Complication.Host host =
@@ -292,6 +307,28 @@
         }
     }
 
+    @Override
+    public void onComeToFront() {
+        // Make sure the bouncer is closed. Expanding the shade effectively contracts the bouncer
+        // an equal amount.
+        if (mDreamOverlayContainerViewController != null
+                && mDreamOverlayContainerViewController.isBouncerShowing()) {
+            mScrimManager.getCurrentController().expand(
+                    new ShadeExpansionChangeEvent(
+                            /* fraction= */ 1.f,
+                            /* expanded= */ false,
+                            /* tracking= */ true));
+        }
+
+        // closeSystemDialogs takes care of closing anything that responds to the
+        // {@link Intent.ACTION_CLOSE_SYSTEM_DIALOGS} broadcast (which includes the notification
+        // shade).
+        mSystemDialogsCloser.closeSystemDialogs();
+
+        // Hide glanceable hub (this is a nop if glanceable hub is not open).
+        mCommunalInteractor.changeScene(CommunalScenes.Blank, null);
+    }
+
     /**
      * Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
      * called from the main executing thread. The window attributes closely mirror those that are
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SystemDialogsCloser.java b/packages/SystemUI/src/com/android/systemui/dreams/SystemDialogsCloser.java
new file mode 100644
index 0000000..6e7239a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/SystemDialogsCloser.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+/** Defines an interface for a class that is responsible for closing system dialogs. */
+public interface SystemDialogsCloser {
+    /** Close any open system dialogs. */
+    void closeSystemDialogs();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 31710ac..516b8c5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -31,6 +31,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayNotificationCountProvider;
 import com.android.systemui.dreams.DreamOverlayService;
+import com.android.systemui.dreams.SystemDialogsCloser;
 import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
 import com.android.systemui.dreams.homecontrols.DreamActivityProvider;
 import com.android.systemui.dreams.homecontrols.DreamActivityProviderImpl;
@@ -146,6 +147,15 @@
         return Optional.empty();
     }
 
+    /**
+     * Provides an implementation for {@link SystemDialogsCloser} that calls
+     * {@link Context.closeSystemDialogs}.
+     */
+    @Provides
+    static SystemDialogsCloser providesSystemDialogsCloser(Context context) {
+        return () -> context.closeSystemDialogs();
+    }
+
     /** */
     @Provides
     @Named(DREAM_ONLY_ENABLED_FOR_DOCK_USER)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 92612b8..7d05539 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -87,6 +87,7 @@
         }
 
         /** Whether either of the bouncers are visible when we're FINISHED in the given state. */
+        @JvmStatic
         fun isBouncerState(state: KeyguardState): Boolean {
             return state == PRIMARY_BOUNCER || state == ALTERNATE_BOUNCER
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index adcb14a..8b7e11c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -448,6 +448,9 @@
                 || mScreenOffAnimationController.shouldIgnoreKeyguardTouches()) {
             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
             mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+        } else if (state.glanceableHubShowing) {
+            mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
+            mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
         } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
             // Make sure to remove FLAG_ALT_FOCUSABLE_IM when keyguard needs input.
@@ -611,6 +614,7 @@
                 state.panelVisible,
                 state.shadeOrQsExpanded,
                 state.notificationShadeFocusable,
+                state.glanceableHubShowing,
                 state.bouncerShowing,
                 state.keyguardFadingAway,
                 state.keyguardGoingAway,
@@ -740,6 +744,12 @@
     }
 
     @Override
+    public void setGlanceableHubShowing(boolean showing) {
+        mCurrentState.glanceableHubShowing = showing;
+        apply(mCurrentState);
+    }
+
+    @Override
     public void setBackdropShowing(boolean showing) {
         mCurrentState.mediaBackdropShowing = showing;
         apply(mCurrentState);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index e0a98b3..6a4b52a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -35,6 +35,7 @@
     @JvmField var shadeOrQsExpanded: Boolean = false,
     @JvmField var notificationShadeFocusable: Boolean = false,
     @JvmField var bouncerShowing: Boolean = false,
+    @JvmField var glanceableHubShowing: Boolean = false,
     @JvmField var keyguardFadingAway: Boolean = false,
     @JvmField var keyguardGoingAway: Boolean = false,
     @JvmField var qsExpanded: Boolean = false,
@@ -79,6 +80,7 @@
             shadeOrQsExpanded.toString(),
             notificationShadeFocusable.toString(),
             bouncerShowing.toString(),
+            glanceableHubShowing.toString(),
             keyguardFadingAway.toString(),
             keyguardGoingAway.toString(),
             qsExpanded.toString(),
@@ -119,6 +121,7 @@
             panelVisible: Boolean,
             panelExpanded: Boolean,
             notificationShadeFocusable: Boolean,
+            glanceableHubShowing: Boolean,
             bouncerShowing: Boolean,
             keyguardFadingAway: Boolean,
             keyguardGoingAway: Boolean,
@@ -149,6 +152,7 @@
                 this.panelVisible = panelVisible
                 this.shadeOrQsExpanded = panelExpanded
                 this.notificationShadeFocusable = notificationShadeFocusable
+                this.glanceableHubShowing = glanceableHubShowing
                 this.bouncerShowing = bouncerShowing
                 this.keyguardFadingAway = keyguardFadingAway
                 this.keyguardGoingAway = keyguardGoingAway
@@ -197,6 +201,7 @@
                 "panelVisible",
                 "panelExpanded",
                 "notificationShadeFocusable",
+                "glanceableHubShowing",
                 "bouncerShowing",
                 "keyguardFadingAway",
                 "keyguardGoingAway",
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index e669556..707d59aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -86,6 +86,9 @@
     /** Sets the state of whether the bouncer is showing or not. */
     default void setBouncerShowing(boolean showing) {}
 
+    /** Sets the state of whether the glanceable hub is showing or not. */
+    default void setGlanceableHubShowing(boolean showing) {}
+
     /** Sets the state of whether the backdrop is showing or not. */
     default void setBackdropShowing(boolean showing) {}