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

Commit c4757391 authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Fix scrim clipping

It was not reacting properly to changes in enabled.

Also, add a table log for future debug.

Logging should be done in bg thread for changes that happen frequently,
to avoid locking LogBuffer

Test: manual
Test: logs
Test: atest PlatformScenarioTests:QSMediaControllerDismissCarousel
Fixes: 406248870
Flag: com.android.systemui.qs_ui_refactor_compose_fragment
Change-Id: Id4e9166b7e931ceeb8147403cefa3f3e57b63e3b
parent 120da50d
Loading
Loading
Loading
Loading
+85 −56
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import android.graphics.PointF
import android.graphics.Rect
import android.os.Bundle
import android.os.Trace
import android.util.IndentingPrintWriter
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
@@ -60,7 +59,6 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
@@ -95,6 +93,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launchTraced
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
@@ -117,11 +116,13 @@ import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
import com.android.systemui.brightness.ui.compose.ContainerColors
import com.android.systemui.compose.modifiers.sysUiResTagContainer
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyboard.shortcut.ui.composable.InteractionsConfig
import com.android.systemui.keyboard.shortcut.ui.composable.ProvideShortcutHelperIndication
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.setSnapshotBinding
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.qs.QSContainerController
@@ -136,6 +137,7 @@ import com.android.systemui.qs.composefragment.ui.toEditMode
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.panels.shared.model.QSFragmentComposeClippingTableLog
import com.android.systemui.qs.panels.ui.compose.EditMode
import com.android.systemui.qs.panels.ui.compose.QuickQuickSettings
import com.android.systemui.qs.panels.ui.compose.TileGrid
@@ -148,12 +150,14 @@ import com.android.systemui.util.LifecycleFragment
import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.asIndenting
import com.android.systemui.util.children
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.printSection
import com.android.systemui.util.println
import java.io.PrintWriter
import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
@@ -168,7 +172,9 @@ class QSFragmentCompose
@Inject
constructor(
    private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory,
    @QSFragmentComposeClippingTableLog private val qsClippingTableLogBuffer: TableLogBuffer,
    private val dumpManager: DumpManager,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) : LifecycleFragment(), QS, Dumpable {

    private val scrollListener = MutableStateFlow<QS.ScrollListener?>(null)
@@ -184,20 +190,8 @@ constructor(
    private val composeViewPositionOnScreen = Rect()
    private val scrollState = ScrollState(0)
    private val locationTemp = IntArray(2)

    // Inside object for namespacing
    private val notificationScrimClippingParams =
        object {
            var isEnabled by mutableStateOf(false)
            var params by mutableStateOf(NotificationScrimClipParams())

            fun dump(pw: IndentingPrintWriter) {
                pw.printSection("NotificationScrimClippingParams") {
                    pw.println("isEnabled", isEnabled)
                    pw.println("params", params)
                }
            }
        }
    private val containerView: FrameLayoutTouchPassthrough?
        get() = view as? FrameLayoutTouchPassthrough

    override fun onStart() {
        super.onStart()
@@ -261,12 +255,12 @@ constructor(
        val frame =
            FrameLayoutTouchPassthrough(
                context,
                { notificationScrimClippingParams.isEnabled },
                snapshotFlow { notificationScrimClippingParams.params },
                // Only allow scrolling when we are fully expanded. That way, we don't intercept
                // swipes in lockscreen (when somehow QS is receiving touches).
                canScrollQs,
                viewModel::emitMotionEventForFalsingSwipeNested,
                qsClippingTableLogBuffer,
                backgroundDispatcher,
            )
        frame.addView(
            composeView,
@@ -559,8 +553,8 @@ constructor(
        visible: Boolean,
        fullWidth: Boolean,
    ) {
        notificationScrimClippingParams.isEnabled = visible
        notificationScrimClippingParams.params =
        containerView?.clipData =
            visible to
                NotificationScrimClipParams(
                    top,
                    bottom,
@@ -937,9 +931,15 @@ constructor(
        }
    }

    private val clipData
        get() = containerView?.clipData

    override fun dump(pw: PrintWriter, args: Array<out String>) {
        pw.asIndenting().run {
            notificationScrimClippingParams.dump(this)
            printSection("NotificationScrimClippingParams") {
                println("isEnabled", clipData?.first)
                println("params", clipData?.second)
            }
            printSection("QQS positioning") {
                println("qqsHeight", "${headerHeight}px")
                println("qqsTop", "${headerTop}px")
@@ -1108,65 +1108,89 @@ private const val EDIT_MODE_TIME_MILLIS = 500
 */
private class FrameLayoutTouchPassthrough(
    context: Context,
    private val clippingEnabledProvider: () -> Boolean,
    private val clippingParams: Flow<NotificationScrimClipParams>,
    private val canScrollQs: CanScrollQs,
    private val emitMotionEventForFalsing: () -> Unit,
    private val logBuffer: TableLogBuffer,
    private val backgroundDispatcher: CoroutineDispatcher,
) : FrameLayout(context) {

    init {
        repeatWhenAttached {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                clippingParams.collect { currentClipParams = it }
                launchTraced("FrameLayoutTouchPassthrough.logs", backgroundDispatcher) {
                    _clipData
                        .pairwise(initialValue = false to NotificationScrimClipParams())
                        .collect { (prev, new) ->
                            logBuffer.logDiffs(
                                columnPrefix = PREFIX_PARAMS,
                                prevVal = prev.second,
                                newVal = new.second,
                            )
                            if (prev.first != new.first) {
                                logBuffer.logChange(
                                    columnName = COL_CLIP_ENABLED,
                                    value = new.first,
                                    isInitial = false,
                                )
                            }
                        }
                }
            }

    private val currentClippingPath = Path()
    private var lastWidth = -1
        set(value) {
            if (field != value) {
                field = value
                updateClippingPath()
        }
    }

    private var currentClipParams = NotificationScrimClipParams()
    private val currentClippingPath = Path()

    private val _clipData = MutableStateFlow(false to NotificationScrimClipParams())

    // [first] is enabled and [second] is the clipping params
    var clipData
        get() = _clipData.value
        set(value) {
            if (field != value) {
                field = value
                updateClippingPath()
            if (_clipData.value != value) {
                _clipData.value = value
                dirtyClipData = true
                invalidate()
            }
        }

    private var dirtyClipData = false

    private val clipEnabled
        get() = clipData.first

    private val clipParams
        get() = clipData.second

    private fun updateClippingPath() {
        currentClippingPath.rewind()
        if (clippingEnabledProvider()) {
            val right = width + currentClipParams.rightInset
            val left = -currentClipParams.leftInset
            val top = currentClipParams.top
            val bottom = currentClipParams.bottom
        val (clipEnabled, clipParams) = clipData
        if (clipEnabled) {
            val right = width + clipParams.rightInset
            val left = -clipParams.leftInset
            val top = clipParams.top
            val bottom = clipParams.bottom
            currentClippingPath.addRoundRect(
                left.toFloat(),
                top.toFloat(),
                right.toFloat(),
                bottom.toFloat(),
                currentClipParams.radius.toFloat(),
                currentClipParams.radius.toFloat(),
                clipParams.radius.toFloat(),
                clipParams.radius.toFloat(),
                Path.Direction.CW,
            )
        }
        invalidate()
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        lastWidth = right - left
    }

    override fun dispatchDraw(canvas: Canvas) {
        if (dirtyClipData) {
            dirtyClipData = false
            updateClippingPath()
        }
        if (!currentClippingPath.isEmpty) {
            canvas.translate(0f, -translationY)
            canvas.clipOutPath(currentClippingPath)
            canvas.translate(0f, translationY)
        }
        super.dispatchDraw(canvas)
    }
@@ -1177,7 +1201,7 @@ private class FrameLayoutTouchPassthrough(
        child: View?,
        outLocalPoint: PointF?,
    ): Boolean {
        return if (clippingEnabledProvider() && y + translationY > currentClipParams.top) {
        return if (clipEnabled && y + translationY > clipParams.top) {
            false
        } else {
            super.isTransformedTouchPointInView(x, y, child, outLocalPoint)
@@ -1244,6 +1268,11 @@ private class FrameLayoutTouchPassthrough(
        }
        return super.onInterceptTouchEvent(ev)
    }

    private companion object {
        const val COL_CLIP_ENABLED = "enabled"
        const val PREFIX_PARAMS = "params"
    }
}

private interface CanScrollQs {
+38 −1
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.systemui.qs.composefragment.ui

import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger

/** Params for [notificationScrimClip]. */
data class NotificationScrimClipParams(
    val top: Int = 0,
@@ -23,4 +26,38 @@ data class NotificationScrimClipParams(
    val leftInset: Int = 0,
    val rightInset: Int = 0,
    val radius: Int = 0,
)
) : Diffable<NotificationScrimClipParams> {
    override fun logDiffs(prevVal: NotificationScrimClipParams, row: TableRowLogger) {
        if (top != prevVal.top) {
            row.logChange(Columns.COL_TOP, top)
        }
        if (bottom != prevVal.bottom) {
            row.logChange(Columns.COL_BOTTOM, bottom)
        }
        if (leftInset != prevVal.leftInset) {
            row.logChange(Columns.COL_LEFT_INSET, leftInset)
        }
        if (rightInset != prevVal.rightInset) {
            row.logChange(Columns.COL_RIGHT_INSET, rightInset)
        }
        if (radius != prevVal.radius) {
            row.logChange(Columns.COL_RADIUS, radius)
        }
    }

    override fun logFull(row: TableRowLogger) {
        row.logChange(Columns.COL_TOP, top)
        row.logChange(Columns.COL_BOTTOM, bottom)
        row.logChange(Columns.COL_LEFT_INSET, leftInset)
        row.logChange(Columns.COL_RIGHT_INSET, rightInset)
        row.logChange(Columns.COL_RADIUS, radius)
    }
}

private object Columns {
    const val COL_TOP = "top"
    const val COL_BOTTOM = "bottom"
    const val COL_LEFT_INSET = "left_inset"
    const val COL_RIGHT_INSET = "right_inset"
    const val COL_RADIUS = "radius"
}
+1 −1
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 * 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.
+10 −0
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.qs.panels.data.repository.AppIconRepository
import com.android.systemui.qs.panels.data.repository.AppIconRepositoryImpl
import com.android.systemui.qs.panels.domain.interactor.EditTilesResetInteractor
@@ -29,6 +31,7 @@ import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.panels.shared.model.QSFragmentComposeClippingTableLog
import com.android.systemui.qs.panels.ui.compose.GridLayout
import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout
@@ -75,6 +78,13 @@ interface PanelsModuleBase {
            return factory.create("PanelsLog", 50)
        }

        @Provides
        @SysUISingleton
        @QSFragmentComposeClippingTableLog
        fun providesQSFragmentComposeClippingLog(factory: TableLogBufferFactory): TableLogBuffer {
            return factory.create("QSFragmentComposeClippingTableLog", 100)
        }

        @Provides
        @IntoSet
        fun provideGridLayout(gridLayout: InfiniteGridLayout): Pair<GridLayoutType, GridLayout> {
+24 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.qs.panels.shared.model

import javax.inject.Qualifier

@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class QSFragmentComposeClippingTableLog