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

Commit d5f0275c authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Exit desktop windowing transition" into main

parents f87fc38d c715ed18
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -64,7 +64,8 @@ public enum DesktopModeFlags {
    ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
    ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS(
            Flags::enableDesktopWindowingTaskbarRunningApps, true),
    ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false);
    ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
    ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false);

    private static final String TAG = "DesktopModeFlagsUtil";
    // Function called to obtain aconfig flag value.
+1 −0
Original line number Diff line number Diff line
@@ -220,6 +220,7 @@ android_library {
        "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
        "//frameworks/libs/systemui:iconloader_base",
        "com_android_wm_shell_flags_lib",
        "PlatformAnimationLib",
        "WindowManager-Shell-proto",
        "WindowManager-Shell-lite-proto",
        "WindowManager-Shell-shared",
+67 −17
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.wm.shell.dagger;

import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS;
import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;

import android.annotation.Nullable;
@@ -61,8 +62,10 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
@@ -87,6 +90,8 @@ import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarterInitializer;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipTransitionController;
@@ -332,9 +337,13 @@ public abstract class WMShellModule {
    static FreeformComponents provideFreeformComponents(
            FreeformTaskListener taskListener,
            FreeformTaskTransitionHandler transitionHandler,
            FreeformTaskTransitionObserver transitionObserver) {
            FreeformTaskTransitionObserver transitionObserver,
            FreeformTaskTransitionStarterInitializer transitionStarterInitializer) {
        return new FreeformComponents(
                taskListener, Optional.of(transitionHandler), Optional.of(transitionObserver));
                taskListener,
                Optional.of(transitionHandler),
                Optional.of(transitionObserver),
                Optional.of(transitionStarterInitializer));
    }

    @WMSingleton
@@ -358,27 +367,15 @@ public abstract class WMShellModule {
    @WMSingleton
    @Provides
    static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
            ShellInit shellInit,
            Transitions transitions,
            Context context,
            WindowDecorViewModel windowDecorViewModel,
            DisplayController displayController,
            @ShellMainThread ShellExecutor mainExecutor,
            @ShellAnimationThread ShellExecutor animExecutor,
            @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
            InteractionJankMonitor interactionJankMonitor,
            @ShellMainThread Handler handler) {
            @ShellAnimationThread ShellExecutor animExecutor) {
        return new FreeformTaskTransitionHandler(
                shellInit,
                transitions,
                context,
                windowDecorViewModel,
                displayController,
                mainExecutor,
                animExecutor,
                desktopModeTaskRepository,
                interactionJankMonitor,
                handler);
                animExecutor);
    }

    @WMSingleton
@@ -392,6 +389,23 @@ public abstract class WMShellModule {
                context, shellInit, transitions, windowDecorViewModel);
    }

    @WMSingleton
    @Provides
    static FreeformTaskTransitionStarterInitializer provideFreeformTaskTransitionStarterInitializer(
            ShellInit shellInit,
            WindowDecorViewModel windowDecorViewModel,
            FreeformTaskTransitionHandler freeformTaskTransitionHandler,
            Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler) {
        FreeformTaskTransitionStarter transitionStarter;
        if (desktopMixedTransitionHandler.isPresent()) {
            transitionStarter = desktopMixedTransitionHandler.get();
        } else {
            transitionStarter = freeformTaskTransitionHandler;
        }
        return new FreeformTaskTransitionStarterInitializer(shellInit, windowDecorViewModel,
                transitionStarter);
    }

    //
    // One handed mode
    //
