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

Commit c7c1d1ac authored by Johannes Gallmann's avatar Johannes Gallmann Committed by Android (Google) Code Review
Browse files

Merge "[SysUI][Floaty] Connect SqueezeEffect with Zoom Effect" into main

parents a3ea3096 efdabfaf
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import com.android.systemui.topwindoweffects.data.repository.fakeSqueezeEffectRe
import com.android.systemui.topwindoweffects.domain.interactor.SqueezeEffectInteractor
import com.android.systemui.topwindoweffects.ui.compose.EffectsWindowRoot
import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel
import java.util.Optional
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import org.junit.Before
@@ -71,6 +72,7 @@ class TopLevelWindowEffectsTest : SysuiTestCase() {
                viewModelFactory = viewModelFactory,
                squeezeEffectInteractor =
                    SqueezeEffectInteractor(squeezeEffectRepository = fakeSqueezeEffectRepository),
                appZoomOutOptional = Optional.empty(),
            )
        }

+5 −0
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@ import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
import com.android.systemui.topwindoweffects.domain.interactor.SqueezeEffectInteractor
import com.android.systemui.topwindoweffects.ui.compose.EffectsWindowRoot
import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel
import com.android.wm.shell.appzoomout.AppZoomOut
import java.util.Optional
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
@@ -50,6 +52,8 @@ constructor(
    private val squeezeEffectInteractor: SqueezeEffectInteractor,
    private val keyEventInteractor: KeyEventInteractor,
    private val viewModelFactory: SqueezeEffectViewModel.Factory,
    //TODO(b/409930584): make AppZoomOut non-optional
    private val appZoomOutOptional: Optional<AppZoomOut>,
) : CoreStartable {

    private var root: EffectsWindowRoot? = null
@@ -108,6 +112,7 @@ constructor(
                            root = null
                        }
                    },
                    appZoomOutOptional = appZoomOutOptional,
                )
            root?.let { rootView ->
                windowManager.addView(rootView, getWindowManagerLayoutParams())
+4 −0
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.AbstractComposeView
import com.android.systemui.compose.ComposeInitializer
import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel
import com.android.wm.shell.appzoomout.AppZoomOut
import java.util.Optional

@SuppressLint("ViewConstructor")
class EffectsWindowRoot(
@@ -32,6 +34,7 @@ class EffectsWindowRoot(
    @DrawableRes private val topRoundedCornerResourceId: Int,
    @DrawableRes private val bottomRoundedCornerResourceId: Int,
    private val physicalPixelDisplaySizeRatio: Float,
    private val appZoomOutOptional: Optional<AppZoomOut>,
) : AbstractComposeView(context) {

    override fun onAttachedToWindow() {
@@ -52,6 +55,7 @@ class EffectsWindowRoot(
            topRoundedCornerResourceId = topRoundedCornerResourceId,
            bottomRoundedCornerResourceId = bottomRoundedCornerResourceId,
            physicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio,
            appZoomOutOptional = appZoomOutOptional,
        )
    }
}
+50 −18
Original line number Diff line number Diff line
@@ -37,12 +37,19 @@ import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.VectorPainter
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel

private val SqueezeEffectMaxThickness = 12.dp
import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel.Companion.ZOOM_OUT_SCALE
import com.android.wm.shell.appzoomout.AppZoomOut
import java.util.Optional

// Defines the amount the squeeze border overlaps the shrinking content.
// This is the difference between the total squeeze thickness and the thickness purely caused by the
// zoom effect. At full progress, this overlap is 8 dp.
private val SqueezeEffectOverlapMaxThickness = 8.dp
private val SqueezeColor = Color.Black

@Composable
@@ -52,6 +59,7 @@ fun SqueezeEffect(
    @DrawableRes bottomRoundedCornerResourceId: Int,
    physicalPixelDisplaySizeRatio: Float,
    onEffectFinished: () -> Unit,
    appZoomOutOptional: Optional<AppZoomOut>,
) {
    val viewModel = rememberViewModel(traceName = "SqueezeEffect") { viewModelFactory.create() }

@@ -105,56 +113,80 @@ fun SqueezeEffect(
        }
    }

    LaunchedEffect(squeezeProgress.value) {
        appZoomOutOptional.ifPresent {
            it.setTopLevelScale(1f - squeezeProgress.value * ZOOM_OUT_SCALE)
        }
    }

    val screenWidth = LocalWindowInfo.current.containerSize.width
    val screenHeight = LocalWindowInfo.current.containerSize.height

    Canvas(modifier = Modifier.fillMaxSize()) {
        if (squeezeProgress.value <= 0) {
            return@Canvas
        }

        val squeezeThickness = SqueezeEffectMaxThickness.toPx() * squeezeProgress.value
        // Calculate the thickness of the squeeze effect borders.
        // The total thickness on each side is composed of two parts:
        // 1. Zoom Thickness: This accounts for the visual space created by the AppZoomOut
        //    effect scaling the content down. It's calculated as half the total reduction
        //    in screen dimension (width or height) caused by scaling (ZOOM_OUT_SCALE),
        //    proportional to the current squeezeProgress. We divide by 2 because the
        //    reduction happens on both sides (left/right or top/bottom).
        // 2. Overlap Thickness: An additional fixed thickness (converted from dp to px)
        //    scaled by the squeezeProgress, designed to make the border slightly overlap
        //    the scaled content for a better visual effect.
        val horizontalZoomThickness = screenWidth * ZOOM_OUT_SCALE * squeezeProgress.value / 2f
        val verticalZoomThickness = screenHeight * ZOOM_OUT_SCALE * squeezeProgress.value / 2f
        val overlapThickness = SqueezeEffectOverlapMaxThickness.toPx() * squeezeProgress.value

        val horizontalSqueezeThickness = horizontalZoomThickness + overlapThickness
        val verticalSqueezeThickness = verticalZoomThickness + overlapThickness

        drawRect(color = SqueezeColor, size = Size(size.width, squeezeThickness))
        drawRect(color = SqueezeColor, size = Size(size.width, verticalSqueezeThickness))

        drawRect(
            color = SqueezeColor,
            topLeft = Offset(0f, size.height - squeezeThickness),
            size = Size(size.width, squeezeThickness),
            topLeft = Offset(0f, size.height - verticalSqueezeThickness),
            size = Size(size.width, verticalSqueezeThickness),
        )

        drawRect(color = SqueezeColor, size = Size(squeezeThickness, size.height))
        drawRect(color = SqueezeColor, size = Size(horizontalSqueezeThickness, size.height))

        drawRect(
            color = SqueezeColor,
            topLeft = Offset(size.width - squeezeThickness, 0f),
            size = Size(squeezeThickness, size.height),
            topLeft = Offset(size.width - horizontalSqueezeThickness, 0f),
            size = Size(horizontalSqueezeThickness, size.height),
        )

        drawTransform(
            dx = squeezeThickness,
            dy = squeezeThickness,
            dx = horizontalSqueezeThickness,
            dy = verticalSqueezeThickness,
            rotation = 0f,
            corner = top,
            displaySizeRatio = physicalPixelDisplaySizeRatio,
        )

        drawTransform(
            dx = size.width - squeezeThickness,
            dy = squeezeThickness,
            dx = size.width - horizontalSqueezeThickness,
            dy = verticalSqueezeThickness,
            rotation = 90f,
            corner = top,
            displaySizeRatio = physicalPixelDisplaySizeRatio,
        )

        drawTransform(
            dx = squeezeThickness,
            dy = size.height - squeezeThickness,
            dx = horizontalSqueezeThickness,
            dy = size.height - verticalSqueezeThickness,
            rotation = 270f,
            corner = bottom,
            displaySizeRatio = physicalPixelDisplaySizeRatio,
        )

        drawTransform(
            dx = size.width - squeezeThickness,
            dy = size.height - squeezeThickness,
            dx = size.width - horizontalSqueezeThickness,
            dy = size.height - verticalSqueezeThickness,
            rotation = 180f,
            corner = bottom,
            displaySizeRatio = physicalPixelDisplaySizeRatio,
+17 −14
Original line number Diff line number Diff line
@@ -23,21 +23,20 @@ import com.android.systemui.lifecycle.Hydrator
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject

class SqueezeEffectViewModel
@AssistedInject
constructor(
    keyEventInteractor: KeyEventInteractor
) : ExclusiveActivatable() {
class SqueezeEffectViewModel @AssistedInject constructor(keyEventInteractor: KeyEventInteractor) :
    ExclusiveActivatable() {
    private val hydrator = Hydrator("SqueezeEffectViewModel.hydrator")

    val isPowerButtonPressed: Boolean by hydrator.hydratedStateOf(
    val isPowerButtonPressed: Boolean by
        hydrator.hydratedStateOf(
            traceName = "isPowerButtonPressed",
        source = keyEventInteractor.isPowerButtonDown
            source = keyEventInteractor.isPowerButtonDown,
        )

    val isPowerButtonLongPressed: Boolean by hydrator.hydratedStateOf(
    val isPowerButtonLongPressed: Boolean by
        hydrator.hydratedStateOf(
            traceName = "isPowerButtonLongPressed",
        source = keyEventInteractor.isPowerButtonLongPressed
            source = keyEventInteractor.isPowerButtonLongPressed,
        )

    override suspend fun onActivated(): Nothing {
@@ -48,4 +47,8 @@ constructor(
    interface Factory {
        fun create(): SqueezeEffectViewModel
    }

    companion object {
        const val ZOOM_OUT_SCALE = 0.05f
    }
}