Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9aa24c21 authored by William Xiao's avatar William Xiao
Browse files

Add logging for glanceable hub touch handling

Adds a new CommunalTouchLog buffer and a bunch of touch handling logs
so that we can diagnose touch handling bugs more easily from
bugreports.

Bug: 361091641
Test: N/A, verified logs are correct on device
Flag: com.android.systemui.communal_hub
Change-Id: Ie1c18e92dd82588623a2d55021aed99a459c5d0a
parent 7707c243
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
/*
 * 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
+10 −0
Original line number Diff line number Diff line
@@ -617,6 +617,16 @@ public class LogModule {
        return factory.create("CommunalLog", 250);
    }

    /**
     * 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.
     */
+112 −25
Original line number Diff line number Diff line
@@ -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()
@@ -142,6 +149,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.
     *
@@ -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
+9 −4
Original line number Diff line number Diff line
@@ -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