Merge changes I92b9c220,I31a55ca1,I536a2e1e into main
* changes:
Fix ShadeTouchHandler over the lock screen
Use TouchMonitor for touch handling over hub
Stop overlay touch handling when the bouncer or glanceable hub are visible over the dream
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 04c4efb..fefe5a0 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 @@
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 @@
2)).isTrue();
}
- private enum Direction {
- DOWN,
- UP,
- }
-
@Test
public void testSwipeUp_whenBouncerInitiallyShowing_reduceHeightWithExclusionRects() {
mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion,
@@ -210,7 +204,7 @@
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 @@
}
/**
- * 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 @@
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 @@
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 @@
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 @@
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 @@
}
/**
- * 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 @@
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 @@
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 @@
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 @@
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/ambient/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
index 27bffd0..11a4241 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
@@ -18,8 +18,10 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.view.GestureDetector;
import android.view.MotionEvent;
@@ -28,7 +30,6 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -36,6 +37,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -49,66 +51,89 @@
CentralSurfaces mCentralSurfaces;
@Mock
- ShadeViewController mShadeViewController;
-
- @Mock
TouchHandler.TouchSession mTouchSession;
ShadeTouchHandler mTouchHandler;
+ @Captor
+ ArgumentCaptor<GestureDetector.OnGestureListener> mGestureListenerCaptor;
+ @Captor
+ ArgumentCaptor<InputChannelCompat.InputEventListener> mInputListenerCaptor;
+
private static final int TOUCH_HEIGHT = 20;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), mShadeViewController,
- TOUCH_HEIGHT);
+
+ mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), TOUCH_HEIGHT);
+ }
+
+ // Verifies that a swipe down in the gesture region is captured by the shade touch handler.
+ @Test
+ public void testSwipeDown_captured() {
+ final boolean captured = swipe(Direction.DOWN);
+
+ assertThat(captured).isTrue();
+ }
+
+ // Verifies that a swipe in the upward direction is not catpured.
+ @Test
+ public void testSwipeUp_notCaptured() {
+ final boolean captured = swipe(Direction.UP);
+
+ // Motion events not captured as the swipe is going in the wrong direction.
+ assertThat(captured).isFalse();
+ }
+
+ // Verifies that a swipe down forwards captured touches to the shade window for handling.
+ @Test
+ public void testSwipeDown_sentToShadeWindow() {
+ swipe(Direction.DOWN);
+
+ // Both motion events are sent for the shade window to process.
+ verify(mCentralSurfaces, times(2)).handleExternalShadeWindowTouch(any());
+ }
+
+ // Verifies that a swipe down is not forwarded to the shade window.
+ @Test
+ public void testSwipeUp_touchesNotSent() {
+ swipe(Direction.UP);
+
+ // Motion events are not sent for the shade window to process as the swipe is going in the
+ // wrong direction.
+ verify(mCentralSurfaces, never()).handleExternalShadeWindowTouch(any());
}
/**
- * Verify that touches aren't handled when the bouncer is showing.
+ * Simulates a swipe in the given direction and returns true if the touch was intercepted by the
+ * touch handler's gesture listener.
+ * <p>
+ * Swipe down starts from a Y coordinate of 0 and goes downward. Swipe up starts from the edge
+ * of the gesture region, {@link #TOUCH_HEIGHT}, and goes upward to 0.
*/
- @Test
- public void testInactiveOnBouncer() {
- when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
+ private boolean swipe(Direction direction) {
+ Mockito.clearInvocations(mTouchSession);
mTouchHandler.onSessionStart(mTouchSession);
- verify(mTouchSession).pop();
+
+ verify(mTouchSession).registerGestureListener(mGestureListenerCaptor.capture());
+ verify(mTouchSession).registerInputListener(mInputListenerCaptor.capture());
+
+ final float startY = direction == Direction.UP ? TOUCH_HEIGHT : 0;
+ final float endY = direction == Direction.UP ? 0 : TOUCH_HEIGHT;
+
+ // Send touches to the input and gesture listener.
+ final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, startY, 0);
+ final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, endY, 0);
+ mInputListenerCaptor.getValue().onInputEvent(event1);
+ mInputListenerCaptor.getValue().onInputEvent(event2);
+ final boolean captured = mGestureListenerCaptor.getValue().onScroll(event1, event2, 0,
+ startY - endY);
+
+ return captured;
}
- /**
- * Make sure {@link ShadeTouchHandler}
- */
- @Test
- public void testTouchPilferingOnScroll() {
- final MotionEvent motionEvent1 = Mockito.mock(MotionEvent.class);
- final MotionEvent motionEvent2 = Mockito.mock(MotionEvent.class);
-
- final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerArgumentCaptor =
- ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
-
- mTouchHandler.onSessionStart(mTouchSession);
- verify(mTouchSession).registerGestureListener(gestureListenerArgumentCaptor.capture());
-
- assertThat(gestureListenerArgumentCaptor.getValue()
- .onScroll(motionEvent1, motionEvent2, 1, 1))
- .isTrue();
+ private enum Direction {
+ DOWN, UP,
}
-
- /**
- * Ensure touches are propagated to the {@link ShadeViewController}.
- */
- @Test
- public void testEventPropagation() {
- final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
-
- final ArgumentCaptor<InputChannelCompat.InputEventListener>
- inputEventListenerArgumentCaptor =
- ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
-
- mTouchHandler.onSessionStart(mTouchSession);
- verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture());
- inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent);
- verify(mShadeViewController).handleExternalTouch(motionEvent);
- }
-
}
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 6a8ab39..bdb0c9a 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 @@
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.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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
- Mockito.verify(mDreamOverlayContainerViewController).init()
+ verify(mDreamOverlayContainerViewController).init()
}
@Test
@@ -282,7 +306,7 @@
false /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
- Mockito.verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView)
+ verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView)
}
@Test
@@ -297,7 +321,7 @@
true /*shouldShowComplication*/
)
mMainExecutor.runAllReady()
- Truth.assertThat(mService!!.shouldShowComplications()).isTrue()
+ assertThat(mService.shouldShowComplications()).isTrue()
}
@Test
@@ -312,8 +336,8 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
val client = client
// Destroy the service.
- mService!!.onDestroy()
+ mService.onDestroy()
mMainExecutor.runAllReady()
// Inform the overlay service of dream starting.
@@ -476,15 +500,15 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index 29fbee0..e3c6dee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -108,7 +108,7 @@
mTouchHandler.onSessionStart(mTouchSession);
verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture());
inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent);
- verify(mCentralSurfaces).handleDreamTouch(motionEvent);
+ verify(mCentralSurfaces).handleExternalShadeWindowTouch(motionEvent);
}
@Test
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 d0f08f5..85aeb27 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.MotionEvent;
import android.view.VelocityTracker;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.UiEvent;
@@ -94,13 +95,11 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
: 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 @@
}
}
- 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 @@
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/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
index 9ef9938..9c7fc9d 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
@@ -23,7 +23,8 @@
import android.view.GestureDetector;
import android.view.MotionEvent;
-import com.android.systemui.shade.ShadeViewController;
+import androidx.annotation.NonNull;
+
import com.android.systemui.statusbar.phone.CentralSurfaces;
import java.util.Optional;
@@ -37,29 +38,34 @@
*/
public class ShadeTouchHandler implements TouchHandler {
private final Optional<CentralSurfaces> mSurfaces;
- private final ShadeViewController mShadeViewController;
private final int mInitiationHeight;
+ /**
+ * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
+ */
+ private Boolean mCapture;
+
@Inject
ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces,
- ShadeViewController shadeViewController,
@Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) {
mSurfaces = centralSurfaces;
- mShadeViewController = shadeViewController;
mInitiationHeight = initiationHeight;
}
@Override
public void onSessionStart(TouchSession session) {
- if (mSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) {
+ if (mSurfaces.isEmpty()) {
session.pop();
return;
}
- session.registerInputListener(ev -> {
- mShadeViewController.handleExternalTouch((MotionEvent) ev);
+ session.registerCallback(() -> mCapture = null);
+ session.registerInputListener(ev -> {
if (ev instanceof MotionEvent) {
+ if (mCapture != null && mCapture) {
+ mSurfaces.get().handleExternalShadeWindowTouch((MotionEvent) ev);
+ }
if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
session.pop();
}
@@ -68,15 +74,25 @@
session.registerGestureListener(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) {
- return true;
+ if (mCapture == null) {
+ // Only capture swipes that are going downwards.
+ mCapture = Math.abs(distanceY) > Math.abs(distanceX) && distanceY < 0;
+ if (mCapture) {
+ // Send the initial touches over, as the input listener has already
+ // processed these touches.
+ mSurfaces.get().handleExternalShadeWindowTouch(e1);
+ mSurfaces.get().handleExternalShadeWindowTouch(e2);
+ }
+ }
+ return mCapture;
}
@Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
float velocityY) {
- return true;
+ return mCapture;
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 3d52bcd..a9ef531 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 @@
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.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.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 @@
* 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 @@
// 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 @@
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 @@
@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 @@
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 @@
new HashSet<>(Arrays.asList(
mDreamComplicationComponent.getHideComplicationTouchHandler(),
mDreamOverlayComponent.getCommunalTouchHandler())));
- mLifecycleOwner = lifecycleOwner;
- mLifecycleRegistry = mLifecycleOwner.getRegistry();
+ mLifecycleRegistry = lifecycleOwner.getRegistry();
- mExecutor.execute(() -> setCurrentStateLocked(Lifecycle.State.CREATED));
+ mExecutor.execute(() -> setLifecycleStateLocked(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 @@
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 @@
return;
}
- setCurrentStateLocked(Lifecycle.State.RESUMED);
+ setLifecycleStateLocked(Lifecycle.State.RESUMED);
mStateController.setOverlayActive(true);
final ComponentName dreamComponent = getDreamComponent();
mStateController.setLowLightActive(
@@ -291,14 +379,27 @@
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/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index 1c047dd..fff0c58 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -98,7 +98,7 @@
// Notification shade window has its own logic to be visible if the hub is open, no need to
// do anything here other than send touch events over.
session.registerInputListener(ev -> {
- surfaces.handleDreamTouch((MotionEvent) ev);
+ surfaces.handleExternalShadeWindowTouch((MotionEvent) ev);
if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
var unused = session.pop();
}
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 7224536..d191768 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 @@
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/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index a8481cd..a5a5474 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -28,10 +28,14 @@
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.ambient.touch.TouchMonitor
+import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
import com.android.systemui.communal.dagger.Communal
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.ui.compose.CommunalContainer
@@ -45,6 +49,8 @@
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import com.android.systemui.util.kotlin.collectFlow
import javax.inject.Inject
@@ -67,12 +73,27 @@
private val shadeInteractor: ShadeInteractor,
private val powerManager: PowerManager,
private val communalColors: CommunalColors,
- @Communal private val dataSourceDelegator: SceneDataSourceDelegator,
-) {
+ private val ambientTouchComponentFactory: AmbientTouchComponent.Factory,
+ @Communal private val dataSourceDelegator: SceneDataSourceDelegator
+) : LifecycleOwner {
/** The container view for the hub. This will not be initialized until [initView] is called. */
private var communalContainerView: View? = null
/**
+ * This lifecycle is used to control when the [touchMonitor] listens to touches. The lifecycle
+ * should only be [Lifecycle.State.RESUMED] when the hub is showing and not covered by anything,
+ * such as the notification shade or bouncer.
+ */
+ private var lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
+
+ /**
+ * This [TouchMonitor] listens for top and bottom swipe gestures globally when the hub is open.
+ * When a top or bottom swipe is detected, they will be intercepted and used to open the
+ * notification shade/bouncer.
+ */
+ private var touchMonitor: TouchMonitor? = null
+
+ /**
* The width of the area in which a right edge swipe can open the hub, in pixels. Read from
* resources when [initView] is called.
*/
@@ -80,20 +101,6 @@
private var rightEdgeSwipeRegionWidth: Int = 0
/**
- * The height of the area in which a top edge swipe while the hub is open will not intercept
- * touches, in pixels. This allows the top edge swipe to instead open the notification shade.
- * Read from resources when [initView] is called.
- */
- private var topEdgeSwipeRegionWidth: Int = 0
-
- /**
- * The height of the area in which a bottom edge swipe while the hub is open will not intercept
- * touches, in pixels. This allows the bottom edge swipe to instead open the bouncer. Read from
- * resources when [initView] is called.
- */
- private var bottomEdgeSwipeRegionWidth: Int = 0
-
- /**
* True if we are currently tracking a gesture for opening the hub that started in the edge
* swipe region.
*/
@@ -102,9 +109,6 @@
/** True if we are currently tracking a touch on the hub while it's open. */
private var isTrackingHubTouch = false
- /** True if we are tracking a top or bottom swipe gesture while the hub is open. */
- private var isTrackingHubGesture = false
-
/**
* True if the hub UI is fully open, meaning it should receive touch input.
*
@@ -121,9 +125,15 @@
private var anyBouncerShowing = false
/**
- * True if the shade is fully expanded, meaning the hub should not receive any touch input.
+ * True if the shade is fully expanded and the user is not interacting with it anymore, meaning
+ * the hub should not receive any touch input.
*
- * Tracks [ShadeInteractor.isAnyFullyExpanded].
+ * We need to not pause the touch handling lifecycle as soon as the shade opens because if the
+ * user swipes down, then back up without lifting their finger, the lifecycle will be paused
+ * then resumed, and resuming force-stops all active touch sessions. This means the shade will
+ * not receive the end of the gesture and will be stuck open.
+ *
+ * Based on [ShadeInteractor.isAnyFullyExpanded] and [ShadeInteractor.isUserInteracting].
*/
private var shadeShowing = false
@@ -132,8 +142,6 @@
* and just let the dream overlay's touch handling deal with them.
*
* Tracks [KeyguardInteractor.isDreaming].
- *
- * TODO(b/328838259): figure out a proper solution for touch handling above the lock screen too
*/
private var isDreaming = false
@@ -192,28 +200,45 @@
throw RuntimeException("Communal view has already been initialized")
}
+ if (touchMonitor == null) {
+ touchMonitor =
+ ambientTouchComponentFactory.create(this, HashSet()).getTouchMonitor().apply {
+ init()
+ }
+ }
+ lifecycleRegistry.currentState = Lifecycle.State.CREATED
+
communalContainerView = containerView
rightEdgeSwipeRegionWidth =
containerView.resources.getDimensionPixelSize(
R.dimen.communal_right_edge_swipe_region_width
)
- topEdgeSwipeRegionWidth =
- containerView.resources.getDimensionPixelSize(
- R.dimen.communal_top_edge_swipe_region_height
- )
- bottomEdgeSwipeRegionWidth =
- containerView.resources.getDimensionPixelSize(
- R.dimen.communal_bottom_edge_swipe_region_height
- )
collectFlow(
containerView,
keyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState),
- { anyBouncerShowing = it }
+ {
+ anyBouncerShowing = it
+ updateLifecycleState()
+ }
)
- collectFlow(containerView, communalInteractor.isCommunalShowing, { hubShowing = it })
- collectFlow(containerView, shadeInteractor.isAnyFullyExpanded, { shadeShowing = it })
+ collectFlow(
+ containerView,
+ communalInteractor.isCommunalShowing,
+ {
+ hubShowing = it
+ updateLifecycleState()
+ }
+ )
+ collectFlow(
+ containerView,
+ and(shadeInteractor.isAnyFullyExpanded, not(shadeInteractor.isUserInteracting)),
+ {
+ shadeShowing = it
+ updateLifecycleState()
+ }
+ )
collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it })
communalContainerView = containerView
@@ -221,10 +246,24 @@
return containerView
}
+ /**
+ * Updates the lifecycle stored by the [lifecycleRegistry] to control when the [touchMonitor]
+ * should listen for and intercept top and bottom swipes.
+ */
+ private fun updateLifecycleState() {
+ val shouldInterceptGestures = hubShowing && !(shadeShowing || anyBouncerShowing)
+ if (shouldInterceptGestures) {
+ lifecycleRegistry.currentState = Lifecycle.State.RESUMED
+ } else {
+ lifecycleRegistry.currentState = Lifecycle.State.STARTED
+ }
+ }
+
/** Removes the container view from its parent. */
fun disposeView() {
communalContainerView?.let {
(it.parent as ViewGroup).removeView(it)
+ lifecycleRegistry.currentState = Lifecycle.State.CREATED
communalContainerView = null
}
}
@@ -262,15 +301,7 @@
if (isDown && !hubOccluded) {
// Only intercept down events if the hub isn't occluded by the bouncer or
// notification shade.
- val y = ev.rawY
- val topSwipe: Boolean = y <= topEdgeSwipeRegionWidth
- val bottomSwipe = y >= view.height - bottomEdgeSwipeRegionWidth
-
- if (topSwipe || bottomSwipe) {
- isTrackingHubGesture = true
- } else {
- isTrackingHubTouch = true
- }
+ isTrackingHubTouch = true
}
if (isTrackingHubTouch) {
@@ -283,19 +314,6 @@
// gesture
// may return false from dispatchTouchEvent.
return true
- } else if (isTrackingHubGesture) {
- // Tracking a top or bottom swipe on the hub UI.
- if (isUp || isCancel) {
- isTrackingHubGesture = false
- }
-
- // If we're dreaming, intercept touches so the hub UI doesn't receive them, but
- // don't do anything so that the dream's touch handling takes care of opening
- // the bouncer or shade.
- //
- // If we're not dreaming, we don't intercept touches at the top/bottom edge so that
- // swipes can open the notification shade and bouncer.
- return isDreaming
}
return false
@@ -347,4 +365,7 @@
0
)
}
+
+ override val lifecycle: Lifecycle
+ get() = lifecycleRegistry
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 907cf5e..44f86da 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -25,7 +25,6 @@
import android.app.StatusBarManager;
import android.util.Log;
import android.view.GestureDetector;
-import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -74,14 +73,14 @@
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.util.time.SystemClock;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
import java.io.PrintWriter;
import java.util.Optional;
import java.util.function.Consumer;
import javax.inject.Inject;
-import kotlinx.coroutines.ExperimentalCoroutinesApi;
-
/**
* Controller for {@link NotificationShadeWindowView}.
*/
@@ -137,6 +136,11 @@
private final PanelExpansionInteractor mPanelExpansionInteractor;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
+ /**
+ * If {@code true}, an external touch sent in {@link #handleExternalTouch(MotionEvent)} has been
+ * intercepted and all future touch events for the gesture should be processed by this view.
+ */
+ private boolean mExternalTouchIntercepted = false;
private boolean mIsTrackingBarGesture = false;
private boolean mIsOcclusionTransitionRunning = false;
private DisableSubpixelTextTransitionListener mDisableSubpixelTextTransitionListener;
@@ -253,11 +257,28 @@
}
/**
- * Handle a touch event while dreaming by forwarding the event to the content view.
+ * Handle a touch event while dreaming or on the hub by forwarding the event to the content
+ * view.
+ * <p>
+ * Since important logic for handling touches lives in the dispatch/intercept phases, we
+ * simulate going through all of these stages before sending onTouchEvent if intercepted.
+ *
* @param event The event to forward.
*/
- public void handleDreamTouch(MotionEvent event) {
- mView.dispatchTouchEvent(event);
+ public void handleExternalTouch(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mExternalTouchIntercepted = false;
+ }
+
+ if (!mView.dispatchTouchEvent(event)) {
+ return;
+ }
+ if (!mExternalTouchIntercepted) {
+ mExternalTouchIntercepted = mView.onInterceptTouchEvent(event);
+ }
+ if (mExternalTouchIntercepted) {
+ mView.onTouchEvent(event);
+ }
}
/** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 8fb552f..7d97428 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -283,11 +283,12 @@
void awakenDreams();
/**
- * Handle a touch event while dreaming when the touch was initiated within a prescribed
- * swipeable area. This method is provided for cases where swiping in certain areas of a dream
- * should be handled by CentralSurfaces instead (e.g. swiping communal hub open).
+ * Handle a touch event while dreaming or on the glanceable hub when the touch was initiated
+ * within a prescribed swipeable area. This method is provided for cases where swiping in
+ * certain areas should be handled by CentralSurfaces instead (e.g. swiping hub open, opening
+ * the notification shade over dream or hub).
*/
- void handleDreamTouch(MotionEvent event);
+ void handleExternalShadeWindowTouch(MotionEvent event);
boolean isBouncerShowing();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 8af7ee8..d5e66ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -79,7 +79,7 @@
override fun updateScrimController() {}
override fun shouldIgnoreTouch() = false
override fun isDeviceInteractive() = false
- override fun handleDreamTouch(event: MotionEvent?) {}
+ override fun handleExternalShadeWindowTouch(event: MotionEvent?) {}
override fun awakenDreams() {}
override fun isBouncerShowing() = false
override fun isBouncerShowingScrimmed() = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index e9aa7aa..b2b2cea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2928,8 +2928,8 @@
};
@Override
- public void handleDreamTouch(MotionEvent event) {
- getNotificationShadeWindowViewController().handleDreamTouch(event);
+ public void handleExternalShadeWindowTouch(MotionEvent event) {
+ getNotificationShadeWindowViewController().handleExternalTouch(event);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index fd9daf8..03f5ecf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -25,23 +25,24 @@
import android.view.View
import android.view.WindowManager
import android.widget.FrameLayout
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
+import com.android.systemui.ambient.touch.TouchHandler
+import com.android.systemui.ambient.touch.TouchMonitor
+import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
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.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -51,7 +52,6 @@
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
import com.android.systemui.shade.data.repository.fakeShadeRepository
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.testKosmos
@@ -60,7 +60,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertThrows
@@ -87,16 +86,14 @@
@Mock private lateinit var communalViewModel: CommunalViewModel
@Mock private lateinit var powerManager: PowerManager
@Mock private lateinit var dialogFactory: SystemUIDialogFactory
+ @Mock private lateinit var touchMonitor: TouchMonitor
@Mock private lateinit var communalColors: CommunalColors
- private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
- private lateinit var shadeInteractor: ShadeInteractor
- private lateinit var keyguardInteractor: KeyguardInteractor
+ private lateinit var ambientTouchComponentFactory: AmbientTouchComponent.Factory
private lateinit var parentView: FrameLayout
private lateinit var containerView: View
private lateinit var testableLooper: TestableLooper
- private lateinit var communalInteractor: CommunalInteractor
private lateinit var communalRepository: FakeCommunalRepository
private lateinit var underTest: GlanceableHubContainerController
@@ -104,32 +101,37 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- communalInteractor = kosmos.communalInteractor
communalRepository = kosmos.fakeCommunalRepository
- keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
- keyguardInteractor = kosmos.keyguardInteractor
- shadeInteractor = kosmos.shadeInteractor
- underTest =
- GlanceableHubContainerController(
- communalInteractor,
- communalViewModel,
- dialogFactory,
- keyguardTransitionInteractor,
- keyguardInteractor,
- shadeInteractor,
- powerManager,
- communalColors,
- kosmos.sceneDataSourceDelegator,
- )
+ ambientTouchComponentFactory =
+ object : AmbientTouchComponent.Factory {
+ override fun create(
+ lifecycleOwner: LifecycleOwner,
+ touchHandlers: Set<TouchHandler>
+ ): AmbientTouchComponent =
+ object : AmbientTouchComponent {
+ override fun getTouchMonitor(): TouchMonitor = touchMonitor
+ }
+ }
+
+ with(kosmos) {
+ underTest =
+ GlanceableHubContainerController(
+ communalInteractor,
+ communalViewModel,
+ dialogFactory,
+ keyguardTransitionInteractor,
+ keyguardInteractor,
+ shadeInteractor,
+ powerManager,
+ communalColors,
+ ambientTouchComponentFactory,
+ kosmos.sceneDataSourceDelegator,
+ )
+ }
testableLooper = TestableLooper.get(this)
overrideResource(R.dimen.communal_right_edge_swipe_region_width, RIGHT_SWIPE_REGION_WIDTH)
- overrideResource(R.dimen.communal_top_edge_swipe_region_height, TOP_SWIPE_REGION_WIDTH)
- overrideResource(
- R.dimen.communal_bottom_edge_swipe_region_height,
- BOTTOM_SWIPE_REGION_WIDTH
- )
// Make communal available so that communalInteractor.desiredScene accurately reflects
// scene changes instead of just returning Blank.
@@ -161,6 +163,7 @@
shadeInteractor,
powerManager,
communalColors,
+ ambientTouchComponentFactory,
kosmos.sceneDataSourceDelegator,
)
@@ -215,62 +218,6 @@
}
@Test
- fun onTouchEvent_topSwipeWhenCommunalOpen_doesNotIntercept() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
-
- // Touch event in the top swipe region is not intercepted.
- assertThat(underTest.onTouchEvent(DOWN_IN_TOP_SWIPE_REGION_EVENT)).isFalse()
- }
- }
-
- @Test
- fun onTouchEvent_bottomSwipeWhenCommunalOpen_doesNotIntercept() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
-
- // Touch event in the bottom swipe region is not intercepted.
- assertThat(underTest.onTouchEvent(DOWN_IN_BOTTOM_SWIPE_REGION_EVENT)).isFalse()
- }
- }
-
- @Test
- fun onTouchEvent_topSwipeWhenDreaming_doesNotIntercept() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
-
- // Device is dreaming.
- fakeKeyguardRepository.setDreaming(true)
- runCurrent()
-
- // Touch event in the top swipe region is not intercepted.
- assertThat(underTest.onTouchEvent(DOWN_IN_TOP_SWIPE_REGION_EVENT)).isFalse()
- }
- }
-
- @Test
- fun onTouchEvent_bottomSwipeWhenDreaming_doesNotIntercept() =
- with(kosmos) {
- testScope.runTest {
- // Communal is open.
- goToScene(CommunalScenes.Communal)
-
- // Device is dreaming.
- fakeKeyguardRepository.setDreaming(true)
- runCurrent()
-
- // Touch event in the bottom swipe region is not intercepted.
- assertThat(underTest.onTouchEvent(DOWN_IN_BOTTOM_SWIPE_REGION_EVENT)).isFalse()
- }
- }
-
- @Test
fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() =
with(kosmos) {
testScope.runTest {
@@ -327,6 +274,141 @@
}
@Test
+ fun lifecycle_initializedAfterConstruction() =
+ with(kosmos) {
+ val underTest =
+ GlanceableHubContainerController(
+ communalInteractor,
+ communalViewModel,
+ dialogFactory,
+ keyguardTransitionInteractor,
+ keyguardInteractor,
+ shadeInteractor,
+ powerManager,
+ communalColors,
+ ambientTouchComponentFactory,
+ kosmos.sceneDataSourceDelegator,
+ )
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
+ }
+
+ @Test
+ fun lifecycle_createdAfterViewCreated() =
+ with(kosmos) {
+ val underTest =
+ GlanceableHubContainerController(
+ communalInteractor,
+ communalViewModel,
+ dialogFactory,
+ keyguardTransitionInteractor,
+ keyguardInteractor,
+ shadeInteractor,
+ powerManager,
+ communalColors,
+ ambientTouchComponentFactory,
+ kosmos.sceneDataSourceDelegator,
+ )
+
+ // Only initView without attaching a view as we don't want the flows to start collecting
+ // yet.
+ underTest.initView(View(context))
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun lifecycle_startedAfterFlowsUpdate() {
+ // Flows start collecting due to test setup, causing the state to advance to STARTED.
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+ }
+
+ @Test
+ fun lifecycle_resumedAfterCommunalShows() {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+ }
+
+ @Test
+ fun lifecycle_startedAfterCommunalCloses() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+
+ // Communal closes.
+ goToScene(CommunalScenes.Blank)
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+ }
+ }
+
+ @Test
+ fun lifecycle_startedAfterPrimaryBouncerShows() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Bouncer is visible.
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ KeyguardState.GLANCEABLE_HUB,
+ KeyguardState.PRIMARY_BOUNCER,
+ testScope
+ )
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+ }
+ }
+
+ @Test
+ fun lifecycle_startedAfterAlternateBouncerShows() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Bouncer is visible.
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ KeyguardState.GLANCEABLE_HUB,
+ KeyguardState.ALTERNATE_BOUNCER,
+ testScope
+ )
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+ }
+ }
+
+ @Test
+ fun lifecycle_createdAfterDisposeView() {
+ // Container view disposed.
+ underTest.disposeView()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun lifecycle_startedAfterShadeShows() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Shade shows up.
+ fakeShadeRepository.setQsExpansion(1.0f)
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+ }
+ }
+
+ @Test
fun editMode_communalAvailable() =
with(kosmos) {
testScope.runTest {
@@ -371,8 +453,6 @@
private const val CONTAINER_WIDTH = 100
private const val CONTAINER_HEIGHT = 100
private const val RIGHT_SWIPE_REGION_WIDTH = 20
- private const val TOP_SWIPE_REGION_WIDTH = 20
- private const val BOTTOM_SWIPE_REGION_WIDTH = 20
/**
* A touch down event right in the middle of the screen, to avoid being in any of the swipe
@@ -389,17 +469,6 @@
)
private val DOWN_IN_RIGHT_SWIPE_REGION_EVENT =
MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, CONTAINER_WIDTH.toFloat(), 0f, 0)
- private val DOWN_IN_TOP_SWIPE_REGION_EVENT =
- MotionEvent.obtain(
- 0L,
- 0L,
- MotionEvent.ACTION_DOWN,
- 0f,
- TOP_SWIPE_REGION_WIDTH.toFloat(),
- 0
- )
- private val DOWN_IN_BOTTOM_SWIPE_REGION_EVENT =
- MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, CONTAINER_HEIGHT.toFloat(), 0)
private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
private val UP_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index da09579..d95cc2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -500,6 +500,46 @@
}
@Test
+ fun handleExternalTouch_intercepted_sendsOnTouch() {
+ // Accept dispatch and also intercept.
+ whenever(view.dispatchTouchEvent(any())).thenReturn(true)
+ whenever(view.onInterceptTouchEvent(any())).thenReturn(true)
+
+ underTest.handleExternalTouch(DOWN_EVENT)
+ underTest.handleExternalTouch(MOVE_EVENT)
+
+ // Once intercepted, both events are sent to the view.
+ verify(view).onTouchEvent(DOWN_EVENT)
+ verify(view).onTouchEvent(MOVE_EVENT)
+ }
+
+ @Test
+ fun handleExternalTouch_notDispatched_interceptNotCalled() {
+ // Don't accept dispatch
+ whenever(view.dispatchTouchEvent(any())).thenReturn(false)
+
+ underTest.handleExternalTouch(DOWN_EVENT)
+
+ // Interception is not offered.
+ verify(view, never()).onInterceptTouchEvent(any())
+ }
+
+ @Test
+ fun handleExternalTouch_notIntercepted_onTouchNotSent() {
+ // Accept dispatch, but don't dispatch
+ whenever(view.dispatchTouchEvent(any())).thenReturn(true)
+ whenever(view.onInterceptTouchEvent(any())).thenReturn(false)
+
+ underTest.handleExternalTouch(DOWN_EVENT)
+ underTest.handleExternalTouch(MOVE_EVENT)
+
+ // Interception offered for both events, but onTouchEvent is never called.
+ verify(view).onInterceptTouchEvent(DOWN_EVENT)
+ verify(view).onInterceptTouchEvent(MOVE_EVENT)
+ verify(view, never()).onTouchEvent(any())
+ }
+
+ @Test
fun testGetKeyguardMessageArea() =
testScope.runTest {
underTest.keyguardMessageArea
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 8dc4756..d4b7937 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.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.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 @@
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 }