diff options
| author | 2023-07-17 22:45:35 +0000 | |
|---|---|---|
| committer | 2023-07-17 22:45:35 +0000 | |
| commit | 069b961633c620742f6c863ed9c5b6724f74ec8b (patch) | |
| tree | d51f0bb14b59883b7ebaf4554f2853a1eef40b0e | |
| parent | 5791ab0da3addc15262e4f779c94208c284abdbc (diff) | |
| parent | a2aa6b3a89fc5f6eeb307f2f027c5bd4d9f29733 (diff) | |
Merge "Tap to wake up dreaming for lockscreen hosted dream" into udc-qpr-dev
7 files changed, 323 insertions, 1 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt index eb1ca66f6ca8..809edc09070a 100644 --- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt @@ -70,6 +70,19 @@ constructor( } } + /** + * Wakes up the device if dreaming with a screensaver. + * + * @param why a string explaining why we're waking the device for debugging purposes. Should be + * in SCREAMING_SNAKE_CASE. + * @param wakeReason the PowerManager-based reason why we're waking the device. + */ + fun wakeUpIfDreaming(why: String, @PowerManager.WakeReason wakeReason: Int) { + if (statusBarStateController.isDreaming) { + repository.wakeUp(why, wakeReason) + } + } + companion object { private const val FSI_WAKE_WHY = "full_screen_intent" } diff --git a/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt new file mode 100644 index 000000000000..45fc68a793bd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import android.os.PowerManager +import android.view.GestureDetector +import android.view.MotionEvent +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.FalsingManager.LOW_PENALTY +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.statusbar.StatusBarState +import javax.inject.Inject + +/** + * This gestureListener will wake up by tap when the device is dreaming but not dozing, and the + * selected screensaver is hosted in lockscreen. Tap is gated by the falsing manager. + * + * Touches go through the [NotificationShadeWindowViewController]. + */ +@SysUISingleton +class LockscreenHostedDreamGestureListener +@Inject +constructor( + private val falsingManager: FalsingManager, + private val powerInteractor: PowerInteractor, + private val statusBarStateController: StatusBarStateController, + private val primaryBouncerInteractor: PrimaryBouncerInteractor, + private val keyguardRepository: KeyguardRepository, + private val shadeLogger: ShadeLogger, +) : GestureDetector.SimpleOnGestureListener() { + private val TAG = this::class.simpleName + + override fun onSingleTapUp(e: MotionEvent): Boolean { + if (shouldHandleMotionEvent()) { + if (!falsingManager.isFalseTap(LOW_PENALTY)) { + shadeLogger.d("$TAG#onSingleTapUp tap handled, requesting wakeUpIfDreaming") + powerInteractor.wakeUpIfDreaming( + "DREAMING_SINGLE_TAP", + PowerManager.WAKE_REASON_TAP + ) + } else { + shadeLogger.d("$TAG#onSingleTapUp false tap ignored") + } + return true + } + return false + } + + private fun shouldHandleMotionEvent(): Boolean { + return keyguardRepository.isActiveDreamLockscreenHosted.value && + statusBarStateController.state == StatusBarState.KEYGUARD && + !primaryBouncerInteractor.isBouncerShowing() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index f6db9e434610..108ea68ae8e0 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -16,6 +16,7 @@ package com.android.systemui.shade; +import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; @@ -102,9 +103,12 @@ public class NotificationShadeWindowViewController { private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final AmbientState mAmbientState; private final PulsingGestureListener mPulsingGestureListener; + private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener; private final NotificationInsetsController mNotificationInsetsController; private final boolean mIsTrackpadCommonEnabled; + private final FeatureFlags mFeatureFlags; private GestureDetector mPulsingWakeupGestureHandler; + private GestureDetector mDreamingWakeupGestureHandler; private View mBrightnessMirror; private boolean mTouchActive; private boolean mTouchCancelled; @@ -156,6 +160,7 @@ public class NotificationShadeWindowViewController { NotificationInsetsController notificationInsetsController, AmbientState ambientState, PulsingGestureListener pulsingGestureListener, + LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener, KeyguardBouncerViewModel keyguardBouncerViewModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory, KeyguardMessageAreaController.Factory messageAreaControllerFactory, @@ -187,8 +192,10 @@ public class NotificationShadeWindowViewController { mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; mAmbientState = ambientState; mPulsingGestureListener = pulsingGestureListener; + mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener; mNotificationInsetsController = notificationInsetsController; mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON); + mFeatureFlags = featureFlags; // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); @@ -237,7 +244,10 @@ public class NotificationShadeWindowViewController { mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller); mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(), mPulsingGestureListener); - + if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) { + mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(), + mLockscreenHostedDreamGestureListener); + } mView.setLayoutInsetsController(mNotificationInsetsController); mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() { @Override @@ -291,6 +301,10 @@ public class NotificationShadeWindowViewController { mFalsingCollector.onTouchEvent(ev); mPulsingWakeupGestureHandler.onTouchEvent(ev); + if (mDreamingWakeupGestureHandler != null + && mDreamingWakeupGestureHandler.onTouchEvent(ev)) { + return true; + } if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) { return true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt index 45bb9313264c..435a1f1327d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt @@ -182,6 +182,32 @@ class PowerInteractorTest : SysuiTestCase() { assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_APPLICATION) } + @Test + fun wakeUpIfDreaming_dreaming_woken() { + // GIVEN device is dreaming + whenever(statusBarStateController.isDreaming).thenReturn(true) + + // WHEN wakeUpIfDreaming is called + underTest.wakeUpIfDreaming("testReason", PowerManager.WAKE_REASON_GESTURE) + + // THEN device is woken up + assertThat(repository.lastWakeWhy).isEqualTo("testReason") + assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE) + } + + @Test + fun wakeUpIfDreaming_notDreaming_notWoken() { + // GIVEN device is not dreaming + whenever(statusBarStateController.isDreaming).thenReturn(false) + + // WHEN wakeUpIfDreaming is called + underTest.wakeUpIfDreaming("why", PowerManager.WAKE_REASON_TAP) + + // THEN device is not woken + assertThat(repository.lastWakeWhy).isNull() + assertThat(repository.lastWakeReason).isNull() + } + companion object { private val IMMEDIATE = Dispatchers.Main.immediate } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt new file mode 100644 index 000000000000..24d62fba8471 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import android.os.PowerManager +import android.testing.AndroidTestingRunner +import android.view.MotionEvent +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.phone.ScreenOffAnimationController +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidTestingRunner::class) +class LockscreenHostedDreamGestureListenerTest : SysuiTestCase() { + @Mock private lateinit var falsingManager: FalsingManager + @Mock private lateinit var falsingCollector: FalsingCollector + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var shadeLogger: ShadeLogger + @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController + @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor + + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private lateinit var powerRepository: FakePowerRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var underTest: LockscreenHostedDreamGestureListener + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + powerRepository = FakePowerRepository() + keyguardRepository = FakeKeyguardRepository() + + underTest = + LockscreenHostedDreamGestureListener( + falsingManager, + PowerInteractor( + powerRepository, + keyguardRepository, + falsingCollector, + screenOffAnimationController, + statusBarStateController, + ), + statusBarStateController, + primaryBouncerInteractor, + keyguardRepository, + shadeLogger, + ) + whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(false) + } + + @Test + fun testGestureDetector_onSingleTap_whileDreaming() = + testScope.runTest { + // GIVEN device dreaming and the dream is hosted in lockscreen + whenever(statusBarStateController.isDreaming).thenReturn(true) + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + testScope.runCurrent() + + // GIVEN the falsing manager does NOT think the tap is a false tap + whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false) + + // WHEN there's a tap + underTest.onSingleTapUp(upEv) + + // THEN wake up device if dreaming + Truth.assertThat(powerRepository.lastWakeWhy).isNotNull() + Truth.assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP) + } + + @Test + fun testGestureDetector_onSingleTap_notOnKeyguard() = + testScope.runTest { + // GIVEN device dreaming and the dream is hosted in lockscreen + whenever(statusBarStateController.isDreaming).thenReturn(true) + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + testScope.runCurrent() + + // GIVEN shade is open + whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) + + // GIVEN the falsing manager does NOT think the tap is a false tap + whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false) + + // WHEN there's a tap + underTest.onSingleTapUp(upEv) + + // THEN the falsing manager never gets a call + verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt()) + } + + @Test + fun testGestureDetector_onSingleTap_bouncerShown() = + testScope.runTest { + // GIVEN device dreaming and the dream is hosted in lockscreen + whenever(statusBarStateController.isDreaming).thenReturn(true) + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + testScope.runCurrent() + + // GIVEN bouncer is expanded + whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(true) + + // GIVEN the falsing manager does NOT think the tap is a false tap + whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false) + + // WHEN there's a tap + underTest.onSingleTapUp(upEv) + + // THEN the falsing manager never gets a call + verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt()) + } + + @Test + fun testGestureDetector_onSingleTap_falsing() = + testScope.runTest { + // GIVEN device dreaming and the dream is hosted in lockscreen + whenever(statusBarStateController.isDreaming).thenReturn(true) + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + testScope.runCurrent() + + // GIVEN the falsing manager thinks the tap is a false tap + whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(true) + + // WHEN there's a tap + underTest.onSingleTapUp(upEv) + + // THEN the device doesn't wake up + Truth.assertThat(powerRepository.lastWakeWhy).isNull() + Truth.assertThat(powerRepository.lastWakeReason).isNull() + } + + @Test + fun testSingleTap_notDreaming_noFalsingCheck() = + testScope.runTest { + // GIVEN device not dreaming with lockscreen hosted dream + whenever(statusBarStateController.isDreaming).thenReturn(false) + keyguardRepository.setIsActiveDreamLockscreenHosted(false) + testScope.runCurrent() + + // WHEN there's a tap + underTest.onSingleTapUp(upEv) + + // THEN the falsing manager never gets a call + verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt()) + } +} + +private val upEv = 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 5fb3a7955b5c..2a398c55560c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -111,6 +111,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var lockIconViewController: LockIconViewController @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController @Mock private lateinit var pulsingGestureListener: PulsingGestureListener + @Mock + private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener @Mock private lateinit var notificationInsetsController: NotificationInsetsController @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @@ -147,6 +149,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { featureFlags.set(Flags.DUAL_SHADE, false) featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) + featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) val inputProxy = MultiShadeInputProxy() testScope = TestScope() @@ -183,6 +186,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { notificationInsetsController, ambientState, pulsingGestureListener, + mLockscreenHostedDreamGestureListener, keyguardBouncerViewModel, keyguardBouncerComponentFactory, mock(KeyguardMessageAreaController.Factory::class.java), diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 544137e95779..d9eb9b9166b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -113,6 +113,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController @Mock private lateinit var ambientState: AmbientState @Mock private lateinit var pulsingGestureListener: PulsingGestureListener + @Mock + private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel @Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory @Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @@ -161,6 +163,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { featureFlags.set(Flags.DUAL_SHADE, false) featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) + featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) val inputProxy = MultiShadeInputProxy() testScope = TestScope() val multiShadeInteractor = @@ -196,6 +199,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { notificationInsetsController, ambientState, pulsingGestureListener, + mLockscreenHostedDreamGestureListener, keyguardBouncerViewModel, keyguardBouncerComponentFactory, Mockito.mock(KeyguardMessageAreaController.Factory::class.java), |