@@ -704,6 +718,16 @@ public abstract class WMShellModule {
                transitions, context, interactionJankMonitor, handler);
    }

    @WMSingleton
    @Provides
    static CloseDesktopTaskTransitionHandler provideCloseDesktopTaskTransitionHandler(
            Context context,
            @ShellMainThread ShellExecutor mainExecutor,
            @ShellAnimationThread ShellExecutor animExecutor
    ) {
        return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor);
    }

    @WMSingleton
    @Provides
    static DesktopModeDragAndDropTransitionHandler provideDesktopModeDragAndDropTransitionHandler(
@@ -758,6 +782,32 @@ public abstract class WMShellModule {
        );
    }

    @WMSingleton
    @Provides
    static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
            Context context,
            Transitions transitions,
            @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
            FreeformTaskTransitionHandler freeformTaskTransitionHandler,
            CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
            InteractionJankMonitor interactionJankMonitor,
            @ShellMainThread Handler handler
    ) {
        if (!DesktopModeStatus.canEnterDesktopMode(context)
                || !ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue()) {
            return Optional.empty();
        }
        return Optional.of(
                new DesktopMixedTransitionHandler(
                        context,
                        transitions,
                        desktopModeTaskRepository,
                        freeformTaskTransitionHandler,
                        closeDesktopTaskTransitionHandler,
                        interactionJankMonitor,
                        handler));
    }

    @WMSingleton
    @Provides
    static DesktopModeLoggerTransitionObserver provideDesktopModeLoggerTransitionObserver(
+153 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.desktopmode

import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.RectEvaluator
import android.animation.ValueAnimator
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.graphics.Rect
import android.os.IBinder
import android.util.TypedValue
import android.view.SurfaceControl.Transaction
import android.view.WindowManager
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.core.animation.addListener
import com.android.app.animation.Interpolators
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.transition.Transitions
import java.util.function.Supplier

/** The [Transitions.TransitionHandler] that handles transitions for closing desktop mode tasks. */
class CloseDesktopTaskTransitionHandler
@JvmOverloads
constructor(
    private val context: Context,
    private val mainExecutor: ShellExecutor,
    private val animExecutor: ShellExecutor,
    private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
) : Transitions.TransitionHandler {

    private val runningAnimations = mutableMapOf<IBinder, List<Animator>>()

    /** Returns null, as it only handles transitions started from Shell. */
    override fun handleRequest(
        transition: IBinder,
        request: TransitionRequestInfo,
    ): WindowContainerTransaction? = null

    override fun startAnimation(
        transition: IBinder,
        info: TransitionInfo,
        startTransaction: Transaction,
        finishTransaction: Transaction,
        finishCallback: Transitions.TransitionFinishCallback,
    ): Boolean {
        if (info.type != WindowManager.TRANSIT_CLOSE) return false
        val animations = mutableListOf<Animator>()
        val onAnimFinish: (Animator) -> Unit = { animator ->
            mainExecutor.execute {
                // Animation completed
                animations.remove(animator)
                if (animations.isEmpty()) {
                    // All animations completed, finish the transition
                    runningAnimations.remove(transition)
                    finishCallback.onTransitionFinished(/* wct= */ null)
                }
            }
        }
        animations +=
            info.changes
                .filter {
                    it.mode == WindowManager.TRANSIT_CLOSE &&
                        it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
                }
                .map { createCloseAnimation(it, finishTransaction, onAnimFinish) }
        if (animations.isEmpty()) return false
        runningAnimations[transition] = animations
        animExecutor.execute { animations.forEach(Animator::start) }
        return true
    }

    private fun createCloseAnimation(
        change: TransitionInfo.Change,
        finishTransaction: Transaction,
        onAnimFinish: (Animator) -> Unit,
    ): Animator {
        finishTransaction.hide(change.leash)
        return AnimatorSet().apply {
            playTogether(createBoundsCloseAnimation(change), createAlphaCloseAnimation(change))
            addListener(onEnd = onAnimFinish)
        }
    }

    private fun createBoundsCloseAnimation(change: TransitionInfo.Change): Animator {
        val startBounds = change.startAbsBounds
        val endBounds =
            Rect(startBounds).apply {
                // Scale the end bounds of the window down with an anchor in the center
                inset(
                    (startBounds.width().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt(),
                    (startBounds.height().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt()
                )
                val offsetY =
                    TypedValue.applyDimension(
                            TypedValue.COMPLEX_UNIT_DIP,
                            CLOSE_ANIM_OFFSET_Y,
                            context.resources.displayMetrics
                        )
                        .toInt()
                offset(/* dx= */ 0, offsetY)
            }
        return ValueAnimator.ofObject(RectEvaluator(), startBounds, endBounds).apply {
            duration = CLOSE_ANIM_DURATION_BOUNDS
            interpolator = Interpolators.STANDARD_ACCELERATE
            addUpdateListener { animation ->
                val animBounds = animation.animatedValue as Rect
                val animScale = 1 - (1 - CLOSE_ANIM_SCALE) * animation.animatedFraction
                transactionSupplier
                    .get()
                    .setPosition(change.leash, animBounds.left.toFloat(), animBounds.top.toFloat())
                    .setScale(change.leash, animScale, animScale)
                    .apply()
            }
        }
    }

    private fun createAlphaCloseAnimation(change: TransitionInfo.Change): Animator =
        ValueAnimator.ofFloat(1f, 0f).apply {
            duration = CLOSE_ANIM_DURATION_ALPHA
            interpolator = Interpolators.LINEAR
            addUpdateListener { animation ->
                transactionSupplier
                    .get()
                    .setAlpha(change.leash, animation.animatedValue as Float)
                    .apply()
            }
        }

    private companion object {
        const val CLOSE_ANIM_DURATION_BOUNDS = 200L
        const val CLOSE_ANIM_DURATION_ALPHA = 100L
        const val CLOSE_ANIM_SCALE = 0.95f
        const val CLOSE_ANIM_OFFSET_Y = 36.0f
    }
}
+157 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.desktopmode

import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.os.Handler
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.transition.MixedTransitionHandler
import com.android.wm.shell.transition.Transitions

/** The [Transitions.TransitionHandler] coordinates transition handlers in desktop windowing. */
class DesktopMixedTransitionHandler(
    private val context: Context,
    private val transitions: Transitions,
    private val desktopTaskRepository: DesktopModeTaskRepository,
    private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler,
    private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler,
    private val interactionJankMonitor: InteractionJankMonitor,
    @ShellMainThread private val handler: Handler,
) : MixedTransitionHandler, FreeformTaskTransitionStarter {

    /** Delegates starting transition to [FreeformTaskTransitionHandler]. */
    override fun startWindowingModeTransition(
        targetWindowingMode: Int,
        wct: WindowContainerTransaction?,
    ) = freeformTaskTransitionHandler.startWindowingModeTransition(targetWindowingMode, wct)

    /** Delegates starting minimized mode transition to [FreeformTaskTransitionHandler]. */
    override fun startMinimizedModeTransition(wct: WindowContainerTransaction?): IBinder =
        freeformTaskTransitionHandler.startMinimizedModeTransition(wct)

    /** Starts close transition and handles or delegates desktop task close animation. */
    override fun startRemoveTransition(wct: WindowContainerTransaction?) {
        requireNotNull(wct)
        transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this)
    }

    /** Returns null, as it only handles transitions started from Shell. */
    override fun handleRequest(
        transition: IBinder,
        request: TransitionRequestInfo,
    ): WindowContainerTransaction? = null

    override fun startAnimation(
        transition: IBinder,
        info: TransitionInfo,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction,
        finishCallback: Transitions.TransitionFinishCallback,
    ): Boolean {
        val closeChange = findCloseDesktopTaskChange(info)
        if (closeChange == null) {
            ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: Should have closing desktop task", TAG)
            return false
        }
        if (isLastDesktopTask(closeChange)) {
            // Dispatch close desktop task animation to the default transition handlers.
            return dispatchCloseLastDesktopTaskAnimation(
                transition,
                info,
                closeChange,
                startTransaction,
                finishTransaction,
                finishCallback,
            )
        }
        // Animate close desktop task transition with [CloseDesktopTaskTransitionHandler].
        return closeDesktopTaskTransitionHandler.startAnimation(
            transition,
            info,
            startTransaction,
            finishTransaction,
            finishCallback,
        )
    }

    /**
     * Dispatch close desktop task animation to the default transition handlers. Allows delegating
     * it to Launcher to animate in sync with show Home transition.
     */
    private fun dispatchCloseLastDesktopTaskAnimation(
        transition: IBinder,
        info: TransitionInfo,
        change: TransitionInfo.Change,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction,
        finishCallback: Transitions.TransitionFinishCallback,
    ): Boolean {
        // Starting the jank trace if closing the last window in desktop mode.
        interactionJankMonitor.begin(
            change.leash,
            context,
            handler,
            CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE,
        )
        // Dispatch the last desktop task closing animation.
        return transitions.dispatchTransition(
            transition,
            info,
            startTransaction,
            finishTransaction,
            { wct ->
                // Finish the jank trace when closing the last window in desktop mode.
                interactionJankMonitor.end(CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE)
                finishCallback.onTransitionFinished(wct)
            },
            /* skip= */ this
        ) != null
    }

    private fun isLastDesktopTask(change: TransitionInfo.Change): Boolean =
        change.taskInfo?.let {
            desktopTaskRepository.getActiveNonMinimizedTaskCount(it.displayId) == 1
        } ?: false

    private fun findCloseDesktopTaskChange(info: TransitionInfo): TransitionInfo.Change? {
        if (info.type != WindowManager.TRANSIT_CLOSE) return null
        return info.changes.firstOrNull { change ->
            change.mode == WindowManager.TRANSIT_CLOSE &&
                !change.hasFlags(TransitionInfo.FLAG_IS_WALLPAPER) &&
                change.taskInfo?.taskId != INVALID_TASK_ID &&
                change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
        }
    }

    companion object {
        private const val TAG = "DesktopMixedTransitionHandler"
    }
}
Loading