diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java index bb888189d4..1c1fbd8d64 100644 --- a/quickstep/src/com/android/quickstep/util/TransformParams.java +++ b/quickstep/src/com/android/quickstep/util/TransformParams.java @@ -22,10 +22,14 @@ import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.TransitionInfo; +import androidx.annotation.VisibleForTesting; + import com.android.quickstep.RemoteAnimationTargets; import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties; import com.android.window.flags.Flags; +import java.util.function.Supplier; + public class TransformParams { public static FloatProperty PROGRESS = @@ -60,15 +64,23 @@ public class TransformParams { private float mCornerRadius; private RemoteAnimationTargets mTargetSet; private TransitionInfo mTransitionInfo; + private boolean mCornerRadiusIsOverridden; private SurfaceTransactionApplier mSyncTransactionApplier; + private Supplier mSurfaceTransactionSupplier; private BuilderProxy mHomeBuilderProxy = BuilderProxy.ALWAYS_VISIBLE; private BuilderProxy mBaseBuilderProxy = BuilderProxy.ALWAYS_VISIBLE; public TransformParams() { + this(SurfaceTransaction::new); + } + + @VisibleForTesting + public TransformParams(Supplier surfaceTransactionSupplier) { mProgress = 0; mTargetAlpha = 1; mCornerRadius = -1; + mSurfaceTransactionSupplier = surfaceTransactionSupplier; } /** @@ -115,6 +127,7 @@ public class TransformParams { */ public TransformParams setTransitionInfo(TransitionInfo transitionInfo) { mTransitionInfo = transitionInfo; + mCornerRadiusIsOverridden = false; return this; } @@ -148,7 +161,7 @@ public class TransformParams { /** Builds the SurfaceTransaction from the given BuilderProxy params. */ public SurfaceTransaction createSurfaceParams(BuilderProxy proxy) { RemoteAnimationTargets targets = mTargetSet; - SurfaceTransaction transaction = new SurfaceTransaction(); + SurfaceTransaction transaction = mSurfaceTransactionSupplier.get(); if (targets == null) { return transaction; } @@ -166,8 +179,13 @@ public class TransformParams { targetProxy.onBuildTargetParams(builder, app, this); // Override the corner radius for {@code app} with the leash used by Shell, so that it // doesn't interfere with the window clip and corner radius applied here. - overrideChangeLeashCornerRadiusToZero(app, transaction.getTransaction()); + // Only override the corner radius once - so that we don't accidentally override at the + // end of transition after WM Shell has reset the corner radius of the task. + if (!mCornerRadiusIsOverridden) { + overrideFreeformChangeLeashCornerRadiusToZero(app, transaction.getTransaction()); + } } + mCornerRadiusIsOverridden = true; // always put wallpaper layer to bottom. final int wallpaperLength = targets.wallpapers != null ? targets.wallpapers.length : 0; @@ -178,11 +196,15 @@ public class TransformParams { return transaction; } - private void overrideChangeLeashCornerRadiusToZero( + private void overrideFreeformChangeLeashCornerRadiusToZero( RemoteAnimationTarget app, SurfaceControl.Transaction transaction) { if (!Flags.enableDesktopRecentsTransitionsCornersBugfix()) { return; } + if (app.taskInfo == null || !app.taskInfo.isFreeform()) { + return; + } + SurfaceControl changeLeash = getChangeLeashForApp(app); if (changeLeash != null) { transaction.setCornerRadius(changeLeash, 0); diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TransformParamsTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TransformParamsTest.kt new file mode 100644 index 0000000000..6dbb667a22 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TransformParamsTest.kt @@ -0,0 +1,191 @@ +/* + * 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.quickstep.util + +import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.view.RemoteAnimationTarget +import android.view.SurfaceControl +import android.view.WindowManager.TRANSIT_OPEN +import android.window.TransitionInfo +import android.window.TransitionInfo.Change +import android.window.TransitionInfo.FLAG_NONE +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.quickstep.RemoteAnimationTargets +import com.android.quickstep.util.TransformParams.BuilderProxy.NO_OP +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TransformParamsTest { + private val surfaceTransaction = mock() + private val transaction = mock() + private val transformParams = TransformParams(::surfaceTransaction) + + private val freeformTaskInfo1 = + createTaskInfo(taskId = 1, windowingMode = WINDOWING_MODE_FREEFORM) + private val freeformTaskInfo2 = + createTaskInfo(taskId = 2, windowingMode = WINDOWING_MODE_FREEFORM) + private val fullscreenTaskInfo1 = + createTaskInfo(taskId = 1, windowingMode = WINDOWING_MODE_FULLSCREEN) + + @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule() + + @Before + fun setUp() { + whenever(surfaceTransaction.transaction).thenReturn(transaction) + whenever(surfaceTransaction.forSurface(anyOrNull())) + .thenReturn(mock()) + transformParams.setCornerRadius(CORNER_RADIUS) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX) + fun createSurfaceParams_freeformTasks_overridesCornerRadius() { + val transitionInfo = TransitionInfo(TRANSIT_OPEN, FLAG_NONE) + val leash1 = mock() + val leash2 = mock() + transitionInfo.addChange(createChange(freeformTaskInfo1, leash = leash1)) + transitionInfo.addChange(createChange(freeformTaskInfo2, leash = leash2)) + transformParams.setTransitionInfo(transitionInfo) + transformParams.setTargetSet(createTargetSet(listOf(freeformTaskInfo1, freeformTaskInfo2))) + + transformParams.createSurfaceParams(NO_OP) + + verify(transaction).setCornerRadius(leash1, 0f) + verify(transaction).setCornerRadius(leash2, 0f) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX) + fun createSurfaceParams_freeformTasks_overridesCornerRadiusOnlyOnce() { + val transitionInfo = TransitionInfo(TRANSIT_OPEN, FLAG_NONE) + val leash1 = mock() + val leash2 = mock() + transitionInfo.addChange(createChange(freeformTaskInfo1, leash = leash1)) + transitionInfo.addChange(createChange(freeformTaskInfo2, leash = leash2)) + transformParams.setTransitionInfo(transitionInfo) + transformParams.setTargetSet(createTargetSet(listOf(freeformTaskInfo1, freeformTaskInfo2))) + transformParams.createSurfaceParams(NO_OP) + + transformParams.createSurfaceParams(NO_OP) + + verify(transaction).setCornerRadius(leash1, 0f) + verify(transaction).setCornerRadius(leash2, 0f) + } + + @Test + @DisableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX) + fun createSurfaceParams_flagDisabled_doesntOverrideCornerRadius() { + val transitionInfo = TransitionInfo(TRANSIT_OPEN, FLAG_NONE) + val leash1 = mock() + val leash2 = mock() + transitionInfo.addChange(createChange(freeformTaskInfo1, leash = leash1)) + transitionInfo.addChange(createChange(freeformTaskInfo2, leash = leash2)) + transformParams.setTransitionInfo(transitionInfo) + transformParams.setTargetSet(createTargetSet(listOf(freeformTaskInfo1, freeformTaskInfo2))) + + transformParams.createSurfaceParams(NO_OP) + + verify(transaction, never()).setCornerRadius(leash1, 0f) + verify(transaction, never()).setCornerRadius(leash2, 0f) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX) + fun createSurfaceParams_fullscreenTasks_doesntOverrideCornerRadius() { + val transitionInfo = TransitionInfo(TRANSIT_OPEN, FLAG_NONE) + val leash = mock() + transitionInfo.addChange(createChange(fullscreenTaskInfo1, leash = leash)) + transformParams.setTransitionInfo(transitionInfo) + transformParams.setTargetSet(createTargetSet(listOf(fullscreenTaskInfo1))) + + transformParams.createSurfaceParams(NO_OP) + + verify(transaction, never()).setCornerRadius(leash, 0f) + } + + private fun createTargetSet(taskInfos: List): RemoteAnimationTargets { + val remoteAnimationTargets = mutableListOf() + taskInfos.map { remoteAnimationTargets.add(createRemoteAnimationTarget(it)) } + return RemoteAnimationTargets( + remoteAnimationTargets.toTypedArray(), + /* wallpapers= */ null, + /* nonApps= */ null, + /* targetMode= */ TRANSIT_OPEN, + ) + } + + private fun createRemoteAnimationTarget(taskInfo: RunningTaskInfo): RemoteAnimationTarget { + val windowConfig = mock() + whenever(windowConfig.activityType).thenReturn(ACTIVITY_TYPE_STANDARD) + return RemoteAnimationTarget( + taskInfo.taskId, + /* mode= */ TRANSIT_OPEN, + /* leash= */ null, + /* isTranslucent= */ false, + /* clipRect= */ null, + /* contentInsets= */ null, + /* prefixOrderIndex= */ 0, + /* position= */ null, + /* localBounds= */ null, + /* screenSpaceBounds= */ null, + windowConfig, + /* isNotInRecents= */ false, + /* startLeash= */ null, + /* startBounds= */ null, + taskInfo, + /* allowEnterPip= */ false, + ) + } + + private fun createTaskInfo(taskId: Int, windowingMode: Int): RunningTaskInfo { + val taskInfo = RunningTaskInfo() + taskInfo.taskId = taskId + taskInfo.configuration.windowConfiguration.windowingMode = windowingMode + return taskInfo + } + + private fun createChange(taskInfo: RunningTaskInfo, leash: SurfaceControl): Change { + val taskInfo = createTaskInfo(taskInfo.taskId, taskInfo.windowingMode) + val change = Change(taskInfo.token, mock()) + change.mode = TRANSIT_OPEN + change.taskInfo = taskInfo + change.leash = leash + return change + } + + private companion object { + private const val CORNER_RADIUS = 30f + } +}