diff options
6 files changed, 449 insertions, 312 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java index 04c4efbf7c78..fefe5a011358 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java @@ -149,7 +149,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { mUiEventLogger); when(mScrimManager.getCurrentController()).thenReturn(mScrimController); - when(mCentralSurfaces.isBouncerShowing()).thenReturn(false); when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator); when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker); when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE); @@ -193,11 +192,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { 2)).isTrue(); } - private enum Direction { - DOWN, - UP, - } - @Test public void testSwipeUp_whenBouncerInitiallyShowing_reduceHeightWithExclusionRects() { mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, @@ -210,7 +204,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { SCREEN_HEIGHT_PX * MIN_BOUNCER_HEIGHT; final int minAllowableBottom = SCREEN_HEIGHT_PX - Math.round(minBouncerHeight); - expected.set(0, minAllowableBottom , SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX); + expected.set(0, minAllowableBottom, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX); assertThat(bounds).isEqualTo(expected); @@ -278,69 +272,11 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { } /** - * Makes sure swiping up when bouncer initially showing doesn't change the expansion amount. - */ - @DisableFlags(Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING) - @Test - public void testSwipeUp_whenBouncerInitiallyShowing_doesNotSetExpansion() { - when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); - - mTouchHandler.onSessionStart(mTouchSession); - ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor = - ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class); - verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture()); - - final OnGestureListener gestureListener = gestureListenerCaptor.getValue(); - - final float percent = .3f; - final float distanceY = SCREEN_HEIGHT_PX * percent; - - // Swiping up near the top of the screen where the touch initiation region is. - final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, - 0, distanceY, 0); - final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, - 0, 0, 0); - - assertThat(gestureListener.onScroll(event1, event2, 0, distanceY)).isTrue(); - - verify(mScrimController, never()).expand(any()); - } - - /** - * Makes sure swiping up when bouncer initially showing doesn't change the expansion amount. - */ - @Test - @EnableFlags(Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING) - public void testSwipeUp_whenBouncerInitiallyShowing_doesNotSetExpansion_directionFiltering() { - when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); - - mTouchHandler.onSessionStart(mTouchSession); - ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor = - ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class); - verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture()); - - final OnGestureListener gestureListener = gestureListenerCaptor.getValue(); - - final float percent = .3f; - final float distanceY = SCREEN_HEIGHT_PX * percent; - - // Swiping up near the top of the screen where the touch initiation region is. - final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, - 0, distanceY, 0); - final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, - 0, 0, 0); - - assertThat(gestureListener.onScroll(event1, event2, 0, distanceY)).isFalse(); - - verify(mScrimController, never()).expand(any()); - } - - /** - * Makes sure swiping down when bouncer initially hidden doesn't change the expansion amount. + * Makes sure swiping down doesn't change the expansion amount. */ @Test @DisableFlags(Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING) - public void testSwipeDown_whenBouncerInitiallyHidden_doesNotSetExpansion() { + public void testSwipeDown_doesNotSetExpansion() { mTouchHandler.onSessionStart(mTouchSession); ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor = ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class); @@ -401,34 +337,8 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { final OnGestureListener gestureListener = gestureListenerCaptor.getValue(); - verifyScroll(.3f, Direction.UP, false, gestureListener); - - // Ensure that subsequent gestures are treated as expanding even if the bouncer state - // changes. - when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); - verifyScroll(.7f, Direction.UP, false, gestureListener); - } - - /** - * Makes sure the expansion amount is proportional to scroll. - */ - @Test - public void testSwipeDown_setsCorrectExpansionAmount() { - when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); - - mTouchHandler.onSessionStart(mTouchSession); - ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor = - ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class); - verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture()); - - final OnGestureListener gestureListener = gestureListenerCaptor.getValue(); - - verifyScroll(.3f, Direction.DOWN, true, gestureListener); - - // Ensure that subsequent gestures are treated as collapsing even if the bouncer state - // changes. - when(mCentralSurfaces.isBouncerShowing()).thenReturn(false); - verifyScroll(.7f, Direction.DOWN, true, gestureListener); + verifyScroll(.3f, gestureListener); + verifyScroll(.7f, gestureListener); } /** @@ -493,25 +403,24 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { verify(mCentralSurfaces, never()).awakenDreams(); } - private void verifyScroll(float percent, Direction direction, - boolean isBouncerInitiallyShowing, GestureDetector.OnGestureListener gestureListener) { + private void verifyScroll(float percent, + OnGestureListener gestureListener) { final float distanceY = SCREEN_HEIGHT_PX * percent; final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, - 0, direction == Direction.UP ? SCREEN_HEIGHT_PX : 0, 0); + 0, SCREEN_HEIGHT_PX, 0); final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, - 0, direction == Direction.UP ? SCREEN_HEIGHT_PX - distanceY : distanceY, 0); + 0, SCREEN_HEIGHT_PX - distanceY, 0); reset(mScrimController); assertThat(gestureListener.onScroll(event1, event2, 0, - direction == Direction.UP ? distanceY : -distanceY)) + distanceY)) .isTrue(); // Ensure only called once verify(mScrimController).expand(any()); - final float expansion = isBouncerInitiallyShowing ? percent : 1 - percent; - final float dragDownAmount = event2.getY() - event1.getY(); + final float expansion = 1 - percent; // Ensure correct expansion passed in. ShadeExpansionChangeEvent event = @@ -529,7 +438,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { final float expansion = 1 - swipeUpPercentage; // The upward velocity is ignored. final float velocityY = -1; - swipeToPosition(swipeUpPercentage, Direction.UP, velocityY); + swipeToPosition(swipeUpPercentage, velocityY); verify(mValueAnimatorCreator).create(eq(expansion), eq(KeyguardBouncerConstants.EXPANSION_HIDDEN)); @@ -552,7 +461,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { final float expansion = 1 - swipeUpPercentage; // The downward velocity is ignored. final float velocityY = 1; - swipeToPosition(swipeUpPercentage, Direction.UP, velocityY); + swipeToPosition(swipeUpPercentage, velocityY); verify(mValueAnimatorCreator).create(eq(expansion), eq(KeyguardBouncerConstants.EXPANSION_VISIBLE)); @@ -573,57 +482,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { } /** - * Tests that ending a downward swipe above the set threshold will continue the expansion, - * but will not trigger logging of the DREAM_SWIPED event. - */ - @Test - public void testSwipeDownPositionAboveThreshold_expandsBouncer_doesNotLog() { - when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); - - final float swipeDownPercentage = .3f; - // The downward velocity is ignored. - final float velocityY = 1; - swipeToPosition(swipeDownPercentage, Direction.DOWN, velocityY); - - verify(mValueAnimatorCreator).create(eq(swipeDownPercentage), - eq(KeyguardBouncerConstants.EXPANSION_VISIBLE)); - verify(mValueAnimator, never()).addListener(any()); - - verify(mFlingAnimationUtils).apply(eq(mValueAnimator), - eq(SCREEN_HEIGHT_PX * swipeDownPercentage), - eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_VISIBLE), - eq(velocityY), eq((float) SCREEN_HEIGHT_PX)); - verify(mValueAnimator).start(); - verify(mUiEventLogger, never()).log(any()); - } - - /** - * Tests that swiping down with a speed above the set threshold leads to bouncer collapsing - * down. - */ - @Test - public void testSwipeDownVelocityAboveMin_collapsesBouncer() { - when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); - when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn((float) 0); - - // The ending position above the set threshold is ignored. - final float swipeDownPercentage = .3f; - final float velocityY = 1; - swipeToPosition(swipeDownPercentage, Direction.DOWN, velocityY); - - verify(mValueAnimatorCreator).create(eq(swipeDownPercentage), - eq(KeyguardBouncerConstants.EXPANSION_HIDDEN)); - verify(mValueAnimator, never()).addListener(any()); - - verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator), - eq(SCREEN_HEIGHT_PX * swipeDownPercentage), - eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_HIDDEN), - eq(velocityY), eq((float) SCREEN_HEIGHT_PX)); - verify(mValueAnimator).start(); - verify(mUiEventLogger, never()).log(any()); - } - - /** * Tests that swiping up with a speed above the set threshold will continue the expansion. */ @Test @@ -634,7 +492,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { final float swipeUpPercentage = .3f; final float expansion = 1 - swipeUpPercentage; final float velocityY = -1; - swipeToPosition(swipeUpPercentage, Direction.UP, velocityY); + swipeToPosition(swipeUpPercentage, velocityY); verify(mValueAnimatorCreator).create(eq(expansion), eq(KeyguardBouncerConstants.EXPANSION_VISIBLE)); @@ -654,26 +512,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE); } - /** - * Ensures {@link CentralSurfaces} - */ - @Test - public void testInformBouncerShowingOnExpand() { - swipeToPosition(1f, Direction.UP, 0); - } - - /** - * Ensures {@link CentralSurfaces} - */ - @Test - public void testInformBouncerHidingOnCollapse() { - // Must swipe up to set initial state. - swipeToPosition(1f, Direction.UP, 0); - Mockito.clearInvocations(mCentralSurfaces); - - swipeToPosition(0f, Direction.DOWN, 0); - } - @Test public void testTouchSessionOnRemovedCalledTwice() { mTouchHandler.onSessionStart(mTouchSession); @@ -684,7 +522,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { onRemovedCallbackCaptor.getValue().onRemoved(); } - private void swipeToPosition(float percent, Direction direction, float velocityY) { + private void swipeToPosition(float percent, float velocityY) { Mockito.clearInvocations(mTouchSession); mTouchHandler.onSessionStart(mTouchSession); ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor = @@ -699,12 +537,12 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { final float distanceY = SCREEN_HEIGHT_PX * percent; final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, - 0, direction == Direction.UP ? SCREEN_HEIGHT_PX : 0, 0); + 0, SCREEN_HEIGHT_PX, 0); final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, - 0, direction == Direction.UP ? SCREEN_HEIGHT_PX - distanceY : distanceY, 0); + 0, SCREEN_HEIGHT_PX - distanceY, 0); assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0, - direction == Direction.UP ? distanceY : -distanceY)) + distanceY)) .isTrue(); final MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 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 6a8ab39c997d..bdb0c9aeb6ee 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt @@ -17,41 +17,52 @@ package com.android.systemui.dreams import android.content.ComponentName import android.content.Intent -import android.os.RemoteException import android.service.dreams.IDreamOverlay import android.service.dreams.IDreamOverlayCallback import android.service.dreams.IDreamOverlayClient import android.service.dreams.IDreamOverlayClientCallback +import android.testing.TestableLooper import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.WindowManagerImpl import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.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.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository +import com.android.systemui.communal.data.repository.FakeCommunalRepository +import com.android.systemui.communal.data.repository.fakeCommunalRepository +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 import com.android.systemui.dreams.complication.HideComplicationTouchHandler import com.android.systemui.dreams.dagger.DreamOverlayComponent +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos 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 +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.runCurrent import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -59,20 +70,24 @@ import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -import org.mockito.invocation.InvocationOnMock +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) @RunWith(AndroidJUnit4::class) class DreamOverlayServiceTest : SysuiTestCase() { private val mFakeSystemClock = FakeSystemClock() private val mMainExecutor = FakeExecutor(mFakeSystemClock) + private val kosmos = testKosmos() + private val testScope = kosmos.testScope @Mock lateinit var mLifecycleOwner: DreamOverlayLifecycleOwner - @Mock lateinit var mLifecycleRegistry: LifecycleRegistry + private lateinit var lifecycleRegistry: FakeLifecycleRegistry - lateinit var mWindowParams: WindowManager.LayoutParams + private lateinit var mWindowParams: WindowManager.LayoutParams @Mock lateinit var mDreamOverlayCallback: IDreamOverlayCallback @@ -124,22 +139,29 @@ class DreamOverlayServiceTest : SysuiTestCase() { @Mock lateinit var mScrimController: ScrimController - @Mock lateinit var mCommunalInteractor: CommunalInteractor - @Mock lateinit var mSystemDialogsCloser: SystemDialogsCloser @Mock lateinit var mDreamOverlayCallbackController: DreamOverlayCallbackController + private lateinit var bouncerRepository: FakeKeyguardBouncerRepository + private lateinit var communalRepository: FakeCommunalRepository + @Captor var mViewCaptor: ArgumentCaptor<View>? = null - var mService: DreamOverlayService? = null + private lateinit var mService: DreamOverlayService + @Before fun setup() { MockitoAnnotations.initMocks(this) + + lifecycleRegistry = FakeLifecycleRegistry(mLifecycleOwner) + bouncerRepository = kosmos.fakeKeyguardBouncerRepository + communalRepository = kosmos.fakeCommunalRepository + whenever(mDreamOverlayComponent.getDreamOverlayContainerViewController()) .thenReturn(mDreamOverlayContainerViewController) whenever(mComplicationComponent.getComplicationHostViewController()) .thenReturn(mComplicationHostViewController) - whenever(mLifecycleOwner.registry).thenReturn(mLifecycleRegistry) + whenever(mLifecycleOwner.registry).thenReturn(lifecycleRegistry) whenever(mComplicationComponentFactory.create(any(), any(), any(), any())) .thenReturn(mComplicationComponent) whenever(mComplicationComponent.getVisibilityController()) @@ -170,26 +192,29 @@ class DreamOverlayServiceTest : SysuiTestCase() { mStateController, mKeyguardUpdateMonitor, mScrimManager, - mCommunalInteractor, + kosmos.communalInteractor, mSystemDialogsCloser, mUiEventLogger, mTouchInsetManager, LOW_LIGHT_COMPONENT, HOME_CONTROL_PANEL_DREAM_COMPONENT, mDreamOverlayCallbackController, + kosmos.keyguardInteractor, WINDOW_NAME ) } - @get:Throws(RemoteException::class) - val client: IDreamOverlayClient + private val client: IDreamOverlayClient get() { - val proxy = mService!!.onBind(Intent()) + mService.onCreate() + TestableLooper.get(this).processAllMessages() + + val proxy = mService.onBind(Intent()) val overlay = IDreamOverlay.Stub.asInterface(proxy) val callback = Mockito.mock(IDreamOverlayClientCallback::class.java) overlay.getClient(callback) val clientCaptor = ArgumentCaptor.forClass(IDreamOverlayClient::class.java) - Mockito.verify(callback).onDreamOverlayClient(clientCaptor.capture()) + verify(callback).onDreamOverlayClient(clientCaptor.capture()) return clientCaptor.value } @@ -205,9 +230,8 @@ class DreamOverlayServiceTest : SysuiTestCase() { false /*shouldShowComplication*/ ) mMainExecutor.runAllReady() - Mockito.verify(mUiEventLogger) - .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START) - Mockito.verify(mUiEventLogger) + verify(mUiEventLogger).log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START) + verify(mUiEventLogger) .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START) } @@ -223,7 +247,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { false /*shouldShowComplication*/ ) mMainExecutor.runAllReady() - Mockito.verify(mWindowManager).addView(any(), any()) + verify(mWindowManager).addView(any(), any()) } // Validates that {@link DreamOverlayService} properly handles the case where the dream's @@ -242,14 +266,14 @@ class DreamOverlayServiceTest : SysuiTestCase() { false /*shouldShowComplication*/ ) mMainExecutor.runAllReady() - Mockito.verify(mWindowManager).addView(any(), any()) - Mockito.verify(mStateController).setOverlayActive(false) - Mockito.verify(mStateController).setLowLightActive(false) - Mockito.verify(mStateController).setEntryAnimationsFinished(false) - Mockito.verify(mStateController, Mockito.never()).setOverlayActive(true) - Mockito.verify(mUiEventLogger, Mockito.never()) + verify(mWindowManager).addView(any(), any()) + verify(mStateController).setOverlayActive(false) + verify(mStateController).setLowLightActive(false) + verify(mStateController).setEntryAnimationsFinished(false) + verify(mStateController, Mockito.never()).setOverlayActive(true) + verify(mUiEventLogger, Mockito.never()) .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START) - Mockito.verify(mDreamOverlayCallbackController, Mockito.never()).onStartDream() + verify(mDreamOverlayCallbackController, Mockito.never()).onStartDream() } @Test @@ -264,7 +288,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { false /*shouldShowComplication*/ ) mMainExecutor.runAllReady() - Mockito.verify(mDreamOverlayContainerViewController).init() + verify(mDreamOverlayContainerViewController).init() } @Test @@ -282,7 +306,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { false /*shouldShowComplication*/ ) mMainExecutor.runAllReady() - Mockito.verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView) + verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView) } @Test @@ -297,7 +321,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { true /*shouldShowComplication*/ ) mMainExecutor.runAllReady() - Truth.assertThat(mService!!.shouldShowComplications()).isTrue() + assertThat(mService.shouldShowComplications()).isTrue() } @Test @@ -312,8 +336,8 @@ class DreamOverlayServiceTest : SysuiTestCase() { false /*shouldShowComplication*/ ) mMainExecutor.runAllReady() - Truth.assertThat(mService!!.dreamComponent).isEqualTo(LOW_LIGHT_COMPONENT) - Mockito.verify(mStateController).setLowLightActive(true) + assertThat(mService.dreamComponent).isEqualTo(LOW_LIGHT_COMPONENT) + verify(mStateController).setLowLightActive(true) } @Test @@ -328,8 +352,8 @@ class DreamOverlayServiceTest : SysuiTestCase() { false /*shouldShowComplication*/ ) mMainExecutor.runAllReady() - Truth.assertThat(mService!!.dreamComponent).isEqualTo(HOME_CONTROL_PANEL_DREAM_COMPONENT) - Mockito.verify(mStateController).setHomeControlPanelActive(true) + assertThat(mService.dreamComponent).isEqualTo(HOME_CONTROL_PANEL_DREAM_COMPONENT) + verify(mStateController).setHomeControlPanelActive(true) } @Test @@ -346,19 +370,19 @@ class DreamOverlayServiceTest : SysuiTestCase() { mMainExecutor.runAllReady() // Verify view added. - Mockito.verify(mWindowManager).addView(mViewCaptor!!.capture(), any()) + verify(mWindowManager).addView(mViewCaptor!!.capture(), any()) // Service destroyed. - mService!!.onEndDream() + mService.onEndDream() mMainExecutor.runAllReady() // Verify view removed. - Mockito.verify(mWindowManager).removeView(mViewCaptor!!.value) + verify(mWindowManager).removeView(mViewCaptor!!.value) // Verify state correctly set. - Mockito.verify(mStateController).setOverlayActive(false) - Mockito.verify(mStateController).setLowLightActive(false) - Mockito.verify(mStateController).setEntryAnimationsFinished(false) + verify(mStateController).setOverlayActive(false) + verify(mStateController).setLowLightActive(false) + verify(mStateController).setEntryAnimationsFinished(false) } @Test @@ -391,7 +415,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { // Schedule the endDream call in the middle of the startDream implementation, as any // ordering is possible. - Mockito.doAnswer { invocation: InvocationOnMock? -> + Mockito.doAnswer { client.endDream() null } @@ -427,37 +451,37 @@ class DreamOverlayServiceTest : SysuiTestCase() { mMainExecutor.runAllReady() // Verify view added. - Mockito.verify(mWindowManager).addView(mViewCaptor!!.capture(), any()) + verify(mWindowManager).addView(mViewCaptor!!.capture(), any()) // Service destroyed. - mService!!.onDestroy() + mService.onDestroy() mMainExecutor.runAllReady() // Verify view removed. - Mockito.verify(mWindowManager).removeView(mViewCaptor!!.value) + verify(mWindowManager).removeView(mViewCaptor!!.value) // Verify state correctly set. - Mockito.verify(mKeyguardUpdateMonitor).removeCallback(any()) - Mockito.verify(mLifecycleRegistry).currentState = Lifecycle.State.DESTROYED - Mockito.verify(mStateController).setOverlayActive(false) - Mockito.verify(mStateController).setLowLightActive(false) - Mockito.verify(mStateController).setEntryAnimationsFinished(false) + verify(mKeyguardUpdateMonitor).removeCallback(any()) + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.DESTROYED) + verify(mStateController).setOverlayActive(false) + verify(mStateController).setLowLightActive(false) + verify(mStateController).setEntryAnimationsFinished(false) } @Test fun testDoNotRemoveViewOnDestroyIfOverlayNotStarted() { // Service destroyed without ever starting dream. - mService!!.onDestroy() + mService.onDestroy() mMainExecutor.runAllReady() // Verify no view is removed. - Mockito.verify(mWindowManager, Mockito.never()).removeView(any()) + verify(mWindowManager, Mockito.never()).removeView(any()) // Verify state still correctly set. - Mockito.verify(mKeyguardUpdateMonitor).removeCallback(any()) - Mockito.verify(mLifecycleRegistry).currentState = Lifecycle.State.DESTROYED - Mockito.verify(mStateController).setOverlayActive(false) - Mockito.verify(mStateController).setLowLightActive(false) + verify(mKeyguardUpdateMonitor).removeCallback(any()) + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.DESTROYED) + verify(mStateController).setOverlayActive(false) + verify(mStateController).setLowLightActive(false) } @Test @@ -465,7 +489,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { val client = client // Destroy the service. - mService!!.onDestroy() + mService.onDestroy() mMainExecutor.runAllReady() // Inform the overlay service of dream starting. @@ -476,15 +500,15 @@ class DreamOverlayServiceTest : SysuiTestCase() { false /*shouldShowComplication*/ ) mMainExecutor.runAllReady() - Mockito.verify(mWindowManager, Mockito.never()).addView(any(), any()) + verify(mWindowManager, Mockito.never()).addView(any(), any()) } @Test fun testNeverRemoveDecorViewIfNotAdded() { // Service destroyed before dream started. - mService!!.onDestroy() + mService.onDestroy() mMainExecutor.runAllReady() - Mockito.verify(mWindowManager, Mockito.never()).removeView(any()) + verify(mWindowManager, Mockito.never()).removeView(any()) } @Test @@ -501,11 +525,11 @@ class DreamOverlayServiceTest : SysuiTestCase() { mMainExecutor.runAllReady() // Verify that a new window is added. - Mockito.verify(mWindowManager).addView(mViewCaptor!!.capture(), any()) + verify(mWindowManager).addView(mViewCaptor!!.capture(), any()) val windowDecorView = mViewCaptor!!.value // Assert that the overlay is not showing complications. - Truth.assertThat(mService!!.shouldShowComplications()).isFalse() + assertThat(mService.shouldShowComplications()).isFalse() Mockito.clearInvocations(mDreamOverlayComponent) Mockito.clearInvocations(mAmbientTouchComponent) Mockito.clearInvocations(mWindowManager) @@ -522,16 +546,16 @@ class DreamOverlayServiceTest : SysuiTestCase() { mMainExecutor.runAllReady() // Assert that the overlay is showing complications. - Truth.assertThat(mService!!.shouldShowComplications()).isTrue() + assertThat(mService.shouldShowComplications()).isTrue() // Verify that the old overlay window has been removed, and a new one created. - Mockito.verify(mWindowManager).removeView(windowDecorView) - Mockito.verify(mWindowManager).addView(any(), any()) + verify(mWindowManager).removeView(windowDecorView) + verify(mWindowManager).addView(any(), any()) // Verify that new instances of overlay container view controller and overlay touch monitor // are created. - Mockito.verify(mDreamOverlayComponent).getDreamOverlayContainerViewController() - Mockito.verify(mAmbientTouchComponent).getTouchMonitor() + verify(mDreamOverlayComponent).getDreamOverlayContainerViewController() + verify(mAmbientTouchComponent).getTouchMonitor() } @Test @@ -546,15 +570,15 @@ class DreamOverlayServiceTest : SysuiTestCase() { true /*shouldShowComplication*/ ) mMainExecutor.runAllReady() - mService!!.onWakeUp() - Mockito.verify(mDreamOverlayContainerViewController).wakeUp() - Mockito.verify(mDreamOverlayCallbackController).onWakeUp() + mService.onWakeUp() + verify(mDreamOverlayContainerViewController).wakeUp() + verify(mDreamOverlayCallbackController).onWakeUp() } @Test fun testWakeUpBeforeStartDoesNothing() { - mService!!.onWakeUp() - Mockito.verify(mDreamOverlayContainerViewController, Mockito.never()).wakeUp() + mService.onWakeUp() + verify(mDreamOverlayContainerViewController, Mockito.never()).wakeUp() } @Test @@ -572,8 +596,8 @@ class DreamOverlayServiceTest : SysuiTestCase() { val paramsCaptor = ArgumentCaptor.forClass(WindowManager.LayoutParams::class.java) // Verify that a new window is added. - Mockito.verify(mWindowManager).addView(any(), paramsCaptor.capture()) - Truth.assertThat( + verify(mWindowManager).addView(any(), paramsCaptor.capture()) + assertThat( paramsCaptor.value.privateFlags and WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS == WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS @@ -598,7 +622,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { whenever(mDreamOverlayContainerViewController.isBouncerShowing()).thenReturn(true) mService!!.onComeToFront() - Mockito.verify(mScrimController).expand(any()) + verify(mScrimController).expand(any()) } // Tests that glanceable hub is hidden when DreamOverlayService is told that the dream is @@ -617,7 +641,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { mMainExecutor.runAllReady() mService!!.onComeToFront() - Mockito.verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Blank), nullable()) + assertThat(communalRepository.currentScene.value).isEqualTo(CommunalScenes.Blank) } // Tests that system dialogs (e.g. notification shade) closes when DreamOverlayService is told @@ -636,7 +660,197 @@ class DreamOverlayServiceTest : SysuiTestCase() { mMainExecutor.runAllReady() mService!!.onComeToFront() - Mockito.verify(mSystemDialogsCloser).closeSystemDialogs() + verify(mSystemDialogsCloser).closeSystemDialogs() + } + + @Test + fun testLifecycle_createdAfterConstruction() { + mMainExecutor.runAllReady() + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.CREATED) + } + + @Test + fun testLifecycle_resumedAfterDreamStarts() { + val client = client + + // Inform the overlay service of dream starting. Do not show dream complications. + client.startDream( + mWindowParams, + mDreamOverlayCallback, + DREAM_COMPONENT, + false /*shouldShowComplication*/ + ) + mMainExecutor.runAllReady() + assertThat(lifecycleRegistry.mLifecycles) + .containsExactly( + Lifecycle.State.CREATED, + Lifecycle.State.STARTED, + Lifecycle.State.RESUMED + ) + } + + @Test + fun testLifecycle_destroyedAfterOnDestroy() { + val client = client + + // Inform the overlay service of dream starting. Do not show dream complications. + client.startDream( + mWindowParams, + mDreamOverlayCallback, + DREAM_COMPONENT, + false /*shouldShowComplication*/ + ) + mMainExecutor.runAllReady() + mService.onDestroy() + mMainExecutor.runAllReady() + assertThat(lifecycleRegistry.mLifecycles) + .containsExactly( + Lifecycle.State.CREATED, + Lifecycle.State.STARTED, + Lifecycle.State.RESUMED, + Lifecycle.State.DESTROYED + ) + } + + @Test + fun testNotificationShadeShown_setsLifecycleState() { + val client = client + + // Inform the overlay service of dream starting. Do not show dream complications. + client.startDream( + mWindowParams, + mDreamOverlayCallback, + DREAM_COMPONENT, + false /*shouldShowComplication*/ + ) + mMainExecutor.runAllReady() + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.RESUMED) + val callbackCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) + verify(mKeyguardUpdateMonitor).registerCallback(callbackCaptor.capture()) + + // Notification shade opens. + callbackCaptor.value.onShadeExpandedChanged(true) + mMainExecutor.runAllReady() + + // Lifecycle state goes from resumed back to started when the notification shade shows. + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.STARTED) + + // Notification shade closes. + callbackCaptor.value.onShadeExpandedChanged(false) + mMainExecutor.runAllReady() + + // Lifecycle state goes back to RESUMED. + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.RESUMED) + } + + @Test + fun testBouncerShown_setsLifecycleState() { + val client = client + + // Inform the overlay service of dream starting. Do not show dream complications. + client.startDream( + mWindowParams, + mDreamOverlayCallback, + DREAM_COMPONENT, + false /*shouldShowComplication*/ + ) + mMainExecutor.runAllReady() + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.RESUMED) + + // Bouncer shows. + bouncerRepository.setPrimaryShow(true) + testScope.runCurrent() + mMainExecutor.runAllReady() + + // Lifecycle state goes from resumed back to started when the notification shade shows. + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.STARTED) + + // Bouncer closes. + bouncerRepository.setPrimaryShow(false) + testScope.runCurrent() + mMainExecutor.runAllReady() + + // Lifecycle state goes back to RESUMED. + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.RESUMED) + } + + @Test + fun testCommunalVisible_setsLifecycleState() { + val client = client + + // Inform the overlay service of dream starting. Do not show dream complications. + client.startDream( + mWindowParams, + mDreamOverlayCallback, + DREAM_COMPONENT, + false /*shouldShowComplication*/ + ) + mMainExecutor.runAllReady() + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.RESUMED) + val transitionState: MutableStateFlow<ObservableTransitionState> = + MutableStateFlow(ObservableTransitionState.Idle(CommunalScenes.Blank)) + communalRepository.setTransitionState(transitionState) + + // Communal becomes visible. + transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal) + testScope.runCurrent() + mMainExecutor.runAllReady() + + // Lifecycle state goes from resumed back to started when the notification shade shows. + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.STARTED) + + // Communal closes. + transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Blank) + testScope.runCurrent() + mMainExecutor.runAllReady() + + // Lifecycle state goes back to RESUMED. + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.RESUMED) + } + + // Verifies the dream's lifecycle + @Test + fun testLifecycleStarted_whenAnyOcclusion() { + val client = client + + // Inform the overlay service of dream starting. Do not show dream complications. + client.startDream( + mWindowParams, + mDreamOverlayCallback, + DREAM_COMPONENT, + false /*shouldShowComplication*/ + ) + mMainExecutor.runAllReady() + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.RESUMED) + val transitionState: MutableStateFlow<ObservableTransitionState> = + MutableStateFlow(ObservableTransitionState.Idle(CommunalScenes.Blank)) + communalRepository.setTransitionState(transitionState) + + // Communal becomes visible. + transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal) + testScope.runCurrent() + mMainExecutor.runAllReady() + + // Lifecycle state goes from resumed back to started when the notification shade shows. + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.STARTED) + + // Communal closes. + transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Blank) + testScope.runCurrent() + mMainExecutor.runAllReady() + + // Lifecycle state goes back to RESUMED. + assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.RESUMED) + } + + internal class FakeLifecycleRegistry(provider: LifecycleOwner) : LifecycleRegistry(provider) { + val mLifecycles: MutableList<State> = ArrayList() + + override var currentState: State + get() = mLifecycles[mLifecycles.size - 1] + set(state) { + mLifecycles.add(state) + } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java index d0f08f53fb32..85aeb27261aa 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java @@ -27,6 +27,7 @@ import android.view.InputEvent; import android.view.MotionEvent; import android.view.VelocityTracker; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.UiEvent; @@ -94,13 +95,11 @@ public class BouncerSwipeTouchHandler implements TouchHandler { private Boolean mCapture; private Boolean mExpanded; - private boolean mBouncerInitiallyShowing; - private TouchSession mTouchSession; - private ValueAnimatorCreator mValueAnimatorCreator; + private final ValueAnimatorCreator mValueAnimatorCreator; - private VelocityTrackerFactory mVelocityTrackerFactory; + private final VelocityTrackerFactory mVelocityTrackerFactory; private final UiEventLogger mUiEventLogger; @@ -118,17 +117,12 @@ public class BouncerSwipeTouchHandler implements TouchHandler { private final GestureDetector.OnGestureListener mOnGestureListener = new GestureDetector.SimpleOnGestureListener() { @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) { if (mCapture == null) { - mBouncerInitiallyShowing = mCentralSurfaces - .map(CentralSurfaces::isBouncerShowing) - .orElse(false); - if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) { mCapture = Math.abs(distanceY) > Math.abs(distanceX) - && ((distanceY < 0 && mBouncerInitiallyShowing) - || (distanceY > 0 && !mBouncerInitiallyShowing)); + && distanceY > 0; } else { // If the user scrolling favors a vertical direction, begin capturing // scrolls. @@ -146,13 +140,8 @@ public class BouncerSwipeTouchHandler implements TouchHandler { return false; } - // Don't set expansion for downward scroll when the bouncer is hidden. - if (!mBouncerInitiallyShowing && (e1.getY() < e2.getY())) { - return true; - } - - // Don't set expansion for upward scroll when the bouncer is shown. - if (mBouncerInitiallyShowing && (e1.getY() > e2.getY())) { + // Don't set expansion for downward scroll. + if (e1.getY() < e2.getY()) { return true; } @@ -176,8 +165,7 @@ public class BouncerSwipeTouchHandler implements TouchHandler { final float dragDownAmount = e2.getY() - e1.getY(); final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY()) / mTouchSession.getBounds().height(); - setPanelExpansion(mBouncerInitiallyShowing - ? screenTravelPercentage : 1 - screenTravelPercentage); + setPanelExpansion(1 - screenTravelPercentage); return true; } }; @@ -223,9 +211,9 @@ public class BouncerSwipeTouchHandler implements TouchHandler { LockPatternUtils lockPatternUtils, UserTracker userTracker, @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING) - FlingAnimationUtils flingAnimationUtils, + FlingAnimationUtils flingAnimationUtils, @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING) - FlingAnimationUtils flingAnimationUtilsClosing, + FlingAnimationUtils flingAnimationUtilsClosing, @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage, @Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage, UiEventLogger uiEventLogger) { @@ -247,17 +235,13 @@ public class BouncerSwipeTouchHandler implements TouchHandler { public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) { final int width = bounds.width(); final int height = bounds.height(); - final float minBouncerHeight = height * mMinBouncerZoneScreenPercentage; final int minAllowableBottom = Math.round(height * (1 - mMinBouncerZoneScreenPercentage)); - final boolean isBouncerShowing = - mCentralSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false); - final Rect normalRegion = isBouncerShowing - ? new Rect(0, 0, width, Math.round(height * mBouncerZoneScreenPercentage)) - : new Rect(0, Math.round(height * (1 - mBouncerZoneScreenPercentage)), - width, height); + final Rect normalRegion = new Rect(0, + Math.round(height * (1 - mBouncerZoneScreenPercentage)), + width, height); - if (!isBouncerShowing && exclusionRect != null) { + if (exclusionRect != null) { int lowestBottom = Math.min(Math.max(0, exclusionRect.bottom), minAllowableBottom); normalRegion.top = Math.max(normalRegion.top, lowestBottom); } @@ -322,8 +306,7 @@ public class BouncerSwipeTouchHandler implements TouchHandler { : KeyguardBouncerConstants.EXPANSION_HIDDEN; // Log the swiping up to show Bouncer event. - if (!mBouncerInitiallyShowing - && expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) { + if (expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) { mUiEventLogger.log(DreamEvent.DREAM_SWIPED); } @@ -335,17 +318,15 @@ public class BouncerSwipeTouchHandler implements TouchHandler { } } - private ValueAnimator createExpansionAnimator(float targetExpansion, float expansionHeight) { + private ValueAnimator createExpansionAnimator(float targetExpansion) { final ValueAnimator animator = mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion); animator.addUpdateListener( animation -> { float expansionFraction = (float) animation.getAnimatedValue(); - float dragDownAmount = expansionFraction * expansionHeight; setPanelExpansion(expansionFraction); }); - if (!mBouncerInitiallyShowing - && targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) { + if (targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) { animator.addListener( new AnimatorListenerAdapter() { @Override @@ -381,8 +362,7 @@ public class BouncerSwipeTouchHandler implements TouchHandler { final float viewHeight = mTouchSession.getBounds().height(); final float currentHeight = viewHeight * mCurrentExpansion; final float targetHeight = viewHeight * expansion; - final float expansionHeight = targetHeight - currentHeight; - final ValueAnimator animator = createExpansionAnimator(expansion, expansionHeight); + final ValueAnimator animator = createExpansionAnimator(expansion); if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) { // Hides the bouncer, i.e., fully expands the space above the bouncer. mFlingAnimationUtilsClosing.apply(animator, currentHeight, targetHeight, velocity, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 3d52bcd02ada..a9ef53104c31 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -19,9 +19,11 @@ package com.android.systemui.dreams; import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_WINDOW_TITLE; import static com.android.systemui.dreams.dagger.DreamModule.DREAM_TOUCH_INSET_MANAGER; import static com.android.systemui.dreams.dagger.DreamModule.HOME_CONTROL_PANEL_DREAM_COMPONENT; +import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.graphics.drawable.ColorDrawable; import android.util.Log; import android.view.View; @@ -34,7 +36,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; +import androidx.lifecycle.LifecycleService; +import androidx.lifecycle.ServiceLifecycleDispatcher; import androidx.lifecycle.ViewModelStore; import com.android.dream.lowlight.dagger.LowLightDreamModule; @@ -52,12 +57,14 @@ 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.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.touch.TouchInsetManager; import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.Arrays; import java.util.HashSet; +import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Named; @@ -67,7 +74,8 @@ import javax.inject.Named; * dream reaches directly out to the service with a Window reference (via LayoutParams), which the * service uses to insert its own child Window into the dream's parent Window. */ -public class DreamOverlayService extends android.service.dreams.DreamOverlayService { +public class DreamOverlayService extends android.service.dreams.DreamOverlayService implements + LifecycleOwner { private static final String TAG = "DreamOverlayService"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -98,6 +106,21 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ // True if the service has been destroyed. private boolean mDestroyed = false; + /** + * True if the notification shade is open. + */ + private boolean mShadeExpanded = false; + + /** + * True if any part of the glanceable hub is visible. + */ + private boolean mCommunalVisible = false; + + /** + * True if the primary bouncer is visible. + */ + private boolean mBouncerShowing = false; + private final ComplicationComponent mComplicationComponent; private final AmbientTouchComponent mAmbientTouchComponent; @@ -107,9 +130,21 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ private final DreamOverlayComponent mDreamOverlayComponent; - private final DreamOverlayLifecycleOwner mLifecycleOwner; + /** + * This {@link LifecycleRegistry} controls when dream overlay functionality, like touch + * handling, should be active. It will automatically be paused when the dream overlay is hidden + * while dreaming, such as when the notification shade, bouncer, or glanceable hub are visible. + */ private final LifecycleRegistry mLifecycleRegistry; + /** + * Drives the lifecycle exposed by this service's {@link #getLifecycle()}. + * <p> + * Used to mimic a {@link LifecycleService}, though we do not update the lifecycle in + * {@link #onBind(Intent)} since it's final in the base class. + */ + private final ServiceLifecycleDispatcher mDispatcher = new ServiceLifecycleDispatcher(this); + private TouchMonitor mTouchMonitor; private final CommunalInteractor mCommunalInteractor; @@ -121,17 +156,46 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ @Override public void onShadeExpandedChanged(boolean expanded) { mExecutor.execute(() -> { - if (getCurrentStateLocked() != Lifecycle.State.RESUMED - && getCurrentStateLocked() != Lifecycle.State.STARTED) { + if (mShadeExpanded == expanded) { return; } + mShadeExpanded = expanded; - setCurrentStateLocked( - expanded ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED); + updateLifecycleStateLocked(); }); } }; + private final Consumer<Boolean> mCommunalVisibleConsumer = new Consumer<>() { + @Override + public void accept(Boolean communalVisible) { + mExecutor.execute(() -> { + if (mCommunalVisible == communalVisible) { + return; + } + + mCommunalVisible = communalVisible; + + updateLifecycleStateLocked(); + }); + } + }; + + private final Consumer<Boolean> mBouncerShowingConsumer = new Consumer<>() { + @Override + public void accept(Boolean bouncerShowing) { + mExecutor.execute(() -> { + if (mBouncerShowing == bouncerShowing) { + return; + } + + mBouncerShowing = bouncerShowing; + + updateLifecycleStateLocked(); + }); + } + }; + private final DreamOverlayStateController.Callback mExitAnimationFinishedCallback = new DreamOverlayStateController.Callback() { @Override @@ -183,10 +247,11 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ UiEventLogger uiEventLogger, @Named(DREAM_TOUCH_INSET_MANAGER) TouchInsetManager touchInsetManager, @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT) - ComponentName lowLightDreamComponent, + ComponentName lowLightDreamComponent, @Nullable @Named(HOME_CONTROL_PANEL_DREAM_COMPONENT) - ComponentName homeControlPanelDreamComponent, + ComponentName homeControlPanelDreamComponent, DreamOverlayCallbackController dreamOverlayCallbackController, + KeyguardInteractor keyguardInteractor, @Named(DREAM_OVERLAY_WINDOW_TITLE) String windowTitle) { super(executor); mContext = context; @@ -218,10 +283,32 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ new HashSet<>(Arrays.asList( mDreamComplicationComponent.getHideComplicationTouchHandler(), mDreamOverlayComponent.getCommunalTouchHandler()))); - mLifecycleOwner = lifecycleOwner; - mLifecycleRegistry = mLifecycleOwner.getRegistry(); + mLifecycleRegistry = lifecycleOwner.getRegistry(); + + mExecutor.execute(() -> setLifecycleStateLocked(Lifecycle.State.CREATED)); - mExecutor.execute(() -> setCurrentStateLocked(Lifecycle.State.CREATED)); + collectFlow(getLifecycle(), communalInteractor.isCommunalVisible(), + mCommunalVisibleConsumer); + collectFlow(getLifecycle(), keyguardInteractor.primaryBouncerShowing, + mBouncerShowingConsumer); + } + + @NonNull + @Override + public Lifecycle getLifecycle() { + return mDispatcher.getLifecycle(); + } + + @Override + public void onCreate() { + mDispatcher.onServicePreSuperOnCreate(); + super.onCreate(); + } + + @Override + public void onStart(Intent intent, int startId) { + mDispatcher.onServicePreSuperOnStart(); + super.onStart(intent, startId); } @Override @@ -229,19 +316,20 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback); mExecutor.execute(() -> { - setCurrentStateLocked(Lifecycle.State.DESTROYED); + setLifecycleStateLocked(Lifecycle.State.DESTROYED); resetCurrentDreamOverlayLocked(); mDestroyed = true; }); + mDispatcher.onServicePreSuperOnDestroy(); super.onDestroy(); } @Override public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) { - setCurrentStateLocked(Lifecycle.State.STARTED); + setLifecycleStateLocked(Lifecycle.State.STARTED); mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START); @@ -271,7 +359,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ return; } - setCurrentStateLocked(Lifecycle.State.RESUMED); + setLifecycleStateLocked(Lifecycle.State.RESUMED); mStateController.setOverlayActive(true); final ComponentName dreamComponent = getDreamComponent(); mStateController.setLowLightActive( @@ -291,14 +379,27 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ resetCurrentDreamOverlayLocked(); } - private Lifecycle.State getCurrentStateLocked() { + private Lifecycle.State getLifecycleStateLocked() { return mLifecycleRegistry.getCurrentState(); } - private void setCurrentStateLocked(Lifecycle.State state) { + private void setLifecycleStateLocked(Lifecycle.State state) { mLifecycleRegistry.setCurrentState(state); } + private void updateLifecycleStateLocked() { + if (getLifecycleStateLocked() != Lifecycle.State.RESUMED + && getLifecycleStateLocked() != Lifecycle.State.STARTED) { + return; + } + + // If anything is on top of the dream, we should stop touch handling. + boolean shouldPause = mShadeExpanded || mCommunalVisible || mBouncerShowing; + + setLifecycleStateLocked( + shouldPause ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED); + } + @Override public void onWakeUp() { if (mDreamOverlayContainerViewController != null) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 7224536cfe70..d19176853387 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -226,7 +226,7 @@ constructor( val ambientIndicationVisible: Flow<Boolean> = repository.ambientIndicationVisible.asStateFlow() /** Whether the primary bouncer is showing or not. */ - val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow + @JvmField val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow /** Whether the alternate bouncer is showing or not. */ val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index 8dc4756569f1..d4b793720328 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -22,6 +22,7 @@ import android.content.applicationContext import android.os.fakeExecutorHandler import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.bouncerRepository +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.classifier.falsingCollector import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository @@ -41,6 +42,7 @@ import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInterac import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.model.sceneContainerPlugin import com.android.systemui.plugins.statusbar.statusBarStateController @@ -78,6 +80,8 @@ class KosmosJavaAdapter( val bouncerRepository by lazy { kosmos.bouncerRepository } val communalRepository by lazy { kosmos.fakeCommunalRepository } val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } + val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository } + val keyguardInteractor by lazy { kosmos.keyguardInteractor } val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor } val powerRepository by lazy { kosmos.fakePowerRepository } |