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

Commit 23a02a0e authored by William Xiao's avatar William Xiao Committed by Android (Google) Code Review
Browse files

Merge "Add logging for glanceable hub touch handling" into main

parents 2c836346 9aa24c21
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