diff options
11 files changed, 260 insertions, 162 deletions
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java index e92c1eb81e89..43dd9b73b352 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java @@ -74,6 +74,12 @@ public class Interpolators { 0.05f, 0.7f, 0.1f, 1f); /** + * The standard accelerating interpolator that should be used on every regular movement of + * content that is disappearing e.g. when moving off screen. + */ + public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(0.3f, 0f, 1f, 1f); + + /** * The standard decelerating interpolator that should be used on every regular movement of * content that is appearing e.g. when coming from off screen. */ diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt index 0586e265eced..4ecace0292cf 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt @@ -19,52 +19,76 @@ package com.android.wm.shell.shared.animation import android.animation.Animator import android.animation.AnimatorSet import android.animation.ValueAnimator -import android.util.DisplayMetrics +import android.content.Context +import android.os.Handler +import android.view.Choreographer import android.view.SurfaceControl.Transaction -import android.view.animation.LinearInterpolator -import android.view.animation.PathInterpolator import android.window.TransitionInfo.Change +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW +import com.android.internal.jank.InteractionJankMonitor /** Creates minimization animation */ object MinimizeAnimator { private const val MINIMIZE_ANIM_ALPHA_DURATION_MS = 100L - private val STANDARD_ACCELERATE = PathInterpolator(0.3f, 0f, 1f, 1f) - private val minimizeBoundsAnimationDef = WindowAnimator.BoundsAnimationParams( durationMs = 200, endOffsetYDp = 12f, endScale = 0.97f, - interpolator = STANDARD_ACCELERATE, + interpolator = Interpolators.STANDARD_ACCELERATE, ) + /** + * Creates a minimize animator for given task [Change]. + * + * @param onAnimFinish finish-callback for the animation, note that this is called on the same + * thread as the animation itself. + * @param animationHandler the Handler that the animation is running on. + */ @JvmStatic fun create( - displayMetrics: DisplayMetrics, + context: Context, change: Change, transaction: Transaction, onAnimFinish: (Animator) -> Unit, + interactionJankMonitor: InteractionJankMonitor, + animationHandler: Handler, ): Animator { val boundsAnimator = WindowAnimator.createBoundsAnimator( - displayMetrics, + context.resources.displayMetrics, minimizeBoundsAnimationDef, change, transaction, ) val alphaAnimator = ValueAnimator.ofFloat(1f, 0f).apply { duration = MINIMIZE_ANIM_ALPHA_DURATION_MS - interpolator = LinearInterpolator() + interpolator = Interpolators.LINEAR addUpdateListener { animation -> - transaction.setAlpha(change.leash, animation.animatedValue as Float).apply() + transaction + .setAlpha(change.leash, animation.animatedValue as Float) + .setFrameTimeline(Choreographer.getInstance().vsyncId) + .apply() } } val listener = object : Animator.AnimatorListener { - override fun onAnimationEnd(animator: Animator) = onAnimFinish(animator) - override fun onAnimationCancel(animator: Animator) = Unit + override fun onAnimationStart(animator: Animator) { + interactionJankMonitor.begin( + change.leash, + context, + animationHandler, + CUJ_DESKTOP_MODE_MINIMIZE_WINDOW, + ) + } + override fun onAnimationCancel(animator: Animator) { + interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) + } override fun onAnimationRepeat(animator: Animator) = Unit - override fun onAnimationStart(animator: Animator) = Unit + override fun onAnimationEnd(animator: Animator) { + interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) + onAnimFinish(animator) + } } return AnimatorSet().apply { playTogether(boundsAnimator, alphaAnimator) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java index ee3e39e71558..e9dc6132f5f4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java @@ -162,22 +162,31 @@ public abstract class WMShellConcurrencyModule { } } - /** - * Provide a Shell animation-thread Executor. - */ + /** Provide a Shell animation-thread Handler. */ @WMSingleton @Provides @ShellAnimationThread - public static ShellExecutor provideShellAnimationExecutor() { - HandlerThread shellAnimationThread = new HandlerThread("wmshell.anim", - THREAD_PRIORITY_DISPLAY); - shellAnimationThread.start(); + public static Handler provideShellAnimationHandler() { + HandlerThread animThread = new HandlerThread("wmshell.anim", THREAD_PRIORITY_DISPLAY); + animThread.start(); if (Build.IS_DEBUGGABLE) { - shellAnimationThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER); - shellAnimationThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS, + animThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER); + animThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS, MSGQ_SLOW_DELIVERY_THRESHOLD_MS); } - return new HandlerExecutor(Handler.createAsync(shellAnimationThread.getLooper())); + return Handler.createAsync(animThread.getLooper()); + } + + /** + * Provide a Shell animation-thread Executor. + */ + @WMSingleton + @Provides + @ShellAnimationThread + public static ShellExecutor provideShellAnimationExecutor( + @ShellAnimationThread Handler animHandler + ) { + return new HandlerExecutor(animHandler); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 5a246e7c99b9..5d5e4d3ec758 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -431,9 +431,10 @@ public abstract class WMShellModule { Transitions transitions, DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor, - @ShellAnimationThread ShellExecutor animExecutor) { + @ShellAnimationThread ShellExecutor animExecutor, + @ShellAnimationThread Handler animHandler) { return new FreeformTaskTransitionHandler( - transitions, displayController, mainExecutor, animExecutor); + transitions, displayController, mainExecutor, animExecutor, animHandler); } @WMSingleton @@ -1101,8 +1102,9 @@ public abstract class WMShellModule { Context context, @ShellMainThread ShellExecutor mainExecutor, @ShellAnimationThread ShellExecutor animExecutor, - @ShellMainThread Handler handler) { - return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor, handler); + @ShellAnimationThread Handler animHandler) { + return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor, + animHandler); } @WMSingleton @@ -1110,9 +1112,10 @@ public abstract class WMShellModule { static DesktopMinimizationTransitionHandler provideDesktopMinimizationTransitionHandler( @ShellMainThread ShellExecutor mainExecutor, @ShellAnimationThread ShellExecutor animExecutor, - DisplayController displayController) { + DisplayController displayController, + @ShellAnimationThread Handler mainHandler) { return new DesktopMinimizationTransitionHandler(mainExecutor, animExecutor, - displayController); + displayController, mainHandler); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt index 1ce093e02a4a..b22a46edbf3a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt @@ -37,7 +37,6 @@ import com.android.app.animation.Interpolators import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_CLOSE_TASK import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.transition.Transitions import java.util.function.Supplier @@ -49,7 +48,7 @@ constructor( private val mainExecutor: ShellExecutor, private val animExecutor: ShellExecutor, private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }, - @ShellMainThread private val handler: Handler, + private val animHandler: Handler, ) : Transitions.TransitionHandler { private val runningAnimations = mutableMapOf<IBinder, List<Animator>>() @@ -95,7 +94,7 @@ constructor( interactionJankMonitor.begin( lastChangeLeash, context, - handler, + animHandler, CUJ_DESKTOP_MODE_CLOSE_TASK, ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt index 728638d9bbd3..7074e8bc9cce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt @@ -18,14 +18,17 @@ package com.android.wm.shell.desktopmode import android.animation.Animator import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.os.Handler import android.os.IBinder -import android.util.DisplayMetrics import android.view.SurfaceControl.Transaction import android.window.TransitionInfo import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction +import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.animation.MinimizeAnimator.create import com.android.wm.shell.transition.Transitions @@ -41,6 +44,7 @@ class DesktopMinimizationTransitionHandler( private val mainExecutor: ShellExecutor, private val animExecutor: ShellExecutor, private val displayController: DisplayController, + private val animHandler: Handler, ) : Transitions.TransitionHandler { /** Shouldn't handle anything */ @@ -90,10 +94,30 @@ class DesktopMinimizationTransitionHandler( val t = Transaction() val sc = change.leash finishTransaction.hide(sc) - val displayMetrics: DisplayMetrics? = - change.taskInfo?.let { - displayController.getDisplayContext(it.displayId)?.getResources()?.displayMetrics - } - return displayMetrics?.let { create(it, change, t, onAnimFinish) } + val displayContext = + change.taskInfo?.let { displayController.getDisplayContext(it.displayId) } + if (displayContext == null) { + logW( + "displayContext is null for taskId=${change.taskInfo?.taskId}, " + + "displayId=${change.taskInfo?.displayId}" + ) + return null + } + return create( + displayContext, + change, + t, + onAnimFinish, + InteractionJankMonitor.getInstance(), + animHandler, + ) + } + + private companion object { + private fun logW(msg: String, vararg arguments: Any?) { + ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + const val TAG = "DesktopMinimizationTransitionHandler" } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index 81b136dd1569..f9ab359e952d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -26,7 +26,6 @@ import android.window.DesktopModeFlags import android.window.TransitionInfo import android.window.WindowContainerTransaction import androidx.annotation.VisibleForTesting -import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW import com.android.internal.jank.InteractionJankMonitor import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer @@ -176,28 +175,13 @@ class DesktopTasksLimiter( return taskChange.mode == TRANSIT_TO_BACK } - override fun onTransitionStarting(transition: IBinder) { - val mActiveTaskDetails = activeTransitionTokensAndTasks[transition] - val info = mActiveTaskDetails?.transitionInfo ?: return - val minimizeChange = getMinimizeChange(info, mActiveTaskDetails.taskId) ?: return - // Begin minimize window CUJ instrumentation. - interactionJankMonitor.begin( - minimizeChange.leash, - context, - handler, - CUJ_DESKTOP_MODE_MINIMIZE_WINDOW, - ) - } - private fun getMinimizeChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? = info.changes.find { change -> change.taskInfo?.taskId == taskId && change.mode == TRANSIT_TO_BACK } override fun onTransitionMerged(merged: IBinder, playing: IBinder) { - if (activeTransitionTokensAndTasks.remove(merged) != null) { - interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) - } + activeTransitionTokensAndTasks.remove(merged) pendingTransitionTokensAndTasks.remove(merged)?.let { taskToTransfer -> pendingTransitionTokensAndTasks[playing] = taskToTransfer } @@ -209,13 +193,6 @@ class DesktopTasksLimiter( } override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { - if (activeTransitionTokensAndTasks.remove(transition) != null) { - if (aborted) { - interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) - } else { - interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) - } - } pendingTransitionTokensAndTasks.remove(transition) activeUnminimizeTransitionTokensAndTasks.remove(transition) pendingUnminimizeTransitionTokensAndTasks.remove(transition) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index b60fb5e7bfdd..16e411e1fc07 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -25,10 +25,12 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.ActivityManager; import android.app.WindowConfiguration; +import android.content.Context; import android.graphics.Rect; +import android.os.Handler; import android.os.IBinder; import android.util.ArrayMap; -import android.util.DisplayMetrics; +import android.util.Log; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; @@ -38,6 +40,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.shared.animation.MinimizeAnimator; @@ -52,11 +55,13 @@ import java.util.List; */ public class FreeformTaskTransitionHandler implements Transitions.TransitionHandler, FreeformTaskTransitionStarter { + private static final String TAG = "FreeformTaskTransitionHandler"; private static final int CLOSE_ANIM_DURATION = 400; private final Transitions mTransitions; private final DisplayController mDisplayController; private final ShellExecutor mMainExecutor; private final ShellExecutor mAnimExecutor; + private final Handler mAnimHandler; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); @@ -66,11 +71,13 @@ public class FreeformTaskTransitionHandler Transitions transitions, DisplayController displayController, ShellExecutor mainExecutor, - ShellExecutor animExecutor) { + ShellExecutor animExecutor, + Handler animHandler) { mTransitions = transitions; mDisplayController = displayController; mMainExecutor = mainExecutor; mAnimExecutor = animExecutor; + mAnimHandler = animHandler; } @Override @@ -123,13 +130,11 @@ public class FreeformTaskTransitionHandler @NonNull Transitions.TransitionFinishCallback finishCallback) { boolean transitionHandled = false; final ArrayList<Animator> animations = new ArrayList<>(); - final Runnable onAnimFinish = () -> { + final Runnable onAnimFinish = () -> mMainExecutor.execute(() -> { if (!animations.isEmpty()) return; - mMainExecutor.execute(() -> { - mAnimations.remove(transition); - finishCallback.onTransitionFinished(null /* wct */); - }); - }; + mAnimations.remove(transition); + finishCallback.onTransitionFinished(null /* wct */); + }); for (TransitionInfo.Change change : info.getChanges()) { if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) { continue; @@ -234,18 +239,25 @@ public class FreeformTaskTransitionHandler SurfaceControl.Transaction t = new SurfaceControl.Transaction(); SurfaceControl sc = change.getLeash(); finishT.hide(sc); - final DisplayMetrics displayMetrics = - mDisplayController - .getDisplayContext(taskInfo.displayId).getResources().getDisplayMetrics(); + final Context displayContext = + mDisplayController.getDisplayContext(taskInfo.displayId); + if (displayContext == null) { + Log.w(TAG, "No displayContext for displayId=" + taskInfo.displayId); + return false; + } final Animator animator = MinimizeAnimator.create( - displayMetrics, + displayContext, change, t, (anim) -> { - animations.remove(anim); - onAnimFinish.run(); + mMainExecutor.execute(() -> { + animations.remove(anim); + onAnimFinish.run(); + }); return null; - }); + }, + InteractionJankMonitor.getInstance(), + mAnimHandler); animations.add(animator); return true; } @@ -277,8 +289,10 @@ public class FreeformTaskTransitionHandler new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - animations.remove(animator); - onAnimFinish.run(); + mMainExecutor.execute(() -> { + animations.remove(animator); + onAnimFinish.run(); + }); } }); animations.add(animator); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt index 4c3325d4d1de..0d1c57221fb9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt @@ -21,6 +21,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WindowingMode +import android.os.Handler import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.SurfaceControl @@ -56,7 +57,12 @@ class DesktopMinimizationTransitionHandlerTest : ShellTestCase() { @Before fun setUp() { handler = - DesktopMinimizationTransitionHandler(testExecutor, testExecutor, displayController) + DesktopMinimizationTransitionHandler( + testExecutor, + testExecutor, + displayController, + mock<Handler>(), + ) whenever(displayController.getDisplayContext(any())).thenReturn(mContext) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index d33209dbc30e..62e3c544e390 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -37,7 +37,6 @@ import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.StaticMockitoSession -import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION import com.android.wm.shell.ShellTaskOrganizer @@ -72,8 +71,6 @@ import org.mockito.Mockito.any import org.mockito.Mockito.mock import org.mockito.Mockito.spy import org.mockito.Mockito.`when` -import org.mockito.kotlin.eq -import org.mockito.kotlin.verify import org.mockito.quality.Strictness /** @@ -521,85 +518,6 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test - fun minimizeTransitionReadyAndFinished_logsJankInstrumentationBeginAndEnd() { - desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) - desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) - (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } - val transition = Binder() - val task = setUpFreeformTask() - addPendingMinimizeChange(transition, taskId = task.taskId) - - callOnTransitionReady( - transition, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - ) - - desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition) - - verify(interactionJankMonitor) - .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - - desktopTasksLimiter - .getTransitionObserver() - .onTransitionFinished(transition, /* aborted= */ false) - - verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - } - - @Test - fun minimizeTransitionReadyAndAborted_logsJankInstrumentationBeginAndCancel() { - desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) - desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) - (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } - val transition = Binder() - val task = setUpFreeformTask() - addPendingMinimizeChange(transition, taskId = task.taskId) - - callOnTransitionReady( - transition, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - ) - - desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition) - - verify(interactionJankMonitor) - .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - - desktopTasksLimiter - .getTransitionObserver() - .onTransitionFinished(transition, /* aborted= */ true) - - verify(interactionJankMonitor).cancel(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - } - - @Test - fun minimizeTransitionReadyAndMerged_logsJankInstrumentationBeginAndEnd() { - desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) - desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) - (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } - val mergedTransition = Binder() - val newTransition = Binder() - val task = setUpFreeformTask() - addPendingMinimizeChange(mergedTransition, taskId = task.taskId) - - callOnTransitionReady( - mergedTransition, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - ) - - desktopTasksLimiter.getTransitionObserver().onTransitionStarting(mergedTransition) - - verify(interactionJankMonitor) - .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - - desktopTasksLimiter - .getTransitionObserver() - .onTransitionMerged(mergedTransition, newTransition) - - verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - } - - @Test fun getMinimizingTask_noPendingTransition_returnsNull() { val transition = Binder() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/MinimizeAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/MinimizeAnimatorTest.kt new file mode 100644 index 000000000000..ba609d4322fc --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/MinimizeAnimatorTest.kt @@ -0,0 +1,118 @@ +/* + * 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.wm.shell.shared.animation + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ValueAnimator +import android.content.Context +import android.content.res.Resources +import android.os.Handler +import android.util.DisplayMetrics +import android.view.SurfaceControl +import android.view.SurfaceControl.Transaction +import android.window.TransitionInfo +import android.window.WindowContainerToken +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW +import com.android.internal.jank.InteractionJankMonitor +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyFloat +import org.mockito.ArgumentMatchers.anyLong +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 MinimizeAnimatorTest { + private val context = mock<Context>() + private val resources = mock<Resources>() + private val transaction = mock<Transaction>() + private val leash = mock<SurfaceControl>() + private val interactionJankMonitor = mock<InteractionJankMonitor>() + private val animationHandler = mock<Handler>() + + private val displayMetrics = DisplayMetrics().apply { density = 1f } + + @Before + fun setup() { + whenever(context.resources).thenReturn(resources) + whenever(resources.displayMetrics).thenReturn(displayMetrics) + whenever(transaction.setAlpha(any(), anyFloat())).thenReturn(transaction) + whenever(transaction.setPosition(any(), anyFloat(), anyFloat())).thenReturn(transaction) + whenever(transaction.setScale(any(), anyFloat(), anyFloat())).thenReturn(transaction) + whenever(transaction.setFrameTimeline(anyLong())).thenReturn(transaction) + } + + @Test + fun create_returnsBoundsAndAlphaAnimators() { + val change = TransitionInfo.Change(mock<WindowContainerToken>(), leash) + + val animator = createAnimator(change) + + assertThat(animator).isInstanceOf(AnimatorSet::class.java) + val animatorSet = animator as AnimatorSet + assertThat(animatorSet.childAnimations).hasSize(2) + assertIsBoundsAnimator(animatorSet.childAnimations[0]) + assertIsAlphaAnimator(animatorSet.childAnimations[1]) + } + + @Test + fun create_doesNotlogJankInstrumentation() = runOnUiThread { + val change = TransitionInfo.Change(mock<WindowContainerToken>(), leash) + + createAnimator(change) + + verify(interactionJankMonitor, never()).begin( + leash, context, animationHandler, CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) + } + + @Test + fun onAnimationStart_logsJankInstrumentation() = runOnUiThread { + val change = TransitionInfo.Change(mock<WindowContainerToken>(), leash) + + createAnimator(change).start() + + verify(interactionJankMonitor).begin( + leash, context, animationHandler, CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) + } + + private fun createAnimator(change: TransitionInfo.Change): Animator = + MinimizeAnimator.create(context, change, transaction, {}, interactionJankMonitor, + animationHandler) + + private fun assertIsBoundsAnimator(animator: Animator) { + assertThat(animator).isInstanceOf(ValueAnimator::class.java) + assertThat(animator.duration).isEqualTo(200) + assertThat(animator.interpolator).isEqualTo(Interpolators.STANDARD_ACCELERATE) + } + + private fun assertIsAlphaAnimator(animator: Animator) { + assertThat(animator).isInstanceOf(ValueAnimator::class.java) + assertThat(animator.duration).isEqualTo(100) + assertThat(animator.interpolator).isEqualTo(Interpolators.LINEAR) + } +} + |