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

Commit bfb8d9be authored by Matt Sziklay's avatar Matt Sziklay Committed by Android (Google) Code Review
Browse files

Merge "Allow different tab tearing transitions." into main

parents 55590219 b5e3c3ce
Loading
Loading
Loading
Loading
+12 −1
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ 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.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -558,6 +559,7 @@ public abstract class WMShellModule {
            ReturnToDragStartAnimator returnToDragStartAnimator,
            EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
            ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
            DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler,
            ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
            DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
            @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
@@ -573,7 +575,8 @@ public abstract class WMShellModule {
                displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
                dragAndDropController, transitions, keyguardManager,
                returnToDragStartAnimator, enterDesktopTransitionHandler,
                exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
                exitDesktopTransitionHandler, desktopModeDragAndDropTransitionHandler,
                toggleResizeDesktopTaskTransitionHandler,
                dragToDesktopTransitionHandler, desktopModeTaskRepository,
                desktopModeLoggerTransitionObserver, launchAdjacentController,
                recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
@@ -653,6 +656,14 @@ public abstract class WMShellModule {
            transitions, context, interactionJankMonitor);
    }

    @WMSingleton
    @Provides
    static DesktopModeDragAndDropTransitionHandler provideDesktopModeDragAndDropTransitionHandler(
            Transitions transitions
    ) {
        return new DesktopModeDragAndDropTransitionHandler(transitions);
    }

    @WMSingleton
    @Provides
    @DynamicOverride
+111 −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.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_OPEN
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback

/**
 * Transition handler for drag-and-drop (i.e., tab tear) transitions that occur in desktop mode.
 */
class DesktopModeDragAndDropTransitionHandler(private val transitions: Transitions) :
    Transitions.TransitionHandler {
    private val pendingTransitionTokens: MutableList<IBinder> = mutableListOf()

    /**
     * Begin a transition when a [android.app.PendingIntent] is dropped without a window to
     * accept it.
     */
    fun handleDropEvent(wct: WindowContainerTransaction): IBinder {
        val token = transitions.startTransition(TRANSIT_OPEN, wct, this)
        pendingTransitionTokens.add(token)
        return token
    }

    override fun startAnimation(
        transition: IBinder,
        info: TransitionInfo,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction,
        finishCallback: TransitionFinishCallback
    ): Boolean {
        if (!pendingTransitionTokens.contains(transition)) return false
        val change = findRelevantChange(info)
        val leash = change.leash
        val endBounds = change.endAbsBounds
        startTransaction.hide(leash)
            .setWindowCrop(leash, endBounds.width(), endBounds.height())
            .apply()
        val animator = ValueAnimator()
        animator.setFloatValues(0f, 1f)
        animator.setDuration(FADE_IN_ANIMATION_DURATION)
        val t = SurfaceControl.Transaction()
        animator.addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationStart(animation: Animator) {
                t.show(leash)
                t.apply()
            }

            override fun onAnimationEnd(animation: Animator) {
                finishCallback.onTransitionFinished(null)
            }
        })
        animator.addUpdateListener { animation: ValueAnimator ->
            t.setAlpha(leash, animation.animatedFraction)
            t.apply()
        }
        animator.start()
        pendingTransitionTokens.remove(transition)
        return true
    }

    private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change {
        val matchingChanges =
            info.changes.filter { c ->
                isValidTaskChange(c) && c.mode == TRANSIT_OPEN
            }
        if (matchingChanges.size != 1) {
            throw IllegalStateException(
                "Expected 1 relevant change but found: ${matchingChanges.size}"
            )
        }
        return matchingChanges.first()
    }

    private fun isValidTaskChange(change: TransitionInfo.Change): Boolean {
        return change.taskInfo != null && change.taskInfo?.taskId != -1
    }

    override fun handleRequest(
        transition: IBinder,
        request: TransitionRequestInfo
    ): WindowContainerTransaction? {
        return null
    }

    companion object {
        const val FADE_IN_ANIMATION_DURATION = 300L
    }
}
+57 −25
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.wm.shell.desktopmode;

