Loading packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +64 −27 Original line number Diff line number Diff line Loading @@ -118,6 +118,7 @@ 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 Loading @@ -132,6 +133,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 Loading Loading @@ -163,6 +165,7 @@ class QSFragmentCompose @Inject constructor( private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory, @QSFragmentComposeClippingTableLog private val qsClippingTableLogBuffer: TableLogBuffer, private val dumpManager: DumpManager, ) : LifecycleFragment(), QS, Dumpable { Loading @@ -183,8 +186,12 @@ constructor( // Inside object for namespacing private val notificationScrimClippingParams = object { var isEnabled by mutableStateOf(false) var params by mutableStateOf(NotificationScrimClipParams()) var clipData by mutableStateOf(false to NotificationScrimClipParams()) private val isEnabled get() = clipData.first private val params get() = clipData.second fun dump(pw: IndentingPrintWriter) { pw.printSection("NotificationScrimClippingParams") { Loading Loading @@ -241,12 +248,12 @@ constructor( val frame = FrameLayoutTouchPassthrough( context, { notificationScrimClippingParams.isEnabled }, snapshotFlow { notificationScrimClippingParams.params }, snapshotFlow { notificationScrimClippingParams.clipData }, // Only allow scrolling when we are fully expanded. That way, we don't intercept // swipes in lockscreen (when somehow QS is receiving touches). { (scrollState.canScrollForward && viewModel.isQsFullyExpanded) || isCustomizing }, viewModel::emitMotionEventForFalsingSwipeNested, qsClippingTableLogBuffer, ) frame.addView( composeView, Loading Loading @@ -520,8 +527,8 @@ constructor( visible: Boolean, fullWidth: Boolean, ) { notificationScrimClippingParams.isEnabled = visible notificationScrimClippingParams.params = notificationScrimClippingParams.clipData = visible to NotificationScrimClipParams( top, bottom, Loading Loading @@ -1056,17 +1063,15 @@ private const val EDIT_MODE_TIME_MILLIS = 500 */ private class FrameLayoutTouchPassthrough( context: Context, private val clippingEnabledProvider: () -> Boolean, private val clippingParams: Flow<NotificationScrimClipParams>, private val clippingData: Flow<Pair<Boolean, NotificationScrimClipParams>>, private val canScrollForwardQs: () -> Boolean, private val emitMotionEventForFalsing: () -> Unit, private val logBuffer: TableLogBuffer, ) : FrameLayout(context) { init { repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { clippingParams.collect { currentClipParams = it } } repeatOnLifecycle(Lifecycle.State.STARTED) { clippingData.collect { clipData = it } } } } Loading @@ -1074,33 +1079,53 @@ private class FrameLayoutTouchPassthrough( private var lastWidth = -1 set(value) { if (field != value) { logBuffer.logChange(columnName = COL_WIDTH, value = value, isInitial = false) field = value updateClippingPath() } } private var currentClipParams = NotificationScrimClipParams() // [first] is enabled and [second] is the clipping params private var clipData = false to NotificationScrimClipParams() set(value) { if (field != value) { logBuffer.logDiffs( columnPrefix = PREFIX_PARAMS, prevVal = field.second, newVal = value.second, ) if (field.first != value.first) { logBuffer.logChange( columnName = COL_CLIP_ENABLED, value = value.first, isInitial = false, ) } field = value updateClippingPath() } } 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 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, ) } Loading @@ -1114,7 +1139,12 @@ private class FrameLayoutTouchPassthrough( override fun dispatchDraw(canvas: Canvas) { if (!currentClippingPath.isEmpty) { logBuffer.logChange(columnName = COL_CLIP_APPLIED, value = true, isInitial = false) canvas.translate(0f, -translationY) canvas.clipOutPath(currentClippingPath) canvas.translate(0f, translationY) } else { logBuffer.logChange(columnName = COL_CLIP_APPLIED, value = false, isInitial = false) } super.dispatchDraw(canvas) } Loading @@ -1125,7 +1155,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) Loading Loading @@ -1187,6 +1217,13 @@ private class FrameLayoutTouchPassthrough( } return super.onInterceptTouchEvent(ev) } private companion object { const val COL_CLIP_ENABLED = "enabled" const val COL_CLIP_APPLIED = "applied" const val COL_WIDTH = "width" const val PREFIX_PARAMS = "params" } } private fun Modifier.gesturesDisabled(disabled: Boolean) = Loading packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClipParams.kt +38 −1 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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" } packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt +10 −0 Original line number Diff line number Diff line Loading @@ -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.DefaultLargeTilesRepository import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositoryImpl import com.android.systemui.qs.panels.domain.interactor.EditTilesResetInteractor Loading @@ -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 Loading Loading @@ -74,6 +77,13 @@ interface PanelsModule { 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> { Loading packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/QSFragmentComposeClippingTableLog.kt 0 → 100644 +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 Loading
packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +64 −27 Original line number Diff line number Diff line Loading @@ -118,6 +118,7 @@ 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 Loading @@ -132,6 +133,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 Loading Loading @@ -163,6 +165,7 @@ class QSFragmentCompose @Inject constructor( private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory, @QSFragmentComposeClippingTableLog private val qsClippingTableLogBuffer: TableLogBuffer, private val dumpManager: DumpManager, ) : LifecycleFragment(), QS, Dumpable { Loading @@ -183,8 +186,12 @@ constructor( // Inside object for namespacing private val notificationScrimClippingParams = object { var isEnabled by mutableStateOf(false) var params by mutableStateOf(NotificationScrimClipParams()) var clipData by mutableStateOf(false to NotificationScrimClipParams()) private val isEnabled get() = clipData.first private val params get() = clipData.second fun dump(pw: IndentingPrintWriter) { pw.printSection("NotificationScrimClippingParams") { Loading Loading @@ -241,12 +248,12 @@ constructor( val frame = FrameLayoutTouchPassthrough( context, { notificationScrimClippingParams.isEnabled }, snapshotFlow { notificationScrimClippingParams.params }, snapshotFlow { notificationScrimClippingParams.clipData }, // Only allow scrolling when we are fully expanded. That way, we don't intercept // swipes in lockscreen (when somehow QS is receiving touches). { (scrollState.canScrollForward && viewModel.isQsFullyExpanded) || isCustomizing }, viewModel::emitMotionEventForFalsingSwipeNested, qsClippingTableLogBuffer, ) frame.addView( composeView, Loading Loading @@ -520,8 +527,8 @@ constructor( visible: Boolean, fullWidth: Boolean, ) { notificationScrimClippingParams.isEnabled = visible notificationScrimClippingParams.params = notificationScrimClippingParams.clipData = visible to NotificationScrimClipParams( top, bottom, Loading Loading @@ -1056,17 +1063,15 @@ private const val EDIT_MODE_TIME_MILLIS = 500 */ private class FrameLayoutTouchPassthrough( context: Context, private val clippingEnabledProvider: () -> Boolean, private val clippingParams: Flow<NotificationScrimClipParams>, private val clippingData: Flow<Pair<Boolean, NotificationScrimClipParams>>, private val canScrollForwardQs: () -> Boolean, private val emitMotionEventForFalsing: () -> Unit, private val logBuffer: TableLogBuffer, ) : FrameLayout(context) { init { repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { clippingParams.collect { currentClipParams = it } } repeatOnLifecycle(Lifecycle.State.STARTED) { clippingData.collect { clipData = it } } } } Loading @@ -1074,33 +1079,53 @@ private class FrameLayoutTouchPassthrough( private var lastWidth = -1 set(value) { if (field != value) { logBuffer.logChange(columnName = COL_WIDTH, value = value, isInitial = false) field = value updateClippingPath() } } private var currentClipParams = NotificationScrimClipParams() // [first] is enabled and [second] is the clipping params private var clipData = false to NotificationScrimClipParams() set(value) { if (field != value) { logBuffer.logDiffs( columnPrefix = PREFIX_PARAMS, prevVal = field.second, newVal = value.second, ) if (field.first != value.first) { logBuffer.logChange( columnName = COL_CLIP_ENABLED, value = value.first, isInitial = false, ) } field = value updateClippingPath() } } 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 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, ) } Loading @@ -1114,7 +1139,12 @@ private class FrameLayoutTouchPassthrough( override fun dispatchDraw(canvas: Canvas) { if (!currentClippingPath.isEmpty) { logBuffer.logChange(columnName = COL_CLIP_APPLIED, value = true, isInitial = false) canvas.translate(0f, -translationY) canvas.clipOutPath(currentClippingPath) canvas.translate(0f, translationY) } else { logBuffer.logChange(columnName = COL_CLIP_APPLIED, value = false, isInitial = false) } super.dispatchDraw(canvas) } Loading @@ -1125,7 +1155,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) Loading Loading @@ -1187,6 +1217,13 @@ private class FrameLayoutTouchPassthrough( } return super.onInterceptTouchEvent(ev) } private companion object { const val COL_CLIP_ENABLED = "enabled" const val COL_CLIP_APPLIED = "applied" const val COL_WIDTH = "width" const val PREFIX_PARAMS = "params" } } private fun Modifier.gesturesDisabled(disabled: Boolean) = Loading
packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClipParams.kt +38 −1 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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" }
packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt +10 −0 Original line number Diff line number Diff line Loading @@ -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.DefaultLargeTilesRepository import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositoryImpl import com.android.systemui.qs.panels.domain.interactor.EditTilesResetInteractor Loading @@ -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 Loading Loading @@ -74,6 +77,13 @@ interface PanelsModule { 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> { Loading
packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/QSFragmentComposeClippingTableLog.kt 0 → 100644 +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