Loading packages/SystemUI/src/com/android/systemui/screencapture/common/ScreenCaptureUiComponent.kt +4 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.screencapture.common import android.view.Display import android.view.Window import com.android.systemui.screencapture.common.shared.model.ScreenCaptureUiParameters import com.android.systemui.screencapture.common.ui.compose.ScreenCaptureContent import dagger.BindsInstance Loading Loading @@ -54,6 +55,9 @@ interface ScreenCaptureUiComponent { /** [Display] that hosts the Screen Capture UI. */ @BindsInstance fun setDisplay(@ScreenCaptureUi display: Display): Builder /** [Window] that hosts the Screen Capture UI. */ @BindsInstance fun setWindow(@ScreenCaptureUi window: Window?): Builder /** * Builds this [ScreenCaptureUiComponent]. Actual Subcomponent Builders should override this * method with their own version that returns the actual subcomponent type. Loading packages/SystemUI/src/com/android/systemui/screencapture/record/smallscreen/ui/compose/SmallScreenCaptureRecordContent.kt +35 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package com.android.systemui.screencapture.record.smallscreen.ui.compose import android.graphics.Rect as AndroidRect import android.view.ViewTreeObserver.InternalInsetsInfo import android.view.Window import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize Loading Loading @@ -53,15 +56,20 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.android.compose.PlatformIconButton import com.android.compose.modifiers.thenIf import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.res.R import com.android.systemui.screencapture.common.ScreenCaptureUi import com.android.systemui.screencapture.common.ScreenCaptureUiScope import com.android.systemui.screencapture.common.ui.compose.LoadingIcon import com.android.systemui.screencapture.common.ui.compose.PrimaryButton Loading @@ -70,14 +78,17 @@ import com.android.systemui.screencapture.common.ui.compose.loadIcon import com.android.systemui.screencapture.common.ui.viewmodel.DrawableLoaderViewModel import com.android.systemui.screencapture.record.smallscreen.ui.viewmodel.RecordDetailsPopupType import com.android.systemui.screencapture.record.smallscreen.ui.viewmodel.SmallScreenCaptureRecordViewModel import com.android.systemui.util.view.listenToComputeInternalInsets import javax.inject.Inject @ScreenCaptureUiScope @OptIn(ExperimentalMaterial3ExpressiveApi::class) class SmallScreenCaptureRecordContent @Inject constructor(private val viewModelFactory: SmallScreenCaptureRecordViewModel.Factory) : ScreenCaptureContent { constructor( @ScreenCaptureUi private val window: Window?, private val viewModelFactory: SmallScreenCaptureRecordViewModel.Factory, ) : ScreenCaptureContent { @Composable override fun Content() { Loading @@ -86,6 +97,18 @@ constructor(private val viewModelFactory: SmallScreenCaptureRecordViewModel.Fact viewModelFactory.create() } val toolbarRect: AndroidRect = remember(Unit) { AndroidRect() } val detailsRect: AndroidRect = remember(Unit) { AndroidRect() } LaunchedEffect(window) { window ?: return@LaunchedEffect window.decorView.viewTreeObserver.listenToComputeInternalInsets { setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION) touchableRegion.union(toolbarRect) touchableRegion.union(detailsRect) } } Column( verticalArrangement = Arrangement.spacedBy(4.dp), horizontalAlignment = Alignment.CenterHorizontally, Loading @@ -103,6 +126,7 @@ constructor(private val viewModelFactory: SmallScreenCaptureRecordViewModel.Fact shape = FloatingToolbarDefaults.ContainerShape, color = MaterialTheme.colorScheme.surface, shadowElevation = 6.dp, modifier = Modifier.onGloballyPositioned { toolbarRect.set(it.boundsInWindow()) }, ) { Row( modifier = Modifier.height(64.dp).padding(horizontal = 12.dp), Loading Loading @@ -155,7 +179,10 @@ constructor(private val viewModelFactory: SmallScreenCaptureRecordViewModel.Fact color = MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(28.dp), shadowElevation = 2.dp, modifier = Modifier.animateContentSize(), modifier = Modifier.animateContentSize().onGloballyPositioned { detailsRect.set(it.boundsInWindow()) }, ) { AnimatedContent( targetState = viewModel.detailsPopup, Loading Loading @@ -279,3 +306,7 @@ private fun ToolbarPrimaryButton( } } } private fun AndroidRect.set(rect: Rect) { set(rect.left.toInt(), rect.top.toInt(), rect.right.toInt(), rect.bottom.toInt()) } packages/SystemUI/src/com/android/systemui/screencapture/ui/ScreenCaptureUi.kt +14 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.view.KeyEvent import android.view.View import android.view.View.OnKeyListener import android.view.Window import android.view.WindowManager import android.window.OnBackInvokedCallback import android.window.WindowOnBackInvokedDispatcher import androidx.compose.animation.AnimatedVisibility Loading Loading @@ -90,6 +91,18 @@ constructor( private var composeRoot: ComposeView? = null init { with(window) { addFlags( WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED ) addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY) } } override fun onAttach() { require(composeRoot == null) { "The ui is already attached" } Loading Loading @@ -145,6 +158,7 @@ constructor( .setParameters(parameters) .setScope(coroutineScope) .setDisplay(display) .setWindow(window) .build() } Box(modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing)) { Loading packages/SystemUI/src/com/android/systemui/util/view/ViewTreeObserverExt.kt 0 → 100644 +30 −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.util.view import android.view.ViewTreeObserver import kotlinx.coroutines.suspendCancellableCoroutine suspend fun ViewTreeObserver.listenToComputeInternalInsets( fillBounds: ViewTreeObserver.InternalInsetsInfo.() -> Unit ) = suspendCancellableCoroutine<Unit> { continuation -> val listener = ViewTreeObserver.OnComputeInternalInsetsListener { inoutInfo -> fillBounds(inoutInfo) } addOnComputeInternalInsetsListener(listener) continuation.invokeOnCancellation { removeOnComputeInternalInsetsListener(listener) } } packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt +4 −13 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import android.content.Context import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver import android.view.WindowInsets import android.view.accessibility.AccessibilityEvent import androidx.compose.ui.util.lerp Loading @@ -38,6 +37,7 @@ import com.android.systemui.common.ui.view.updateMargin import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.res.R import com.android.systemui.util.kotlin.awaitCancellationThenDispose import com.android.systemui.util.view.listenToComputeInternalInsets import com.android.systemui.volume.dialog.captions.ui.viewmodel.VolumeDialogCaptionsButtonViewModel import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope Loading @@ -55,7 +55,6 @@ import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.scan import kotlinx.coroutines.suspendCancellableCoroutine private const val SPRING_STIFFNESS = 700f private const val SPRING_DAMPING_RATIO = 0.9f Loading Loading @@ -120,7 +119,9 @@ constructor( .launchInTraced("VDVB#isHalfOpened", this) launchTraced("VDVB#viewTreeObserver") { root.viewTreeObserver.listenToComputeInternalInsets() root.viewTreeObserver.listenToComputeInternalInsets { viewModel.fillTouchableBounds(this) } } launchTraced("VDVB#insets") { Loading Loading @@ -214,16 +215,6 @@ constructor( translationX = lerp(width, 0, fraction).toFloat() } private suspend fun ViewTreeObserver.listenToComputeInternalInsets() = suspendCancellableCoroutine<Unit> { continuation -> val listener = ViewTreeObserver.OnComputeInternalInsetsListener { inoutInfo -> viewModel.fillTouchableBounds(inoutInfo) } addOnComputeInternalInsetsListener(listener) continuation.invokeOnCancellation { removeOnComputeInternalInsetsListener(listener) } } private suspend fun View.applyVerticalOffset(offsetPx: Float, shouldAnimate: Boolean) { if (!shouldAnimate) { translationY = offsetPx Loading Loading
packages/SystemUI/src/com/android/systemui/screencapture/common/ScreenCaptureUiComponent.kt +4 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.screencapture.common import android.view.Display import android.view.Window import com.android.systemui.screencapture.common.shared.model.ScreenCaptureUiParameters import com.android.systemui.screencapture.common.ui.compose.ScreenCaptureContent import dagger.BindsInstance Loading Loading @@ -54,6 +55,9 @@ interface ScreenCaptureUiComponent { /** [Display] that hosts the Screen Capture UI. */ @BindsInstance fun setDisplay(@ScreenCaptureUi display: Display): Builder /** [Window] that hosts the Screen Capture UI. */ @BindsInstance fun setWindow(@ScreenCaptureUi window: Window?): Builder /** * Builds this [ScreenCaptureUiComponent]. Actual Subcomponent Builders should override this * method with their own version that returns the actual subcomponent type. Loading
packages/SystemUI/src/com/android/systemui/screencapture/record/smallscreen/ui/compose/SmallScreenCaptureRecordContent.kt +35 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package com.android.systemui.screencapture.record.smallscreen.ui.compose import android.graphics.Rect as AndroidRect import android.view.ViewTreeObserver.InternalInsetsInfo import android.view.Window import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize Loading Loading @@ -53,15 +56,20 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.android.compose.PlatformIconButton import com.android.compose.modifiers.thenIf import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.res.R import com.android.systemui.screencapture.common.ScreenCaptureUi import com.android.systemui.screencapture.common.ScreenCaptureUiScope import com.android.systemui.screencapture.common.ui.compose.LoadingIcon import com.android.systemui.screencapture.common.ui.compose.PrimaryButton Loading @@ -70,14 +78,17 @@ import com.android.systemui.screencapture.common.ui.compose.loadIcon import com.android.systemui.screencapture.common.ui.viewmodel.DrawableLoaderViewModel import com.android.systemui.screencapture.record.smallscreen.ui.viewmodel.RecordDetailsPopupType import com.android.systemui.screencapture.record.smallscreen.ui.viewmodel.SmallScreenCaptureRecordViewModel import com.android.systemui.util.view.listenToComputeInternalInsets import javax.inject.Inject @ScreenCaptureUiScope @OptIn(ExperimentalMaterial3ExpressiveApi::class) class SmallScreenCaptureRecordContent @Inject constructor(private val viewModelFactory: SmallScreenCaptureRecordViewModel.Factory) : ScreenCaptureContent { constructor( @ScreenCaptureUi private val window: Window?, private val viewModelFactory: SmallScreenCaptureRecordViewModel.Factory, ) : ScreenCaptureContent { @Composable override fun Content() { Loading @@ -86,6 +97,18 @@ constructor(private val viewModelFactory: SmallScreenCaptureRecordViewModel.Fact viewModelFactory.create() } val toolbarRect: AndroidRect = remember(Unit) { AndroidRect() } val detailsRect: AndroidRect = remember(Unit) { AndroidRect() } LaunchedEffect(window) { window ?: return@LaunchedEffect window.decorView.viewTreeObserver.listenToComputeInternalInsets { setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION) touchableRegion.union(toolbarRect) touchableRegion.union(detailsRect) } } Column( verticalArrangement = Arrangement.spacedBy(4.dp), horizontalAlignment = Alignment.CenterHorizontally, Loading @@ -103,6 +126,7 @@ constructor(private val viewModelFactory: SmallScreenCaptureRecordViewModel.Fact shape = FloatingToolbarDefaults.ContainerShape, color = MaterialTheme.colorScheme.surface, shadowElevation = 6.dp, modifier = Modifier.onGloballyPositioned { toolbarRect.set(it.boundsInWindow()) }, ) { Row( modifier = Modifier.height(64.dp).padding(horizontal = 12.dp), Loading Loading @@ -155,7 +179,10 @@ constructor(private val viewModelFactory: SmallScreenCaptureRecordViewModel.Fact color = MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(28.dp), shadowElevation = 2.dp, modifier = Modifier.animateContentSize(), modifier = Modifier.animateContentSize().onGloballyPositioned { detailsRect.set(it.boundsInWindow()) }, ) { AnimatedContent( targetState = viewModel.detailsPopup, Loading Loading @@ -279,3 +306,7 @@ private fun ToolbarPrimaryButton( } } } private fun AndroidRect.set(rect: Rect) { set(rect.left.toInt(), rect.top.toInt(), rect.right.toInt(), rect.bottom.toInt()) }
packages/SystemUI/src/com/android/systemui/screencapture/ui/ScreenCaptureUi.kt +14 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.view.KeyEvent import android.view.View import android.view.View.OnKeyListener import android.view.Window import android.view.WindowManager import android.window.OnBackInvokedCallback import android.window.WindowOnBackInvokedDispatcher import androidx.compose.animation.AnimatedVisibility Loading Loading @@ -90,6 +91,18 @@ constructor( private var composeRoot: ComposeView? = null init { with(window) { addFlags( WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED ) addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY) } } override fun onAttach() { require(composeRoot == null) { "The ui is already attached" } Loading Loading @@ -145,6 +158,7 @@ constructor( .setParameters(parameters) .setScope(coroutineScope) .setDisplay(display) .setWindow(window) .build() } Box(modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing)) { Loading
packages/SystemUI/src/com/android/systemui/util/view/ViewTreeObserverExt.kt 0 → 100644 +30 −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.util.view import android.view.ViewTreeObserver import kotlinx.coroutines.suspendCancellableCoroutine suspend fun ViewTreeObserver.listenToComputeInternalInsets( fillBounds: ViewTreeObserver.InternalInsetsInfo.() -> Unit ) = suspendCancellableCoroutine<Unit> { continuation -> val listener = ViewTreeObserver.OnComputeInternalInsetsListener { inoutInfo -> fillBounds(inoutInfo) } addOnComputeInternalInsetsListener(listener) continuation.invokeOnCancellation { removeOnComputeInternalInsetsListener(listener) } }
packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt +4 −13 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import android.content.Context import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver import android.view.WindowInsets import android.view.accessibility.AccessibilityEvent import androidx.compose.ui.util.lerp Loading @@ -38,6 +37,7 @@ import com.android.systemui.common.ui.view.updateMargin import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.res.R import com.android.systemui.util.kotlin.awaitCancellationThenDispose import com.android.systemui.util.view.listenToComputeInternalInsets import com.android.systemui.volume.dialog.captions.ui.viewmodel.VolumeDialogCaptionsButtonViewModel import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope Loading @@ -55,7 +55,6 @@ import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.scan import kotlinx.coroutines.suspendCancellableCoroutine private const val SPRING_STIFFNESS = 700f private const val SPRING_DAMPING_RATIO = 0.9f Loading Loading @@ -120,7 +119,9 @@ constructor( .launchInTraced("VDVB#isHalfOpened", this) launchTraced("VDVB#viewTreeObserver") { root.viewTreeObserver.listenToComputeInternalInsets() root.viewTreeObserver.listenToComputeInternalInsets { viewModel.fillTouchableBounds(this) } } launchTraced("VDVB#insets") { Loading Loading @@ -214,16 +215,6 @@ constructor( translationX = lerp(width, 0, fraction).toFloat() } private suspend fun ViewTreeObserver.listenToComputeInternalInsets() = suspendCancellableCoroutine<Unit> { continuation -> val listener = ViewTreeObserver.OnComputeInternalInsetsListener { inoutInfo -> viewModel.fillTouchableBounds(inoutInfo) } addOnComputeInternalInsetsListener(listener) continuation.invokeOnCancellation { removeOnComputeInternalInsetsListener(listener) } } private suspend fun View.applyVerticalOffset(offsetPx: Float, shouldAnimate: Boolean) { if (!shouldAnimate) { translationY = offsetPx Loading