import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
@@ -29,7 +28,6 @@ import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
@@ -70,6 +68,37 @@ public class DesktopModeVisualIndicator {
        TO_SPLIT_RIGHT_INDICATOR
    }

    /**
     * The conditions surrounding the drag event that led to the indicator's creation.
     */
    public enum DragStartState {
        /** The indicator is resulting from a freeform task drag. */
        FROM_FREEFORM,
        /** The indicator is resulting from a split screen task drag */
        FROM_SPLIT,
        /** The indicator is resulting from a fullscreen task drag */
        FROM_FULLSCREEN,
        /** The indicator is resulting from an Intent generated during a drag-and-drop event */
        DRAGGED_INTENT;

        /**
         * Get the {@link DragStartState} of a drag event based on the windowing mode of the task.
         * Note that DRAGGED_INTENT will be specified by the caller if needed and not returned
         * here.
         */
        public static DesktopModeVisualIndicator.DragStartState getDragStartState(
                ActivityManager.RunningTaskInfo taskInfo
        ) {
            if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
                return FROM_FULLSCREEN;
            } else if (taskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
                return FROM_SPLIT;
            } else if (taskInfo.isFreeform()) {
                return FROM_FREEFORM;
            } else return null;
        }
    }

    private final Context mContext;
    private final DisplayController mDisplayController;
    private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
