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

Commit 5cc5e8e8 authored by Fabian Kozynski's avatar Fabian Kozynski Committed by Android (Google) Code Review
Browse files

Merge "Fix scrim clipping" into main

parents 6bbb5886 c4757391
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