diff options
| author | 2024-08-26 21:47:22 +0000 | |
|---|---|---|
| committer | 2024-08-26 21:47:22 +0000 | |
| commit | 23a02a0efc2fc20db1f752e2c5e339b76808c44f (patch) | |
| tree | 31485084085d4db4c8d081d6f8ae453ae5007b56 | |
| parent | 2c836346d5710a668415ac4def590ebf41e36536 (diff) | |
| parent | 9aa24c21eb8314c5ccbc6e9c9faa41aa4250d737 (diff) | |
Merge "Add logging for glanceable hub touch handling" into main
4 files changed, 156 insertions, 29 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt new file mode 100644 index 000000000000..b0abdb7c128d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger + +import javax.inject.Qualifier + +/** A [com.android.systemui.log.LogBuffer] for communal touch-handling logging. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class CommunalTouchLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 5cae58a81cf9..ba3c1d216099 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -618,6 +618,16 @@ public class LogModule { } /** + * Provides a {@link LogBuffer} for communal touch-handling logs. + */ + @Provides + @SysUISingleton + @CommunalTouchLog + public static LogBuffer provideCommunalTouchLogBuffer(LogBufferFactory factory) { + return factory.create("CommunalTouchLog", 250); + } + + /** * Provides a {@link TableLogBuffer} for communal-related logs. */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 181c3df92222..6223ca778815 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -33,6 +33,8 @@ import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner import androidx.compose.ui.platform.ComposeView import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.lifecycleScope @@ -55,6 +57,9 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.Logger +import com.android.systemui.log.dagger.CommunalTouchLog import com.android.systemui.media.controls.ui.controller.KeyguardMediaController import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -91,8 +96,10 @@ constructor( @Communal private val dataSourceDelegator: SceneDataSourceDelegator, private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController, private val keyguardMediaController: KeyguardMediaController, - private val lockscreenSmartspaceController: LockscreenSmartspaceController + private val lockscreenSmartspaceController: LockscreenSmartspaceController, + @CommunalTouchLog logBuffer: LogBuffer, ) : LifecycleOwner { + private val logger = Logger(logBuffer, "GlanceableHubContainerController") private class CommunalWrapper(context: Context) : FrameLayout(context) { private val consumers: MutableSet<Consumer<Boolean>> = ArraySet() @@ -143,6 +150,17 @@ constructor( private var isTrackingHubTouch = false /** + * True if a touch gesture on the lock screen has been consumed by the shade/bouncer and thus + * should be ignored by the hub. + * + * This is necessary on the lock screen as gestures on an empty spot go through special touch + * handling logic in [NotificationShadeWindowViewController] that decides if they should go to + * the shade or bouncer. Once the shade or bouncer are moving, we don't get the typical cancel + * event so to play nice, we ignore touches once we see the shade or bouncer are opening. + */ + private var touchTakenByKeyguardGesture = false + + /** * True if the hub UI is fully open, meaning it should receive touch input. * * Tracks [CommunalInteractor.isCommunalShowing]. @@ -206,6 +224,21 @@ constructor( */ private var isDreaming = false + /** Observes and logs state when the lifecycle that controls the [touchMonitor] updates. */ + private val touchLifecycleLogger: LifecycleObserver = LifecycleEventObserver { _, event -> + logger.d({ + "Touch handler lifecycle changed to $str1. hubShowing: $bool1, " + + "shadeShowingAndConsumingTouches: $bool2, " + + "anyBouncerShowing: $bool3, inEditModeTransition: $bool4" + }) { + str1 = event.toString() + bool1 = hubShowing + bool2 = shadeShowingAndConsumingTouches + bool3 = anyBouncerShowing + bool4 = inEditModeTransition + } + } + /** Returns a flow that tracks whether communal hub is available. */ fun communalAvailable(): Flow<Boolean> = anyOf(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen) @@ -268,6 +301,7 @@ constructor( init() } } + lifecycleRegistry.addObserver(touchLifecycleLogger) lifecycleRegistry.currentState = Lifecycle.State.CREATED communalContainerView = containerView @@ -328,6 +362,9 @@ constructor( backGestureInset ) } + logger.d({ "Insets updated: $str1" }) { + str1 = containerView.systemGestureExclusionRects.toString() + } } } @@ -343,6 +380,9 @@ constructor( ), { anyBouncerShowing = it + if (hubShowing) { + logger.d({ "New value for anyBouncerShowing: $bool1" }) { bool1 = it } + } updateTouchHandlingState() } ) @@ -396,7 +436,13 @@ constructor( // If the shade reaches full expansion without interaction, then we should allow it // to consume touches rather than handling it here until it disappears. shadeShowingAndConsumingTouches = - userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive + (userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive).also { + if (it != shadeShowingAndConsumingTouches && hubShowing) { + logger.d({ "New value for shadeShowingAndConsumingTouches: $bool1" }) { + bool1 = it + } + } + } updateTouchHandlingState() } ) @@ -404,6 +450,7 @@ constructor( communalContainerWrapper = CommunalWrapper(containerView.context) communalContainerWrapper?.addView(communalContainerView) + logger.d("Hub container initialized") return communalContainerWrapper!! } @@ -446,6 +493,10 @@ constructor( (it.parent as ViewGroup).removeView(it) communalContainerWrapper = null } + + lifecycleRegistry.removeObserver(touchLifecycleLogger) + + logger.d("Hub container disposed") } /** @@ -463,15 +514,20 @@ constructor( // In the case that we are handling full swipes on the lockscreen, are on the lockscreen, // and the touch is within the horizontal notification band on the screen, do not process // the touch. - if ( - !hubShowing && - (!notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y) || - keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt()) || - lockscreenSmartspaceController.isWithinSmartspaceBounds( - ev.x.toInt(), - ev.y.toInt() - )) - ) { + val touchOnNotifications = + !notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y) + val touchOnUmo = keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt()) + val touchOnSmartspace = + lockscreenSmartspaceController.isWithinSmartspaceBounds(ev.x.toInt(), ev.y.toInt()) + if (!hubShowing && (touchOnNotifications || touchOnUmo || touchOnSmartspace)) { + logger.d({ + "Lockscreen touch ignored: touchOnNotifications: $bool1, touchOnUmo: $bool2, " + + "touchOnSmartspace: $bool3" + }) { + bool1 = touchOnNotifications + bool2 = touchOnUmo + bool3 = touchOnSmartspace + } return false } @@ -487,12 +543,56 @@ constructor( val hubOccluded = anyBouncerShowing || shadeShowingAndConsumingTouches if ((isDown || isMove) && !hubOccluded) { + if (isDown) { + logger.d({ + "Touch started. x: $int1, y: $int2, hubShowing: $bool1, isDreaming: $bool2, " + + "onLockscreen: $bool3" + }) { + int1 = ev.x.toInt() + int2 = ev.y.toInt() + bool1 = hubShowing + bool2 = isDreaming + bool3 = onLockscreen + } + } isTrackingHubTouch = true } if (isTrackingHubTouch) { + // On the lock screen, our touch handlers are not active and we rely on the NSWVC's + // touch handling for gestures on blank areas, which can go up to show the bouncer or + // down to show the notification shade. We see the touches first and they are not + // consumed and cancelled like on the dream or hub so we have to gracefully ignore them + // if the shade or bouncer are handling them. This issue only applies to touches on the + // keyguard itself, once the bouncer or shade are fully open, our logic stops us from + // taking touches. + touchTakenByKeyguardGesture = + (onLockscreen && (shadeConsumingTouches || anyBouncerShowing)).also { + if (it != touchTakenByKeyguardGesture && it) { + logger.d( + "Lock screen touch consumed by shade or bouncer, ignoring " + + "subsequent touches" + ) + } + } if (isUp || isCancel) { + logger.d({ + val endReason = if (bool1) "up" else "cancel" + "Touch ended with $endReason. x: $int1, y: $int2, " + + "shadeConsumingTouches: $bool2, anyBouncerShowing: $bool3" + }) { + int1 = ev.x.toInt() + int2 = ev.y.toInt() + bool1 = isUp + bool2 = shadeConsumingTouches + bool3 = anyBouncerShowing + } isTrackingHubTouch = false + + // Clear out touch taken state to ensure the up/cancel event still gets dispatched + // to the hub. This is necessary as the hub always receives at least the initial + // down even if the shade or bouncer end up handling the touch. + touchTakenByKeyguardGesture = false } return dispatchTouchEvent(ev) } @@ -513,21 +613,8 @@ constructor( return true } try { - // On the lock screen, our touch handlers are not active and we rely on the NSWVC's - // touch handling for gestures on blank areas, which can go up to show the bouncer or - // down to show the notification shade. We see the touches first and they are not - // consumed and cancelled like on the dream or hub so we have to gracefully ignore them - // if the shade or bouncer are handling them. This issue only applies to touches on the - // keyguard itself, once the bouncer or shade are fully open, our logic stops us from - // taking touches. - val touchTaken = onLockscreen && (shadeConsumingTouches || anyBouncerShowing) - - // Only dispatch touches to communal if not already handled or the touch is ending, - // meaning the event is an up or cancel. This is necessary as the hub always receives at - // least the initial down even if the shade or bouncer end up handling the touch. - val dispatchToCommunal = !touchTaken || !isTrackingHubTouch var handled = false - if (dispatchToCommunal) { + if (!touchTakenByKeyguardGesture) { communalContainerWrapper?.dispatchTouchEvent(ev) { if (it) { handled = true 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 b67e111af13d..90c70f26e26b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -58,6 +58,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope +import com.android.systemui.log.logcatLogBuffer import com.android.systemui.media.controls.controller.keyguardMediaController import com.android.systemui.res.R import com.android.systemui.scene.shared.model.sceneDataSourceDelegator @@ -140,7 +141,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { kosmos.sceneDataSourceDelegator, kosmos.notificationStackScrollLayoutController, kosmos.keyguardMediaController, - kosmos.lockscreenSmartspaceController + kosmos.lockscreenSmartspaceController, + logcatLogBuffer("GlanceableHubContainerControllerTest") ) } testableLooper = TestableLooper.get(this) @@ -186,7 +188,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { kosmos.sceneDataSourceDelegator, kosmos.notificationStackScrollLayoutController, kosmos.keyguardMediaController, - kosmos.lockscreenSmartspaceController + kosmos.lockscreenSmartspaceController, + logcatLogBuffer("GlanceableHubContainerControllerTest") ) // First call succeeds. @@ -214,7 +217,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { kosmos.sceneDataSourceDelegator, kosmos.notificationStackScrollLayoutController, kosmos.keyguardMediaController, - kosmos.lockscreenSmartspaceController + kosmos.lockscreenSmartspaceController, + logcatLogBuffer("GlanceableHubContainerControllerTest") ) assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED) @@ -237,7 +241,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { kosmos.sceneDataSourceDelegator, kosmos.notificationStackScrollLayoutController, kosmos.keyguardMediaController, - kosmos.lockscreenSmartspaceController + kosmos.lockscreenSmartspaceController, + logcatLogBuffer("GlanceableHubContainerControllerTest") ) // Only initView without attaching a view as we don't want the flows to start collecting |