diff options
21 files changed, 370 insertions, 4 deletions
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java index 9a02b74b37d0..5da0cb48ad1f 100644 --- a/core/java/android/service/dreams/DreamOverlayService.java +++ b/core/java/android/service/dreams/DreamOverlayService.java @@ -79,6 +79,11 @@ public abstract class DreamOverlayService extends Service { mService.endDream(this); } + @Override + public void comeToFront() { + mService.comeToFront(this); + } + private void onExitRequested() { try { mDreamOverlayCallback.onExitRequested(); @@ -130,6 +135,16 @@ public abstract class DreamOverlayService extends Service { }); } + private void comeToFront(OverlayClient client) { + mExecutor.execute(() -> { + if (mCurrentClient != client) { + return; + } + + onComeToFront(); + }); + } + private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() { @Override public void getClient(IDreamOverlayClientCallback callback) { @@ -190,6 +205,13 @@ public abstract class DreamOverlayService extends Service { public void onWakeUp() {} /** + * This method is overridden by implementations to handle when the dream is coming to the front + * (after having lost focus to something on top of it). + * @hide + */ + public void onComeToFront() {} + + /** * This method is overridden by implementations to handle when the dream has ended. There may * be earlier signals leading up to this step, such as @{@link #onWakeUp(Runnable)}. * diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index f89644729820..c26d83c4853d 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -18,6 +18,7 @@ package android.service.dreams; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.service.dreams.Flags.dreamHandlesConfirmKeys; +import static android.service.dreams.Flags.dreamTracksFocus; import android.annotation.FlaggedApi; import android.annotation.IdRes; @@ -457,6 +458,15 @@ public class DreamService extends Service implements Window.Callback { /** {@inheritDoc} */ @Override public void onWindowFocusChanged(boolean hasFocus) { + if (!dreamTracksFocus()) { + return; + } + + try { + mDreamManager.onDreamFocusChanged(hasFocus); + } catch (RemoteException ex) { + // system server died + } } /** {@inheritDoc} */ @@ -1152,6 +1162,19 @@ public class DreamService extends Service implements Window.Callback { wakeUp(false); } + /** + * Tells the dream to come to the front (which in turn tells the overlay to come to the front). + */ + private void comeToFront() { + mOverlayConnection.addConsumer(overlay -> { + try { + overlay.comeToFront(); + } catch (RemoteException e) { + Log.e(mTag, "could not tell overlay to come to front:" + e); + } + }); + } + private void wakeUp(boolean fromSystem) { if (mDebug) { Slog.v(mTag, "wakeUp(): fromSystem=" + fromSystem + ", mWaking=" + mWaking @@ -1608,6 +1631,15 @@ public class DreamService extends Service implements Window.Callback { public void wakeUp() { mHandler.post(() -> DreamService.this.wakeUp(true /*fromSystem*/)); } + + @Override + public void comeToFront() { + if (!dreamTracksFocus()) { + return; + } + + mHandler.post(DreamService.this::comeToFront); + } } /** @hide */ diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index e45384f5512a..85f0368a7b5c 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -48,4 +48,5 @@ interface IDreamManager { void setSystemDreamComponent(in ComponentName componentName); void registerDreamOverlayService(in ComponentName componentName); void startDreamActivity(in Intent intent); + void onDreamFocusChanged(in boolean hasFocus); } diff --git a/core/java/android/service/dreams/IDreamOverlayClient.aidl b/core/java/android/service/dreams/IDreamOverlayClient.aidl index 78b7280ae652..5054d4d749d3 100644 --- a/core/java/android/service/dreams/IDreamOverlayClient.aidl +++ b/core/java/android/service/dreams/IDreamOverlayClient.aidl @@ -42,4 +42,7 @@ interface IDreamOverlayClient { /** Called when the dream has ended. */ void endDream(); + + /** Called when the dream is coming to the front. */ + void comeToFront(); } diff --git a/core/java/android/service/dreams/IDreamService.aidl b/core/java/android/service/dreams/IDreamService.aidl index 8b5d8754647c..2e2651bfb91d 100644 --- a/core/java/android/service/dreams/IDreamService.aidl +++ b/core/java/android/service/dreams/IDreamService.aidl @@ -25,4 +25,5 @@ oneway interface IDreamService { void attach(IBinder windowToken, boolean canDoze, boolean isPreviewMode, IRemoteCallback started); void detach(); void wakeUp(); + void comeToFront(); } diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig index 88f1090750d2..0a458bccc83c 100644 --- a/core/java/android/service/dreams/flags.aconfig +++ b/core/java/android/service/dreams/flags.aconfig @@ -19,3 +19,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "dream_tracks_focus" + namespace: "communal" + description: "This flag enables the ability for dreams to track whether or not they have focus" + bug: "331798001" +} 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 76f15d2ed257..b4b812d60a1a 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.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() } 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 41bc1dc271f1..e2e5169db029 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 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; @@ -87,6 +94,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { Handler mHandler; @Mock + CoroutineDispatcher mDispatcher; + + @Mock BlurUtils mBlurUtils; @Mock @@ -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 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 b18a8ecee946..6a8ab39c997d 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.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 = diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index 5a174b9d2f80..f437032d0ddb 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.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( diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 5a036b17a35b..0c2709e4afed 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.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. diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 1135afeb6f83..3d52bcd02ada 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.KeyguardUpdateMonitor; 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 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ 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 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ private TouchMonitor mTouchMonitor; + private final CommunalInteractor mCommunalInteractor; + + private final SystemDialogsCloser mSystemDialogsCloser; + private final KeyguardUpdateMonitorCallback mKeyguardCallback = new KeyguardUpdateMonitorCallback() { @Override @@ -168,6 +177,9 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ 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 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mExecutor = executor; mWindowManager = windowManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mScrimManager = scrimManager; mLowLightDreamComponent = lowLightDreamComponent; mHomeControlPanelDreamComponent = homeControlPanelDreamComponent; mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback); @@ -188,6 +201,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mUiEventLogger = uiEventLogger; mDreamOverlayCallbackController = dreamOverlayCallbackController; mWindowTitle = windowTitle; + mCommunalInteractor = communalInteractor; + mSystemDialogsCloser = systemDialogsCloser; final ViewModelStore viewModelStore = new ViewModelStore(); final Complication.Host host = @@ -292,6 +307,28 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ } } + @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 000000000000..6e7239a4a98e --- /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 31710ac4cc4f..516b8c521ca1 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.SysUISingleton; 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 @@ public interface DreamModule { 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 92612b824974..7d0553937f25 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 @@ enum class KeyguardState { } /** 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 adcb14a67983..8b7e11c4ab47 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 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW || 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 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW state.panelVisible, state.shadeOrQsExpanded, state.notificationShadeFocusable, + state.glanceableHubShowing, state.bouncerShowing, state.keyguardFadingAway, state.keyguardGoingAway, @@ -740,6 +744,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } @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 e0a98b3b7f9e..6a4b52af498c 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 @@ class NotificationShadeWindowState( @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 @@ class NotificationShadeWindowState( shadeOrQsExpanded.toString(), notificationShadeFocusable.toString(), bouncerShowing.toString(), + glanceableHubShowing.toString(), keyguardFadingAway.toString(), keyguardGoingAway.toString(), qsExpanded.toString(), @@ -119,6 +121,7 @@ class NotificationShadeWindowState( panelVisible: Boolean, panelExpanded: Boolean, notificationShadeFocusable: Boolean, + glanceableHubShowing: Boolean, bouncerShowing: Boolean, keyguardFadingAway: Boolean, keyguardGoingAway: Boolean, @@ -149,6 +152,7 @@ class NotificationShadeWindowState( 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 @@ class NotificationShadeWindowState( "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 e6695568361e..707d59aa560d 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 @@ public interface NotificationShadeWindowController extends RemoteInputController /** 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) {} diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index 0e8a5fb050dd..a818eabe9597 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -249,6 +249,16 @@ final class DreamController { mCurrentDream.mAppTask = appTask; } + void setDreamHasFocus(boolean hasFocus) { + if (mCurrentDream != null) { + mCurrentDream.mDreamHasFocus = hasFocus; + } + } + + boolean dreamHasFocus() { + return mCurrentDream != null && mCurrentDream.mDreamHasFocus; + } + /** * Sends a user activity signal to PowerManager to stop the screen from turning off immediately * if there hasn't been any user interaction in a while. @@ -271,6 +281,21 @@ final class DreamController { stopDreamInstance(immediate, reason, mCurrentDream); } + public boolean bringDreamToFront() { + if (mCurrentDream == null || mCurrentDream.mService == null) { + return false; + } + + try { + mCurrentDream.mService.comeToFront(); + return true; + } catch (RemoteException e) { + Slog.e(TAG, "Error asking dream to come to the front", e); + } + + return false; + } + /** * Stops the given dream instance. * @@ -426,6 +451,7 @@ final class DreamController { private String mStopReason; private long mDreamStartTime; public boolean mWakingGently; + public boolean mDreamHasFocus; private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded; private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded; diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 42c9e082cf70..fc63494d3c99 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.BIND_DREAM_SERVICE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.service.dreams.Flags.dreamTracksFocus; import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID; @@ -406,8 +407,10 @@ public final class DreamManagerService extends SystemService { /** Whether dreaming can start given user settings and the current dock/charge state. */ private boolean canStartDreamingInternal(boolean isScreenOn) { synchronized (mLock) { - // Can't start dreaming if we are already dreaming. - if (isScreenOn && isDreamingInternal()) { + // Can't start dreaming if we are already dreaming and the dream has focus. If we are + // dreaming but the dream does not have focus, then the dream can be brought to the + // front so it does have focus. + if (isScreenOn && isDreamingInternal() && dreamHasFocus()) { return false; } @@ -442,11 +445,20 @@ public final class DreamManagerService extends SystemService { } } + private boolean dreamHasFocus() { + // Dreams always had focus before they were able to track it. + return !dreamTracksFocus() || mController.dreamHasFocus(); + } + protected void requestStartDreamFromShell() { requestDreamInternal(); } private void requestDreamInternal() { + if (isDreamingInternal() && !dreamHasFocus() && mController.bringDreamToFront()) { + return; + } + // Ask the power manager to nap. It will eventually call back into // startDream() if/when it is appropriate to start dreaming. // Because napping could cause the screen to turn off immediately if the dream @@ -1128,6 +1140,16 @@ public final class DreamManagerService extends SystemService { }); } + @Override + public void onDreamFocusChanged(boolean hasFocus) { + final long ident = Binder.clearCallingIdentity(); + try { + mController.setDreamHasFocus(hasFocus); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + boolean canLaunchDreamActivity(String dreamPackageName, String packageName, int callingUid) { if (dreamPackageName == null || packageName == null) { diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java index db704342ab61..88ab871529ee 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java @@ -19,6 +19,8 @@ package com.android.server.dreams; import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER; import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -270,6 +272,31 @@ public class DreamControllerTest { eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS)); } + @Test + public void setDreamHasFocus_true_dreamHasFocus() { + mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, + 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); + + mDreamController.setDreamHasFocus(true); + assertTrue(mDreamController.dreamHasFocus()); + } + + @Test + public void setDreamHasFocus_false_dreamDoesNotHaveFocus() { + mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, + 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); + + mDreamController.setDreamHasFocus(false); + assertFalse(mDreamController.dreamHasFocus()); + } + + @Test + public void setDreamHasFocus_notDreaming_dreamDoesNotHaveFocus() { + mDreamController.setDreamHasFocus(true); + // Dream still doesn't have focus because it was never started. + assertFalse(mDreamController.dreamHasFocus()); + } + private ServiceConnection captureServiceConnection() { verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(), any()); |