@@ -82,11 +111,13 @@ public class DesktopModeVisualIndicator {

    private View mView;
    private IndicatorType mCurrentType;
    private DragStartState mDragStartState;

    public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
            ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
            Context context, SurfaceControl taskSurface,
            RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
            RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer,
            DragStartState dragStartState) {
        mSyncQueue = syncQueue;
        mTaskInfo = taskInfo;
        mDisplayController = displayController;
@@ -94,6 +125,7 @@ public class DesktopModeVisualIndicator {
        mTaskSurface = taskSurface;
        mRootTdaOrganizer = taskDisplayAreaOrganizer;
        mCurrentType = IndicatorType.NO_INDICATOR;
        mDragStartState = dragStartState;
    }

    /**
@@ -101,7 +133,7 @@ public class DesktopModeVisualIndicator {
     * display, including no visible indicator.
     */
    @NonNull
    IndicatorType updateIndicatorType(PointF inputCoordinates, int windowingMode) {
    IndicatorType updateIndicatorType(PointF inputCoordinates) {
        final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
        // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
        IndicatorType result = IndicatorType.NO_INDICATOR;
@@ -111,14 +143,13 @@ public class DesktopModeVisualIndicator {
        // account for the possibility of the task going off the top of the screen by captionHeight
        final int captionHeight = mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.desktop_mode_freeform_decor_caption_height);
        final Region fullscreenRegion = calculateFullscreenRegion(layout, windowingMode,
        final Region fullscreenRegion = calculateFullscreenRegion(layout, captionHeight);
        final Region splitLeftRegion = calculateSplitLeftRegion(layout, transitionAreaWidth,
                captionHeight);
        final Region splitLeftRegion = calculateSplitLeftRegion(layout, windowingMode,
                transitionAreaWidth, captionHeight);
        final Region splitRightRegion = calculateSplitRightRegion(layout, windowingMode,
                transitionAreaWidth, captionHeight);
        final Region toDesktopRegion = calculateToDesktopRegion(layout, windowingMode,
                splitLeftRegion, splitRightRegion, fullscreenRegion);
        final Region splitRightRegion = calculateSplitRightRegion(layout, transitionAreaWidth,
                captionHeight);
        final Region toDesktopRegion = calculateToDesktopRegion(layout, splitLeftRegion,
                splitRightRegion, fullscreenRegion);
        if (fullscreenRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
            result = IndicatorType.TO_FULLSCREEN_INDICATOR;
        }
@@ -131,20 +162,22 @@ public class DesktopModeVisualIndicator {
        if (toDesktopRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
            result = IndicatorType.TO_DESKTOP_INDICATOR;
        }
        if (mDragStartState != DragStartState.DRAGGED_INTENT) {
            transitionIndicator(result);
        }
        return result;
    }

    @VisibleForTesting
    Region calculateFullscreenRegion(DisplayLayout layout,
            @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
    Region calculateFullscreenRegion(DisplayLayout layout, int captionHeight) {
        final Region region = new Region();
        int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
        int transitionHeight = mDragStartState == DragStartState.FROM_FREEFORM
                || mDragStartState == DragStartState.DRAGGED_INTENT
                ? mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.desktop_mode_transition_region_thickness)
                : 2 * layout.stableInsets().top;
        // A Rect at the top of the screen that takes up the center 40%.
        if (windowingMode == WINDOWING_MODE_FREEFORM) {
        if (mDragStartState == DragStartState.FROM_FREEFORM) {
            final float toFullscreenScale = mContext.getResources().getFloat(
                    R.dimen.desktop_mode_fullscreen_region_scale);
            final float toFullscreenWidth = (layout.width() * toFullscreenScale);
@@ -153,9 +186,11 @@ public class DesktopModeVisualIndicator {
                    (int) ((layout.width() / 2f) + (toFullscreenWidth / 2f)),
                    transitionHeight));
        }
        // A screen-wide Rect if the task is in fullscreen or split.
        if (windowingMode == WINDOWING_MODE_FULLSCREEN
                || windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
        // A screen-wide Rect if the task is in fullscreen, split, or a dragged intent.
        if (mDragStartState == DragStartState.FROM_FULLSCREEN
                || mDragStartState == DragStartState.FROM_SPLIT
                || mDragStartState == DragStartState.DRAGGED_INTENT
        ) {
            region.union(new Rect(0,
                    -captionHeight,
                    layout.width(),
@@ -166,12 +201,11 @@ public class DesktopModeVisualIndicator {

    @VisibleForTesting
    Region calculateToDesktopRegion(DisplayLayout layout,
            @WindowConfiguration.WindowingMode int windowingMode,
            Region splitLeftRegion, Region splitRightRegion,
            Region toFullscreenRegion) {
        final Region region = new Region();
        // If in desktop, we need no region. Otherwise it's the same for all windowing modes.
        if (windowingMode != WINDOWING_MODE_FREEFORM) {
        if (mDragStartState != DragStartState.FROM_FREEFORM) {
            region.union(new Rect(0, 0, layout.width(), layout.height()));
            region.op(splitLeftRegion, Region.Op.DIFFERENCE);
            region.op(splitRightRegion, Region.Op.DIFFERENCE);
@@ -182,11 +216,10 @@ public class DesktopModeVisualIndicator {

    @VisibleForTesting
    Region calculateSplitLeftRegion(DisplayLayout layout,
            @WindowConfiguration.WindowingMode int windowingMode,
            int transitionEdgeWidth, int captionHeight) {
        final Region region = new Region();
        // In freeform, keep the top corners clear.
        int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
        int transitionHeight = mDragStartState == DragStartState.FROM_FREEFORM
                ? mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
                -captionHeight;
@@ -196,11 +229,10 @@ public class DesktopModeVisualIndicator {

    @VisibleForTesting
    Region calculateSplitRightRegion(DisplayLayout layout,
            @WindowConfiguration.WindowingMode int windowingMode,
            int transitionEdgeWidth, int captionHeight) {
        final Region region = new Region();
        // In freeform, keep the top corners clear.
        int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
        int transitionHeight = mDragStartState == DragStartState.FROM_FREEFORM
                ? mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
                -captionHeight;
+87 −19

File changed.

Preview size limit exceeded, changes collapsed.

+14 −7
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@ import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.annotations.ExternalMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -127,7 +128,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
         * drag.
         */
        default boolean onUnhandledDrag(@NonNull PendingIntent launchIntent,
                @NonNull SurfaceControl dragSurface,
                @NonNull DragEvent dragEvent,
                @NonNull Consumer<Boolean> onFinishCallback) {
            return false;
        }
@@ -329,9 +330,18 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
            return false;
        }

        DragSession dragSession = null;
        if (event.getAction() == ACTION_DRAG_STARTED) {
            mActiveDragDisplay = displayId;
            pd.isHandlingDrag = DragUtils.canHandleDrag(event);
            dragSession = new DragSession(ActivityTaskManager.getInstance(),
                    mDisplayController.getDisplayLayout(displayId), event.getClipData(),
                    event.getDragFlags());
            dragSession.initialize();
            final ActivityManager.RunningTaskInfo taskInfo = dragSession.runningTaskInfo;
            // Desktop tasks will have their own drag handling.
            final boolean isDesktopDrag = taskInfo != null && taskInfo.isFreeform()
                    && DesktopModeStatus.canEnterDesktopMode(mContext);
            pd.isHandlingDrag = DragUtils.canHandleDrag(event) && !isDesktopDrag;
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
                    "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s flags=%s",
                    pd.isHandlingDrag, event.getClipData().getItemCount(),
@@ -349,10 +359,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
                    Slog.w(TAG, "Unexpected drag start during an active drag");
                    return false;
                }
                pd.dragSession = new DragSession(ActivityTaskManager.getInstance(),
                        mDisplayController.getDisplayLayout(displayId), event.getClipData(),
                        event.getDragFlags());
                pd.dragSession.initialize();
                pd.dragSession = dragSession;
                pd.activeDragCount++;
                pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
                if (pd.dragSession.hideDragSourceTaskId != -1) {
@@ -437,7 +444,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
        }

        final boolean handled = notifyListeners(
                l -> l.onUnhandledDrag(launchIntent, dragEvent.getDragSurface(), onFinishCallback));
                l -> l.onUnhandledDrag(launchIntent, dragEvent, onFinishCallback));
        if (!handled) {
            // Nobody handled this, we still have to notify WM
            onFinishCallback.accept(false);
Loading